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 60e32b4 commit 63571ea
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 68 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
39 changes: 17 additions & 22 deletions miner/bid_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"sync/atomic"
"time"

"github.com/emirpasic/gods/sets/hashset"
mapset "github.com/deckarep/golang-set/v2"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/bidutil"
Expand All @@ -36,8 +36,6 @@ const (
leftOverTimeRate = 11
// leftOverTimeScale is the scale of left over time to simulate a bid
leftOverTimeScale = 10

PayBidTxGas uint64 = 21000 // The gas reserved for pay bid tx
)

var (
Expand Down Expand Up @@ -65,10 +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 *hashset.Set) (err error)
fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer, bidTxs mapset.Set[common.Hash], minGas uint64) (err error)
}

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

running atomic.Bool // controlled by miner
exitCh chan struct{}
Expand Down Expand Up @@ -121,15 +119,15 @@ func newBidSimulator(
chain *core.BlockChain,
chainConfig *params.ChainConfig,
engine consensus.Engine,
workPreparer WorkPreparer,
bidWorker bidWorker,
) *bidSimulator {
b := &bidSimulator{
config: config,
delayLeftOver: delayLeftOver,
chain: chain,
chainConfig: chainConfig,
engine: engine,
workPreparer: workPreparer,
bidWorker: bidWorker,
exitCh: make(chan struct{}),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
builders: make(map[common.Address]*builderclient.Client),
Expand Down Expand Up @@ -552,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 Down Expand Up @@ -604,27 +602,24 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
}

// fill transactions from mempool
if b.config.EnlargeBid {
if b.config.GreedyMergeTx {
delay := b.engine.Delay(b.chain, bidRuntime.env.header, &b.delayLeftOver)
if delay != nil || *delay > 0 {
log.Debug("BidSimulator: stopTimer", "block", bidRuntime.env.header.Number,
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(0)
defer stopTimer.Stop()
<-stopTimer.C // discard the initial tick

stopTimer.Reset(*delay)
stopTimer := time.NewTimer(*delay)

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

bidRuntime.env.gasPool.SubGas(PayBidTxGas)

_ = b.workPreparer.fillTransactions(interruptCh, bidRuntime.env, stopTimer, bidTxsSet)
fillErr := b.bidWorker.fillTransactions(interruptCh, bidRuntime.env, stopTimer, bidTxsSet, params.TxGas+params.PayBidTxGasLimit)
if fillErr != nil {
log.Debug("BidSimulator: GreedyMergeTx fillTransactions", "block", bidRuntime.env.header.Number, "err", fillErr)
}

// recalculate the packed reward
bidRuntime.packReward(b.config.ValidatorCommission)
Expand Down
2 changes: 1 addition & 1 deletion miner/miner_mev.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type BuilderConfig struct {

type MevConfig struct {
Enabled bool // Whether to enable Mev or not
EnlargeBid bool // Whether to append local transactions to the bid
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
Expand Down
94 changes: 64 additions & 30 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"sync/atomic"
"time"

"github.com/emirpasic/gods/sets/hashset"
mapset "github.com/deckarep/golang-set/v2"
lru "github.com/hashicorp/golang-lru"
"github.com/holiman/uint256"

Expand Down Expand Up @@ -771,7 +771,7 @@ func (w *worker) applyTransaction(env *environment, tx *types.Transaction, recei
}

func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce,
interruptCh chan int32, stopTimer *time.Timer) error {
interruptCh chan int32, stopTimer *time.Timer, minGas uint64) error {
gasLimit := env.header.GasLimit
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit)
Expand Down Expand Up @@ -817,8 +817,8 @@ LOOP:
}
}
// If we don't have enough gas for any further transactions then we're done.
if env.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
if env.gasPool.Gas() < minGas {
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", minGas)
signal = commitInterruptOutOfGas
break
}
Expand Down Expand Up @@ -1046,7 +1046,8 @@ 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, bidTxs *hashset.Set) (err error) {
func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer,
bidTxs mapset.Set[common.Hash], minGas uint64) (err error) {
w.mu.RLock()
tip := w.tip
w.mu.RUnlock()
Expand Down Expand Up @@ -1083,28 +1084,61 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
}

if bidTxs != nil {
var wg sync.WaitGroup
wg.Add(4)

filterBidTxs := func(commonTxs map[common.Address][]*txpool.LazyTransaction) {
defer wg.Done()
for acc, txs := range commonTxs {
newTxs := make([]*txpool.LazyTransaction, 0)
for _, tx := range txs {
if !bidTxs.Contains(tx.Hash) {
newTxs = append(newTxs, tx)
}
for acc, txs := range localPlainTxs {
for i := len(txs) - 1; i >= 0; i-- {
if bidTxs.Contains(txs[i].Hash) {
txs = txs[i+1:]
break
}
commonTxs[acc] = newTxs
}
if len(txs) == 0 {
delete(localPlainTxs, acc)
} else {
localPlainTxs[acc] = txs
}
}

go filterBidTxs(localPlainTxs)
go filterBidTxs(localBlobTxs)
go filterBidTxs(remotePlainTxs)
go filterBidTxs(remoteBlobTxs)
for acc, txs := range localBlobTxs {
for i := len(txs) - 1; i >= 0; i-- {
if bidTxs.Contains(txs[i].Hash) {
txs = txs[i+1:]
break
}
}
if len(txs) == 0 {
delete(localBlobTxs, acc)
} else {
localBlobTxs[acc] = txs
}
}

wg.Wait()
for acc, txs := range remotePlainTxs {
for i := len(txs) - 1; i >= 0; i-- {
if bidTxs.Contains(txs[i].Hash) {
txs = txs[i+1:]
break
}
}
if len(txs) == 0 {
delete(remotePlainTxs, acc)
} else {
remotePlainTxs[acc] = txs
}
}

for acc, txs := range remoteBlobTxs {
for i := len(txs) - 1; i >= 0; i-- {
if bidTxs.Contains(txs[i].Hash) {
txs = txs[i+1:]
break
}
}
if len(txs) == 0 {
delete(remoteBlobTxs, acc)
} else {
remoteBlobTxs[acc] = txs
}
}
}

// Fill the block with all available pending transactions.
Expand All @@ -1118,15 +1152,15 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee)
blobTxs := newTransactionsByPriceAndNonce(env.signer, localBlobTxs, env.header.BaseFee)

if err := w.commitTransactions(env, plainTxs, blobTxs, interruptCh, stopTimer); err != nil {
if err := w.commitTransactions(env, plainTxs, blobTxs, interruptCh, stopTimer, minGas); err != nil {
return err
}
}
if len(remotePlainTxs) > 0 || len(remoteBlobTxs) > 0 {
plainTxs := newTransactionsByPriceAndNonce(env.signer, remotePlainTxs, env.header.BaseFee)
blobTxs := newTransactionsByPriceAndNonce(env.signer, remoteBlobTxs, env.header.BaseFee)

if err := w.commitTransactions(env, plainTxs, blobTxs, interruptCh, stopTimer); err != nil {
if err := w.commitTransactions(env, plainTxs, blobTxs, interruptCh, stopTimer, minGas); err != nil {
return err
}
}
Expand All @@ -1135,21 +1169,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, nil)
if !geParams.noTxs {
err := w.fillTransactions(nil, work, nil, nil, params.TxGas)
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 @@ -1243,7 +1277,7 @@ LOOP:

// Fill pending transactions from the txpool into the block.
fillStart := time.Now()
err = w.fillTransactions(interruptCh, work, stopTimer, nil)
err = w.fillTransactions(interruptCh, work, stopTimer, nil, params.TxGas)
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 63571ea

Please sign in to comment.