Skip to content

Commit

Permalink
feat: greedy merge tx in bid
Browse files Browse the repository at this point in the history
  • Loading branch information
irrun committed Apr 8, 2024
1 parent 8f43835 commit 0575b74
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 40 deletions.
22 changes: 7 additions & 15 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 @@ -68,20 +65,15 @@ func (m *MevAPI) SendBid(ctx context.Context, args types.BidArgs) (common.Hash,
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")
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
79 changes: 63 additions & 16 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 @@ -354,8 +360,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 Down Expand Up @@ -500,8 +504,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
bidLen = len(bidTxs)
payBidTx = bidTxs[bidLen-1]

err error
success bool
)

// ensure simulation exited then start next simulation
Expand Down Expand Up @@ -541,9 +550,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 +561,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 +582,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 == bidLen-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 +602,39 @@ 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: GreedyMergeTx 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)
if fillErr != nil {
log.Debug("BidSimulator: GreedyMergeTx fillTransactions", "block", bidRuntime.env.header.Number, "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 Down Expand Up @@ -663,6 +705,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 @@ -697,5 +742,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
39 changes: 32 additions & 7 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"sync/atomic"
"time"

mapset "github.com/deckarep/golang-set/v2"
lru "github.com/hashicorp/golang-lru"
"github.com/holiman/uint256"

Expand Down Expand Up @@ -1045,7 +1046,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer) (err error) {
func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer, bidTxs mapset.Set[common.Hash]) (err error) {
w.mu.RLock()
tip := w.tip
w.mu.RUnlock()
Expand Down Expand Up @@ -1080,6 +1081,30 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
localBlobTxs[account] = txs
}
}

if bidTxs != nil {
filterBidTxs := func(commonTxs map[common.Address][]*txpool.LazyTransaction) {
for acc, txs := range commonTxs {
for i := len(txs) - 1; i >= 0; i-- {
if bidTxs.Contains(txs[i].Hash) {
txs = txs[i+1:]
break
}
}
if len(txs) == 0 {
delete(commonTxs, acc)
} else {
commonTxs[acc] = txs
}
}
}

filterBidTxs(localPlainTxs)
filterBidTxs(localBlobTxs)
filterBidTxs(remotePlainTxs)
filterBidTxs(remoteBlobTxs)
}

// Fill the block with all available pending transactions.
// we will abort when:
// 1.new block was imported
Expand Down Expand Up @@ -1108,21 +1133,21 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
}

// generateWork generates a sealing block based on the given parameters.
func (w *worker) generateWork(params *generateParams) *newPayloadResult {
work, err := w.prepareWork(params)
func (w *worker) generateWork(geParams *generateParams) *newPayloadResult {
work, err := w.prepareWork(geParams)
if err != nil {
return &newPayloadResult{err: err}
}
defer work.discard()

if !params.noTxs {
err := w.fillTransactions(nil, work, nil)
if !geParams.noTxs {
err := w.fillTransactions(nil, work, nil, nil)
if errors.Is(err, errBlockInterruptedByTimeout) {
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout))
}
}
fees := work.state.GetBalance(consensus.SystemAddress)
block, _, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, params.withdrawals)
block, _, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, geParams.withdrawals)
if err != nil {
return &newPayloadResult{err: err}
}
Expand Down Expand Up @@ -1216,7 +1241,7 @@ LOOP:

// Fill pending transactions from the txpool into the block.
fillStart := time.Now()
err = w.fillTransactions(interruptCh, work, stopTimer)
err = w.fillTransactions(interruptCh, work, stopTimer, nil)
fillDuration := time.Since(fillStart)
switch {
case errors.Is(err, errBlockInterruptedByNewHead):
Expand Down
1 change: 1 addition & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
MinGasLimit uint64 = 5000 // Minimum the gas limit may ever be.
MaxGasLimit uint64 = 0x7fffffffffffffff // Maximum the gas limit (2^63-1).
GenesisGasLimit uint64 = 4712388 // Gas limit of the Genesis block.
PayBidTxGasLimit uint64 = 25000 // Gas limit of the PayBidTx in the types.BidArgs.

MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis.
ForkIDSize uint64 = 4 // The length of fork id
Expand Down

0 comments on commit 0575b74

Please sign in to comment.