mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
eth, miner: report local block fees and remote (MEV) fees too
This commit is contained in:
parent
753e77edbc
commit
2f01d3783d
5 changed files with 114 additions and 15 deletions
|
|
@ -611,6 +611,11 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
|||
|
||||
return api.invalid(err, parent.Header()), nil
|
||||
}
|
||||
// Share the block with the miner to pull out any relevant stats to previous
|
||||
// local block production attempt
|
||||
if payload := api.localBlocks.find(block.NumberU64()); payload != nil {
|
||||
api.eth.Miner().ReportFeeMetrics(payload, block)
|
||||
}
|
||||
hash := block.Hash()
|
||||
return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,20 @@ func (q *payloadQueue) has(id engine.PayloadID) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// find tries to find a locally mined block with the given number. This method is
|
||||
// relatively expensive and should only be used sparsely (goal == metrics).
|
||||
func (q *payloadQueue) find(number uint64) *miner.Payload {
|
||||
for _, item := range q.payloads {
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
if item.payload.Number() == number {
|
||||
return item.payload
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerQueueItem represents an hash->header tuple to store until it's retrieved
|
||||
// or evicted.
|
||||
type headerQueueItem struct {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ import "github.com/ethereum/go-ethereum/metrics"
|
|||
// the block fees generated by each recommit round (relative to the latest).
|
||||
var blockRecommitFeeGaugeName = "miner/fees/recommits"
|
||||
|
||||
// blockFinalFeeGauge is the metric tracking the best fee proposed based on the
|
||||
// blockProposedFeeGauge is the metric tracking the best fee proposed based on the
|
||||
// local transaction pool.
|
||||
var blockFinalFeeGauge = metrics.NewRegisteredResettingGauge("miner/fees/proposed", nil)
|
||||
var blockProposedFeeGauge = metrics.NewRegisteredResettingGauge("miner/fees/proposed", nil)
|
||||
|
||||
// blockIncludedFeeGauge is the metric tracking the final fee of the block as
|
||||
// seen on chain. In case of a detected MEV block, it will be the MEV fees, not
|
||||
// the block proposal fees.
|
||||
var blockIncludedFeeGauge = metrics.NewRegisteredResettingGauge("miner/fees/included", nil)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
|
|
@ -163,3 +165,76 @@ func (miner *Miner) getPending() *newPayloadResult {
|
|||
miner.pending.update(header.Hash(), ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
// ReportFeeMetrics injects a few miner metrics based on live blocks matched with
|
||||
// previously locally proposed blocks.
|
||||
func (miner *Miner) ReportFeeMetrics(payload *Payload, block *types.Block) {
|
||||
// Skip everything if something's screwy being sent to us
|
||||
if payload.full == nil {
|
||||
return
|
||||
}
|
||||
// Report all the local recommits into individual metrics
|
||||
for i := len(payload.recommits) - 1; i >= 0; i-- {
|
||||
// Track the generated fees in the metrics
|
||||
gauge := fmt.Sprintf("%s/%d", blockRecommitFeeGaugeName, len(payload.recommits)-i-1)
|
||||
metrics.GetOrRegisterResettingGauge(gauge, nil).Update(new(big.Int).Div(payload.recommits[i], bigGwei).Int64())
|
||||
}
|
||||
// Report the local proposed fees into its own metric
|
||||
blockProposedFeeGauge.Update(new(big.Int).Div(payload.fullFees, bigGwei).Int64())
|
||||
|
||||
// If the local fee recipient is the zero address, we're running in benchmark
|
||||
// mode. In this case we cannot know if a block is or is not MEV. In this case,
|
||||
// case, assume that MEV blocks will do a final tx to the actual fee recipient
|
||||
// from the coinbase and hope it's a goon enough heuristic.
|
||||
if payload.full.Coinbase() == (common.Address{}) {
|
||||
if count := len(block.Transactions()); count > 0 {
|
||||
payout := block.Transactions()[count-1]
|
||||
|
||||
payer, err := types.Sender(types.LatestSignerForChainID(miner.chainConfig.ChainID), payout)
|
||||
if err != nil {
|
||||
log.Error("Failed to retrieve potential MEV sender", "err", err)
|
||||
return
|
||||
}
|
||||
if block.Coinbase() == payer {
|
||||
// The fee recipient made the last transaction, possibly MEV block
|
||||
mevInEther := new(big.Float).Quo(new(big.Float).SetInt(payout.Value()), big.NewFloat(params.Ether))
|
||||
feeInEther := new(big.Float).Quo(new(big.Float).SetInt(payload.fullFees), big.NewFloat(params.Ether))
|
||||
|
||||
log.Info("MEV block detected", "reward", mevInEther, "local", feeInEther)
|
||||
blockIncludedFeeGauge.Update(new(big.Int).Div(payout.Value(), bigGwei).Int64())
|
||||
} else {
|
||||
// Possibly not an MEV block, report the boring mining fees
|
||||
feeInEther := new(big.Float).Quo(new(big.Float).SetInt(payload.fullFees), big.NewFloat(params.Ether))
|
||||
|
||||
log.Info("Plain block detected", "reward", feeInEther)
|
||||
blockIncludedFeeGauge.Update(new(big.Int).Div(payout.Value(), bigGwei).Int64())
|
||||
}
|
||||
}
|
||||
} else if payload.full.Coinbase() != block.Coinbase() {
|
||||
// We are not in benchmarking mode and out proposed fee recipient is
|
||||
// different from the live reward recipient. Definitely an MEV block.
|
||||
// Try to extract the rewards.
|
||||
if count := len(block.Transactions()); count > 0 {
|
||||
payout := block.Transactions()[count-1]
|
||||
if payout.To() != nil && *payout.To() == payload.full.Coinbase() {
|
||||
mevInEther := new(big.Float).Quo(new(big.Float).SetInt(payout.Value()), big.NewFloat(params.Ether))
|
||||
feeInEther := new(big.Float).Quo(new(big.Float).SetInt(payload.fullFees), big.NewFloat(params.Ether))
|
||||
|
||||
log.Info("MEV reward received", "reward", mevInEther, "local", feeInEther)
|
||||
blockIncludedFeeGauge.Update(new(big.Int).Div(payout.Value(), bigGwei).Int64())
|
||||
} else {
|
||||
// Unknown MEV block, report the boring mining fees
|
||||
feeInEther := new(big.Float).Quo(new(big.Float).SetInt(payload.fullFees), big.NewFloat(params.Ether))
|
||||
|
||||
log.Warn("MEV block detected, unknown reward", "reward", "unknown", "local", feeInEther)
|
||||
blockIncludedFeeGauge.Update(new(big.Int).Div(payout.Value(), bigGwei).Int64())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Probably not an MEV block, report the boring mining fees
|
||||
feeInEther := new(big.Float).Quo(new(big.Float).SetInt(payload.fullFees), big.NewFloat(params.Ether))
|
||||
|
||||
log.Info("Plain reward received", "reward", feeInEther)
|
||||
blockIncludedFeeGauge.Update(new(big.Int).Div(payload.fullFees, bigGwei).Int64())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package miner
|
|||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -28,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
|
@ -74,6 +72,8 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
|
|||
// will be set/updated afterwards.
|
||||
type Payload struct {
|
||||
id engine.PayloadID
|
||||
number uint64
|
||||
|
||||
empty *types.Block
|
||||
full *types.Block
|
||||
sidecars []*types.BlobTxSidecar
|
||||
|
|
@ -89,6 +89,7 @@ type Payload struct {
|
|||
func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
|
||||
payload := &Payload{
|
||||
id: id,
|
||||
number: empty.NumberU64(),
|
||||
empty: empty,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
|
|
@ -134,6 +135,12 @@ func (payload *Payload) update(r *newPayloadResult, elapsed time.Duration) {
|
|||
payload.cond.Broadcast() // fire signal for notifying full block
|
||||
}
|
||||
|
||||
// Number retrieves the block number this payload belongs to. This method is used
|
||||
// in stats reporting to allow matching a block arriving later to a past payload.
|
||||
func (payload *Payload) Number() uint64 {
|
||||
return payload.number
|
||||
}
|
||||
|
||||
// Resolve returns the latest built payload and also terminates the background
|
||||
// thread for updating payload. It's safe to be called multiple times.
|
||||
func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope {
|
||||
|
|
@ -183,13 +190,6 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope {
|
|||
default:
|
||||
close(payload.stop)
|
||||
}
|
||||
// Report all the recommits into individual metrics
|
||||
for i := len(payload.recommits) - 1; i >= 0; i-- {
|
||||
// Track the generated fees in the metrics
|
||||
gauge := fmt.Sprintf("%s/%d", blockRecommitFeeGaugeName, len(payload.recommits)-i-1)
|
||||
metrics.GetOrRegisterResettingGauge(gauge, nil).Update(new(big.Int).Div(payload.recommits[i], bigGwei).Int64())
|
||||
}
|
||||
blockFinalFeeGauge.Update(new(big.Int).Div(payload.fullFees, bigGwei).Int64())
|
||||
return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue