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 all 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
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 {
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)
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
Loading