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
|
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()
|
hash := block.Hash()
|
||||||
return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
|
return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,20 @@ func (q *payloadQueue) has(id engine.PayloadID) bool {
|
||||||
return false
|
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
|
// headerQueueItem represents an hash->header tuple to store until it's retrieved
|
||||||
// or evicted.
|
// or evicted.
|
||||||
type headerQueueItem struct {
|
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).
|
// the block fees generated by each recommit round (relative to the latest).
|
||||||
var blockRecommitFeeGaugeName = "miner/fees/recommits"
|
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.
|
// 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/state"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"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/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -163,3 +165,76 @@ func (miner *Miner) getPending() *newPayloadResult {
|
||||||
miner.pending.update(header.Hash(), ret)
|
miner.pending.update(header.Hash(), ret)
|
||||||
return 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 (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -28,7 +27,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
@ -73,7 +71,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
|
||||||
// the revenue. Therefore, the empty-block here is always available and full-block
|
// the revenue. Therefore, the empty-block here is always available and full-block
|
||||||
// will be set/updated afterwards.
|
// will be set/updated afterwards.
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
id engine.PayloadID
|
id engine.PayloadID
|
||||||
|
number uint64
|
||||||
|
|
||||||
empty *types.Block
|
empty *types.Block
|
||||||
full *types.Block
|
full *types.Block
|
||||||
sidecars []*types.BlobTxSidecar
|
sidecars []*types.BlobTxSidecar
|
||||||
|
|
@ -88,9 +88,10 @@ type Payload struct {
|
||||||
// newPayload initializes the payload object.
|
// newPayload initializes the payload object.
|
||||||
func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
|
func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
|
||||||
payload := &Payload{
|
payload := &Payload{
|
||||||
id: id,
|
id: id,
|
||||||
empty: empty,
|
number: empty.NumberU64(),
|
||||||
stop: make(chan struct{}),
|
empty: empty,
|
||||||
|
stop: make(chan struct{}),
|
||||||
}
|
}
|
||||||
log.Info("Starting work on payload", "id", payload.id)
|
log.Info("Starting work on payload", "id", payload.id)
|
||||||
payload.cond = sync.NewCond(&payload.lock)
|
payload.cond = sync.NewCond(&payload.lock)
|
||||||
|
|
@ -134,6 +135,12 @@ func (payload *Payload) update(r *newPayloadResult, elapsed time.Duration) {
|
||||||
payload.cond.Broadcast() // fire signal for notifying full block
|
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
|
// Resolve returns the latest built payload and also terminates the background
|
||||||
// thread for updating payload. It's safe to be called multiple times.
|
// thread for updating payload. It's safe to be called multiple times.
|
||||||
func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope {
|
func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope {
|
||||||
|
|
@ -183,13 +190,6 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope {
|
||||||
default:
|
default:
|
||||||
close(payload.stop)
|
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)
|
return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue