Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: greedy merge tx in bid #2363

Merged
merged 9 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
86 changes: 69 additions & 17 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
bidTxLen = len(bidTxs)
payBidTx = bidTxs[bidTxLen-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 == 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 +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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to eliminate this for loop.
We can pass bidRuntime.bid.Txs into fillTransactions(..., bidRuntime.bid.Txs) directly.

// in fillTransactions(...)
for _, bigTx range bidRuntime.bid.Txs {
   // check and remove from pendingPlainTxs & pendingBlobTxs
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hashset makes it simple

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 All @@ -615,11 +657,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 +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 @@ -692,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
30 changes: 27 additions & 3 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,29 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
localBlobTxs[account] = txs
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about move it to line#1068, so only need to filter: pendingPlainTxs & pendingBlobTxs, could be more efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok,fixed

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) {
if i == len(txs)-1 {
delete(commonTxs, acc)
} else {
commonTxs[acc] = txs[i+1:]
}
break
}
}
}
}

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 @@ -1116,7 +1140,7 @@ func (w *worker) generateWork(params *generateParams) *newPayloadResult {
defer work.discard()

if !params.noTxs {
err := w.fillTransactions(nil, work, nil)
err := w.fillTransactions(nil, work, nil, nil)
if errors.Is(err, errBlockInterruptedByTimeout) {
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout))
}
Expand Down Expand Up @@ -1216,7 +1240,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
Loading