Skip to content

Commit

Permalink
feat: greedy merge tx in bid (#2363)
Browse files Browse the repository at this point in the history
  • Loading branch information
irrun authored Apr 11, 2024
1 parent 4bb1bd1 commit b7972bc
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 49 deletions.
34 changes: 8 additions & 26 deletions internal/ethapi/api_mev.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

const (
TransferTxGasLimit = 25000
"github.com/ethereum/go-ethereum/params"
)

// MevAPI implements the interfaces that defined in the BEP-322.
Expand Down Expand Up @@ -65,33 +62,18 @@ func (m *MevAPI) SendBid(ctx context.Context, args types.BidArgs) (common.Hash,
return common.Hash{}, types.NewInvalidBidError("builder fee should not be less than 0")
}

if builderFee.Cmp(common.Big0) == 0 {
if len(args.PayBidTx) != 0 || args.PayBidTxGasUsed != 0 {
return common.Hash{}, types.NewInvalidPayBidTxError("payBidTx should be nil when builder fee is 0")
}
}

if builderFee.Cmp(rawBid.GasFee) >= 0 {
return common.Hash{}, types.NewInvalidBidError("builder fee must be less than gas fee")
}
}

if builderFee.Cmp(common.Big0) > 0 {
// payBidTx can be nil when validator and builder take some other settlement

if args.PayBidTxGasUsed > TransferTxGasLimit {
return common.Hash{}, types.NewInvalidBidError(
fmt.Sprintf("transfer tx gas used must be no more than %v", TransferTxGasLimit))
}
if len(args.PayBidTx) == 0 || args.PayBidTxGasUsed == 0 {
return common.Hash{}, types.NewInvalidPayBidTxError("payBidTx and payBidTxGasUsed are must-have")
}

if (len(args.PayBidTx) == 0 && args.PayBidTxGasUsed != 0) ||
(len(args.PayBidTx) != 0 && args.PayBidTxGasUsed == 0) {
return common.Hash{}, types.NewInvalidPayBidTxError("non-aligned payBidTx and payBidTxGasUsed")
}
}
} else {
if len(args.PayBidTx) != 0 || args.PayBidTxGasUsed != 0 {
return common.Hash{}, types.NewInvalidPayBidTxError("payBidTx should be nil when builder fee is nil")
}
if args.PayBidTxGasUsed > params.PayBidTxGasLimit {
return common.Hash{}, types.NewInvalidBidError(
fmt.Sprintf("transfer tx gas used must be no more than %v", params.PayBidTxGasLimit))
}

return m.b.SendBid(ctx, &args)
Expand Down
93 changes: 75 additions & 18 deletions miner/bid_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"sync/atomic"
"time"

mapset "github.com/deckarep/golang-set/v2"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/bidutil"
"github.com/ethereum/go-ethereum/consensus"
Expand Down Expand Up @@ -61,9 +63,10 @@ var (
}
)

type WorkPreparer interface {
type bidWorker interface {
prepareWork(params *generateParams) (*environment, error)
etherbase() common.Address
fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer, bidTxs mapset.Set[common.Hash]) (err error)
}

// simBidReq is the request for simulating a bid
Expand All @@ -79,7 +82,8 @@ type bidSimulator struct {
delayLeftOver time.Duration
chain *core.BlockChain
chainConfig *params.ChainConfig
workPreparer WorkPreparer
engine consensus.Engine
bidWorker bidWorker

running atomic.Bool // controlled by miner
exitCh chan struct{}
Expand Down Expand Up @@ -112,16 +116,18 @@ type bidSimulator struct {
func newBidSimulator(
config *MevConfig,
delayLeftOver time.Duration,
chainConfig *params.ChainConfig,
chain *core.BlockChain,
workPreparer WorkPreparer,
chainConfig *params.ChainConfig,
engine consensus.Engine,
bidWorker bidWorker,
) *bidSimulator {
b := &bidSimulator{
config: config,
delayLeftOver: delayLeftOver,
chainConfig: chainConfig,
chain: chain,
workPreparer: workPreparer,
chainConfig: chainConfig,
engine: engine,
bidWorker: bidWorker,
exitCh: make(chan struct{}),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
builders: make(map[common.Address]*builderclient.Client),
Expand Down Expand Up @@ -305,13 +311,16 @@ func (b *bidSimulator) newBidLoop() {

// commit aborts in-flight bid execution with given signal and resubmits a new one.
commit := func(reason int32, bidRuntime *BidRuntime) {
log.Debug("BidSimulator: start", "bidHash", bidRuntime.bid.Hash().Hex())

// if the left time is not enough to do simulation, return
var simDuration time.Duration
if lastBid := b.GetBestBid(bidRuntime.bid.ParentHash); lastBid != nil && lastBid.duration != 0 {
simDuration = lastBid.duration
}

if time.Until(b.bidMustBefore(bidRuntime.bid.ParentHash)) <= simDuration*leftOverTimeRate/leftOverTimeScale {
log.Debug("BidSimulator: abort commit, not enough time to simulate", "bidHash", bidRuntime.bid.Hash().Hex())
return
}

Expand Down Expand Up @@ -343,6 +352,7 @@ func (b *bidSimulator) newBidLoop() {

if expectedValidatorReward.Cmp(big.NewInt(0)) < 0 {
// damage self profit, ignore
log.Debug("BidSimulator: invalid bid, validator reward is less than 0, ignore", "bidHash", newBid.Hash().Hex())
continue
}

Expand All @@ -354,8 +364,6 @@ func (b *bidSimulator) newBidLoop() {
packedValidatorReward: big.NewInt(0),
}

// TODO(renee-) opt bid comparation

simulatingBid := b.GetSimulatingBid(newBid.ParentHash)
// simulatingBid is nil means there is no bid in simulation
if simulatingBid == nil {
Expand All @@ -374,6 +382,7 @@ func (b *bidSimulator) newBidLoop() {
continue
}

log.Debug("BidSimulator: lower reward, ignore", "bidHash", newBid.Hash().Hex())
continue
}

Expand All @@ -385,6 +394,7 @@ func (b *bidSimulator) newBidLoop() {
continue
}

log.Debug("BidSimulator: lower reward, ignore", "bidHash", newBid.Hash().Hex())
case <-b.exitCh:
return
}
Expand Down Expand Up @@ -500,8 +510,13 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
blockNumber = bidRuntime.bid.BlockNumber
parentHash = bidRuntime.bid.ParentHash
builder = bidRuntime.bid.Builder
err error
success bool

bidTxs = bidRuntime.bid.Txs
bidTxLen = len(bidTxs)
payBidTx = bidTxs[bidTxLen-1]

err error
success bool
)

// ensure simulation exited then start next simulation
Expand All @@ -526,7 +541,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {

if err != nil {
logCtx = append(logCtx, "err", err)
log.Debug("bid simulation failed", logCtx...)
log.Info("BidSimulator: simulation failed", logCtx...)

go b.reportIssue(bidRuntime, err)
}
Expand All @@ -541,9 +556,9 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {

// prepareWork will configure header with a suitable time according to consensus
// prepareWork will start trie prefetching
if bidRuntime.env, err = b.workPreparer.prepareWork(&generateParams{
if bidRuntime.env, err = b.bidWorker.prepareWork(&generateParams{
parentHash: bidRuntime.bid.ParentHash,
coinbase: b.workPreparer.etherbase(),
coinbase: b.bidWorker.etherbase(),
}); err != nil {
return
}
Expand All @@ -552,6 +567,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
if bidRuntime.env.gasPool == nil {
bidRuntime.env.gasPool = new(core.GasPool).AddGas(gasLimit)
bidRuntime.env.gasPool.SubGas(params.SystemTxsGas)
bidRuntime.env.gasPool.SubGas(params.PayBidTxGasLimit)
}

if bidRuntime.bid.GasUsed > bidRuntime.env.gasPool.Gas() {
Expand All @@ -572,17 +588,16 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
default:
}

// Start executing the transaction
bidRuntime.env.state.SetTxContext(tx.Hash(), bidRuntime.env.tcount)
if bidRuntime.env.tcount == bidTxLen-1 {
break
}

err = bidRuntime.commitTransaction(b.chain, b.chainConfig, tx)
if err != nil {
log.Error("BidSimulator: failed to commit tx", "bidHash", bidRuntime.bid.Hash(), "tx", tx.Hash(), "err", err)
err = fmt.Errorf("invalid tx in bid, %v", err)
return
}

bidRuntime.env.tcount++
}

bidRuntime.packReward(b.config.ValidatorCommission)
Expand All @@ -593,6 +608,38 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
return
}

// fill transactions from mempool
if b.config.GreedyMergeTx {
delay := b.engine.Delay(b.chain, bidRuntime.env.header, &b.delayLeftOver)
if delay != nil && *delay > 0 {
log.Debug("BidSimulator: greedy merge tx stopTimer", "block", bidRuntime.env.header.Number,
"header time", time.Until(time.Unix(int64(bidRuntime.env.header.Time), 0)),
"commit delay", *delay, "DelayLeftOver", b.delayLeftOver)

stopTimer := time.NewTimer(*delay)

bidTxsSet := mapset.NewSet[common.Hash]()
for _, tx := range bidRuntime.bid.Txs {
bidTxsSet.Add(tx.Hash())
}

fillErr := b.bidWorker.fillTransactions(interruptCh, bidRuntime.env, stopTimer, bidTxsSet)
log.Info("BidSimulator: greedy merge tx fill transactions", "block", bidRuntime.env.header.Number,
"tx count", bidRuntime.env.tcount-bidTxLen+1, "err", fillErr)

// recalculate the packed reward
bidRuntime.packReward(b.config.ValidatorCommission)
}
}

bidRuntime.env.gasPool.AddGas(params.PayBidTxGasLimit)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, payBidTx)
if err != nil {
log.Error("BidSimulator: failed to commit tx", "bidHash", bidRuntime.bid.Hash(), "tx", payBidTx.Hash(), "err", err)
err = fmt.Errorf("invalid tx in bid, %v", err)
return
}

bestBid := b.GetBestBid(parentHash)

if bestBid == nil {
Expand All @@ -615,11 +662,16 @@ func (b *bidSimulator) reportIssue(bidRuntime *BidRuntime, err error) {

cli := b.builders[bidRuntime.bid.Builder]
if cli != nil {
cli.ReportIssue(context.Background(), &types.BidIssue{
err = cli.ReportIssue(context.Background(), &types.BidIssue{
Validator: bidRuntime.env.header.Coinbase,
Builder: bidRuntime.bid.Builder,
BidHash: bidRuntime.bid.Hash(),
Message: err.Error(),
})

if err != nil {
log.Error("BidSimulator: failed to report issue", "builder", bidRuntime.bid.Builder, "err", err)
}
}
}

Expand Down Expand Up @@ -658,6 +710,9 @@ func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *para
sc *types.BlobSidecar
)

// Start executing the transaction
r.env.state.SetTxContext(tx.Hash(), r.env.tcount)

if tx.Type() == types.BlobTxType {
sc := types.NewBlobSidecarFromTx(tx)
if sc == nil {
Expand Down Expand Up @@ -692,5 +747,7 @@ func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *para
env.receipts = append(env.receipts, receipt)
}

r.env.tcount++

return nil
}
2 changes: 1 addition & 1 deletion miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, false),
}

miner.bidSimulator = newBidSimulator(&config.Mev, config.DelayLeftOver, chainConfig, eth.BlockChain(), miner.worker)
miner.bidSimulator = newBidSimulator(&config.Mev, config.DelayLeftOver, eth.BlockChain(), chainConfig, engine, miner.worker)
miner.worker.setBestBidFetcher(miner.bidSimulator)

miner.wg.Add(1)
Expand Down
3 changes: 2 additions & 1 deletion miner/miner_mev.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ type BuilderConfig struct {

type MevConfig struct {
Enabled bool // Whether to enable Mev or not
GreedyMergeTx bool // Whether to merge local transactions to the bid
BuilderFeeCeil string // The maximum builder fee of a bid
SentryURL string // The url of Mev sentry
Builders []BuilderConfig // The list of builders
ValidatorCommission uint64 // 100 means 1%
ValidatorCommission uint64 // 100 means the validator claims 1% from block reward
BidSimulationLeftOver time.Duration
}

Expand Down
Loading

0 comments on commit b7972bc

Please sign in to comment.