bsc/miner/bid_simulator.go

878 lines
24 KiB
Go
Raw Normal View History

2024-03-08 06:15:35 +03:00
package miner
import (
"context"
"errors"
"fmt"
"math/big"
"net"
"net/http"
"strconv"
2024-03-08 06:15:35 +03:00
"sync"
"sync/atomic"
"time"
2024-04-11 10:15:46 +03:00
mapset "github.com/deckarep/golang-set/v2"
2024-03-08 06:15:35 +03:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/bidutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
2024-03-08 06:15:35 +03:00
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
2024-03-21 10:03:35 +03:00
"github.com/ethereum/go-ethereum/metrics"
2024-03-08 06:15:35 +03:00
"github.com/ethereum/go-ethereum/miner/builderclient"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
const (
// maxBidPerBuilderPerBlock is the max bid number per builder
maxBidPerBuilderPerBlock = 3
)
var (
2024-03-21 10:03:35 +03:00
bidSimTimer = metrics.NewRegisteredTimer("bid/sim/duration", nil)
2024-03-08 06:15:35 +03:00
)
var (
2024-03-21 10:03:35 +03:00
diffInTurn = big.NewInt(2) // the difficulty of a block that proposed by an in-turn validator
2024-03-08 06:15:35 +03:00
dialer = &net.Dialer{
Timeout: time.Second,
KeepAlive: 60 * time.Second,
}
transport = &http.Transport{
DialContext: dialer.DialContext,
MaxIdleConnsPerHost: 50,
MaxConnsPerHost: 50,
IdleConnTimeout: 90 * time.Second,
}
client = &http.Client{
Timeout: 5 * time.Second,
Transport: transport,
}
)
2024-04-11 10:15:46 +03:00
type bidWorker interface {
2024-03-08 06:15:35 +03:00
prepareWork(params *generateParams) (*environment, error)
etherbase() common.Address
2024-04-11 10:15:46 +03:00
fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer, bidTxs mapset.Set[common.Hash]) (err error)
2024-03-08 06:15:35 +03:00
}
// simBidReq is the request for simulating a bid
type simBidReq struct {
bid *BidRuntime
interruptCh chan int32
}
// newBidPackage is the warp of a new bid and a feedback channel
type newBidPackage struct {
bid *types.Bid
feedback chan error
}
2024-03-08 06:15:35 +03:00
// bidSimulator is in charge of receiving bid from builders, reporting issue to builders.
// And take care of bid simulation, rewards computing, best bid maintaining.
type bidSimulator struct {
config *MevConfig
delayLeftOver time.Duration
minGasPrice *big.Int
2024-03-08 06:15:35 +03:00
chain *core.BlockChain
txpool *txpool.TxPool
2024-03-08 06:15:35 +03:00
chainConfig *params.ChainConfig
2024-04-11 10:15:46 +03:00
engine consensus.Engine
bidWorker bidWorker
2024-03-08 06:15:35 +03:00
running atomic.Bool // controlled by miner
exitCh chan struct{}
bidReceiving atomic.Bool // controlled by config and eth.AdminAPI
chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription
sentryCli *builderclient.Client
// builder info (warning: only keep status in memory!)
buildersMu sync.RWMutex
builders map[common.Address]*builderclient.Client
// channels
simBidCh chan *simBidReq
newBidCh chan newBidPackage
2024-03-08 06:15:35 +03:00
pendingMu sync.RWMutex
pending map[uint64]map[common.Address]map[common.Hash]struct{} // blockNumber -> builder -> bidHash -> struct{}
bestBidMu sync.RWMutex
bestBid map[common.Hash]*BidRuntime // prevBlockHash -> bidRuntime
simBidMu sync.RWMutex
simulatingBid map[common.Hash]*BidRuntime // prevBlockHash -> bidRuntime, in the process of simulation
}
func newBidSimulator(
config *MevConfig,
delayLeftOver time.Duration,
minGasPrice *big.Int,
eth Backend,
2024-04-11 10:15:46 +03:00
chainConfig *params.ChainConfig,
engine consensus.Engine,
bidWorker bidWorker,
2024-03-08 06:15:35 +03:00
) *bidSimulator {
b := &bidSimulator{
config: config,
delayLeftOver: delayLeftOver,
minGasPrice: minGasPrice,
chain: eth.BlockChain(),
txpool: eth.TxPool(),
2024-04-11 10:15:46 +03:00
chainConfig: chainConfig,
engine: engine,
bidWorker: bidWorker,
2024-03-08 06:15:35 +03:00
exitCh: make(chan struct{}),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
builders: make(map[common.Address]*builderclient.Client),
simBidCh: make(chan *simBidReq),
newBidCh: make(chan newBidPackage, 100),
2024-03-08 06:15:35 +03:00
pending: make(map[uint64]map[common.Address]map[common.Hash]struct{}),
bestBid: make(map[common.Hash]*BidRuntime),
simulatingBid: make(map[common.Hash]*BidRuntime),
}
b.chainHeadSub = b.chain.SubscribeChainHeadEvent(b.chainHeadCh)
2024-03-08 06:15:35 +03:00
if config.Enabled {
b.bidReceiving.Store(true)
b.dialSentryAndBuilders()
if len(b.builders) == 0 {
log.Warn("BidSimulator: no valid builders")
}
}
go b.clearLoop()
go b.mainLoop()
go b.newBidLoop()
return b
}
func (b *bidSimulator) dialSentryAndBuilders() {
var sentryCli *builderclient.Client
var err error
if b.config.SentryURL != "" {
sentryCli, err = builderclient.DialOptions(context.Background(), b.config.SentryURL, rpc.WithHTTPClient(client))
if err != nil {
log.Error("BidSimulator: failed to dial sentry", "url", b.config.SentryURL, "err", err)
}
}
b.sentryCli = sentryCli
for _, v := range b.config.Builders {
_ = b.AddBuilder(v.Address, v.URL)
}
}
func (b *bidSimulator) start() {
b.running.Store(true)
}
func (b *bidSimulator) stop() {
b.running.Store(false)
}
func (b *bidSimulator) close() {
b.running.Store(false)
close(b.exitCh)
}
func (b *bidSimulator) isRunning() bool {
return b.running.Load()
}
func (b *bidSimulator) receivingBid() bool {
return b.bidReceiving.Load()
}
func (b *bidSimulator) startReceivingBid() {
b.dialSentryAndBuilders()
b.bidReceiving.Store(true)
}
func (b *bidSimulator) stopReceivingBid() {
b.bidReceiving.Store(false)
}
func (b *bidSimulator) AddBuilder(builder common.Address, url string) error {
b.buildersMu.Lock()
defer b.buildersMu.Unlock()
if b.sentryCli != nil {
b.builders[builder] = b.sentryCli
} else {
var builderCli *builderclient.Client
if url != "" {
var err error
builderCli, err = builderclient.DialOptions(context.Background(), url, rpc.WithHTTPClient(client))
if err != nil {
log.Error("BidSimulator: failed to dial builder", "url", url, "err", err)
return err
}
}
b.builders[builder] = builderCli
}
return nil
}
func (b *bidSimulator) RemoveBuilder(builder common.Address) error {
b.buildersMu.Lock()
defer b.buildersMu.Unlock()
delete(b.builders, builder)
return nil
}
func (b *bidSimulator) ExistBuilder(builder common.Address) bool {
b.buildersMu.RLock()
defer b.buildersMu.RUnlock()
_, ok := b.builders[builder]
return ok
}
func (b *bidSimulator) SetBestBid(prevBlockHash common.Hash, bid *BidRuntime) {
b.bestBidMu.Lock()
defer b.bestBidMu.Unlock()
// must discard the environment of the last best bid, otherwise it will cause memory leak
last := b.bestBid[prevBlockHash]
if last != nil && last.env != nil {
last.env.discard()
}
2024-03-08 06:15:35 +03:00
b.bestBid[prevBlockHash] = bid
}
func (b *bidSimulator) GetBestBid(prevBlockHash common.Hash) *BidRuntime {
b.bestBidMu.RLock()
defer b.bestBidMu.RUnlock()
return b.bestBid[prevBlockHash]
}
func (b *bidSimulator) SetSimulatingBid(prevBlockHash common.Hash, bid *BidRuntime) {
b.simBidMu.Lock()
defer b.simBidMu.Unlock()
b.simulatingBid[prevBlockHash] = bid
}
func (b *bidSimulator) GetSimulatingBid(prevBlockHash common.Hash) *BidRuntime {
b.simBidMu.RLock()
defer b.simBidMu.RUnlock()
return b.simulatingBid[prevBlockHash]
}
func (b *bidSimulator) RemoveSimulatingBid(prevBlockHash common.Hash) {
b.simBidMu.Lock()
defer b.simBidMu.Unlock()
delete(b.simulatingBid, prevBlockHash)
}
func (b *bidSimulator) mainLoop() {
defer b.chainHeadSub.Unsubscribe()
for {
select {
case req := <-b.simBidCh:
if !b.isRunning() {
continue
}
b.simBid(req.interruptCh, req.bid)
// System stopped
case <-b.exitCh:
return
case <-b.chainHeadSub.Err():
return
}
}
}
func (b *bidSimulator) newBidLoop() {
var (
interruptCh chan int32
)
// commit aborts in-flight bid execution with given signal and resubmits a new one.
commit := func(reason int32, bidRuntime *BidRuntime) {
if interruptCh != nil {
// each commit work will have its own interruptCh to stop work with a reason
interruptCh <- reason
close(interruptCh)
}
interruptCh = make(chan int32, 1)
select {
case b.simBidCh <- &simBidReq{interruptCh: interruptCh, bid: bidRuntime}:
log.Debug("BidSimulator: commit", "builder", bidRuntime.bid.Builder, "bidHash", bidRuntime.bid.Hash().Hex())
2024-03-08 06:15:35 +03:00
case <-b.exitCh:
return
}
}
genDiscardedReply := func(betterBid *BidRuntime) error {
return fmt.Errorf("bid is discarded, current bestBid is [blockReward: %s, validatorReward: %s]", betterBid.expectedBlockReward, betterBid.expectedValidatorReward)
}
2024-03-08 06:15:35 +03:00
for {
select {
case newBid := <-b.newBidCh:
if !b.isRunning() {
continue
}
bidRuntime, err := newBidRuntime(newBid.bid, b.config.ValidatorCommission)
if err != nil {
if newBid.feedback != nil {
newBid.feedback <- err
}
2024-03-08 06:15:35 +03:00
continue
}
var replyErr error
// simulatingBid will be nil if there is no bid in simulation, compare with the bestBid instead
if simulatingBid := b.GetSimulatingBid(newBid.bid.ParentHash); simulatingBid != nil {
// simulatingBid always better than bestBid, so only compare with simulatingBid if a simulatingBid exists
if bidRuntime.isExpectedBetterThan(simulatingBid) {
2024-03-08 06:15:35 +03:00
commit(commitInterruptBetterBid, bidRuntime)
} else {
replyErr = genDiscardedReply(simulatingBid)
2024-03-08 06:15:35 +03:00
}
} else {
// bestBid is nil means the bid is the first bid, otherwise the bid should compare with the bestBid
if bestBid := b.GetBestBid(newBid.bid.ParentHash); bestBid == nil ||
bidRuntime.isExpectedBetterThan(bestBid) {
2024-03-08 06:15:35 +03:00
commit(commitInterruptBetterBid, bidRuntime)
} else {
replyErr = genDiscardedReply(bestBid)
2024-03-08 06:15:35 +03:00
}
}
if newBid.feedback != nil {
newBid.feedback <- replyErr
log.Info("[BID ARRIVED]",
"block", newBid.bid.BlockNumber,
"builder", newBid.bid.Builder,
"accepted", replyErr == nil,
"blockReward", weiToEtherStringF6(bidRuntime.expectedBlockReward),
"validatorReward", weiToEtherStringF6(bidRuntime.expectedValidatorReward),
"tx", len(newBid.bid.Txs),
"hash", newBid.bid.Hash().TerminalString(),
)
2024-03-08 06:15:35 +03:00
}
case <-b.exitCh:
return
}
}
}
func (b *bidSimulator) bidBetterBefore(parentHash common.Hash) time.Time {
parentHeader := b.chain.GetHeaderByHash(parentHash)
return bidutil.BidBetterBefore(parentHeader, b.chainConfig.Parlia.Period, b.delayLeftOver, b.config.BidSimulationLeftOver)
}
func (b *bidSimulator) clearLoop() {
clearFn := func(parentHash common.Hash, blockNumber uint64) {
b.pendingMu.Lock()
delete(b.pending, blockNumber)
b.pendingMu.Unlock()
b.bestBidMu.Lock()
if bid, ok := b.bestBid[parentHash]; ok {
bid.env.discard()
}
delete(b.bestBid, parentHash)
for k, v := range b.bestBid {
if v.bid.BlockNumber <= blockNumber-b.chain.TriesInMemory() {
2024-03-08 06:15:35 +03:00
v.env.discard()
delete(b.bestBid, k)
}
}
b.bestBidMu.Unlock()
b.simBidMu.Lock()
for k, v := range b.simulatingBid {
if v.bid.BlockNumber <= blockNumber-b.chain.TriesInMemory() {
2024-03-08 06:15:35 +03:00
v.env.discard()
delete(b.simulatingBid, k)
}
}
b.simBidMu.Unlock()
}
for head := range b.chainHeadCh {
if !b.isRunning() {
continue
}
clearFn(head.Block.ParentHash(), head.Block.NumberU64())
}
}
// sendBid checks if the bid is already exists or if the builder sends too many bids,
// if yes, return error, if not, add bid into newBid chan waiting for judge profit.
func (b *bidSimulator) sendBid(_ context.Context, bid *types.Bid) error {
timer := time.NewTimer(1 * time.Second)
defer timer.Stop()
replyCh := make(chan error, 1)
2024-03-08 06:15:35 +03:00
select {
case b.newBidCh <- newBidPackage{bid: bid, feedback: replyCh}:
2024-03-08 06:15:35 +03:00
b.AddPending(bid.BlockNumber, bid.Builder, bid.Hash())
case <-timer.C:
return types.ErrMevBusy
}
select {
case reply := <-replyCh:
return reply
2024-03-08 06:15:35 +03:00
case <-timer.C:
return types.ErrMevBusy
}
}
func (b *bidSimulator) CheckPending(blockNumber uint64, builder common.Address, bidHash common.Hash) error {
b.pendingMu.Lock()
defer b.pendingMu.Unlock()
// check if bid exists or if builder sends too many bids
if _, ok := b.pending[blockNumber]; !ok {
b.pending[blockNumber] = make(map[common.Address]map[common.Hash]struct{})
}
if _, ok := b.pending[blockNumber][builder]; !ok {
b.pending[blockNumber][builder] = make(map[common.Hash]struct{})
}
if _, ok := b.pending[blockNumber][builder][bidHash]; ok {
return errors.New("bid already exists")
}
if len(b.pending[blockNumber][builder]) >= maxBidPerBuilderPerBlock {
return errors.New("too many bids")
}
return nil
}
func (b *bidSimulator) AddPending(blockNumber uint64, builder common.Address, bidHash common.Hash) {
b.pendingMu.Lock()
defer b.pendingMu.Unlock()
b.pending[blockNumber][builder][bidHash] = struct{}{}
}
// simBid simulates a newBid with txs.
// simBid does not enable state prefetching when commit transaction.
func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
// prevent from stopping happen in time interval from sendBid to simBid
if !b.isRunning() || !b.receivingBid() {
return
}
var (
startTS = time.Now()
2024-03-08 06:15:35 +03:00
blockNumber = bidRuntime.bid.BlockNumber
parentHash = bidRuntime.bid.ParentHash
builder = bidRuntime.bid.Builder
2024-04-11 10:15:46 +03:00
bidTxs = bidRuntime.bid.Txs
bidTxLen = len(bidTxs)
payBidTx = bidTxs[bidTxLen-1]
err error
success bool
2024-03-08 06:15:35 +03:00
)
// ensure simulation exited then start next simulation
b.SetSimulatingBid(parentHash, bidRuntime)
defer func(simStart time.Time) {
logCtx := []any{
"blockNumber", blockNumber,
"parentHash", parentHash,
"builder", builder,
"gasUsed", bidRuntime.bid.GasUsed,
}
if bidRuntime.env != nil {
logCtx = append(logCtx, "gasLimit", bidRuntime.env.header.GasLimit)
if err != nil || !success {
bidRuntime.env.discard()
}
}
if err != nil {
logCtx = append(logCtx, "err", err)
2024-04-11 10:15:46 +03:00
log.Info("BidSimulator: simulation failed", logCtx...)
2024-03-08 06:15:35 +03:00
go b.reportIssue(bidRuntime, err)
}
b.RemoveSimulatingBid(parentHash)
close(bidRuntime.finished)
2024-03-08 06:15:35 +03:00
if success {
bidRuntime.duration = time.Since(simStart)
bidSimTimer.UpdateSince(simStart)
2024-03-08 06:15:35 +03:00
// only recommit self bid when newBidCh is empty
if len(b.newBidCh) > 0 {
return
}
select {
case b.newBidCh <- newBidPackage{bid: bidRuntime.bid}:
log.Debug("BidSimulator: recommit", "builder", bidRuntime.bid.Builder, "bidHash", bidRuntime.bid.Hash().Hex())
default:
}
}
}(startTS)
2024-03-08 06:15:35 +03:00
// prepareWork will configure header with a suitable time according to consensus
// prepareWork will start trie prefetching
2024-04-11 10:15:46 +03:00
if bidRuntime.env, err = b.bidWorker.prepareWork(&generateParams{
2024-03-08 06:15:35 +03:00
parentHash: bidRuntime.bid.ParentHash,
2024-04-11 10:15:46 +03:00
coinbase: b.bidWorker.etherbase(),
2024-03-08 06:15:35 +03:00
}); err != nil {
return
}
// if the left time is not enough to do simulation, return
delay := b.engine.Delay(b.chain, bidRuntime.env.header, &b.delayLeftOver)
if delay == nil || *delay <= 0 {
log.Info("BidSimulator: abort commit, not enough time to simulate",
"builder", bidRuntime.bid.Builder, "bidHash", bidRuntime.bid.Hash().Hex())
return
}
2024-03-08 06:15:35 +03:00
gasLimit := bidRuntime.env.header.GasLimit
if bidRuntime.env.gasPool == nil {
bidRuntime.env.gasPool = new(core.GasPool).AddGas(gasLimit)
bidRuntime.env.gasPool.SubGas(params.SystemTxsGas)
2024-04-11 10:15:46 +03:00
bidRuntime.env.gasPool.SubGas(params.PayBidTxGasLimit)
2024-03-08 06:15:35 +03:00
}
if bidRuntime.bid.GasUsed > bidRuntime.env.gasPool.Gas() {
err = errors.New("gas used exceeds gas limit")
return
}
// commit transactions in bid
2024-03-08 06:15:35 +03:00
for _, tx := range bidRuntime.bid.Txs {
select {
case <-interruptCh:
err = errors.New("simulation abort due to better bid arrived")
return
case <-b.exitCh:
err = errors.New("miner exit")
return
default:
}
2024-04-11 10:15:46 +03:00
if bidRuntime.env.tcount == bidTxLen-1 {
break
}
2024-03-08 06:15:35 +03:00
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, tx, bidRuntime.bid.UnRevertible.Contains(tx.Hash()))
2024-03-08 06:15:35 +03:00
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
}
}
// check if bid reward is valid
{
bidRuntime.packReward(b.config.ValidatorCommission)
if !bidRuntime.validReward() {
err = errors.New("reward does not achieve the expectation")
return
}
}
2024-03-08 06:15:35 +03:00
// check if bid gas price is lower than min gas price
{
bidGasUsed := uint64(0)
bidGasFee := big.NewInt(0)
for i, receipt := range bidRuntime.env.receipts {
tx := bidRuntime.env.txs[i]
if !b.txpool.Has(tx.Hash()) {
bidGasUsed += receipt.GasUsed
effectiveTip, er := tx.EffectiveGasTip(bidRuntime.env.header.BaseFee)
if er != nil {
err = errors.New("failed to calculate effective tip")
return
}
if bidRuntime.env.header.BaseFee != nil {
effectiveTip.Add(effectiveTip, bidRuntime.env.header.BaseFee)
}
gasFee := new(big.Int).Mul(effectiveTip, new(big.Int).SetUint64(receipt.GasUsed))
bidGasFee.Add(bidGasFee, gasFee)
if tx.Type() == types.BlobTxType {
blobFee := new(big.Int).Mul(receipt.BlobGasPrice, new(big.Int).SetUint64(receipt.BlobGasUsed))
bidGasFee.Add(bidGasFee, blobFee)
}
}
}
// if bid txs are all from mempool, do not check gas price
if bidGasUsed != 0 {
bidGasPrice := new(big.Int).Div(bidGasFee, new(big.Int).SetUint64(bidGasUsed))
if bidGasPrice.Cmp(b.minGasPrice) < 0 {
err = fmt.Errorf("bid gas price is lower than min gas price, bid:%v, min:%v", bidGasPrice, b.minGasPrice)
return
}
}
2024-03-08 06:15:35 +03:00
}
// if enable greedy merge, fill bid env with transactions from mempool
2024-04-11 10:15:46 +03:00
if b.config.GreedyMergeTx {
delay := b.engine.Delay(b.chain, bidRuntime.env.header, &b.delayLeftOver)
if delay != nil && *delay > 0 {
bidTxsSet := mapset.NewThreadUnsafeSetWithSize[common.Hash](len(bidRuntime.bid.Txs))
2024-04-11 10:15:46 +03:00
for _, tx := range bidRuntime.bid.Txs {
bidTxsSet.Add(tx.Hash())
}
fillErr := b.bidWorker.fillTransactions(interruptCh, bidRuntime.env, nil, bidTxsSet)
log.Trace("BidSimulator: greedy merge stopped", "block", bidRuntime.env.header.Number,
"builder", bidRuntime.bid.Builder, "tx count", bidRuntime.env.tcount-bidTxLen+1, "err", fillErr)
2024-04-11 10:15:46 +03:00
// recalculate the packed reward
bidRuntime.packReward(b.config.ValidatorCommission)
}
}
// commit payBidTx at the end of the block
2024-04-11 10:15:46 +03:00
bidRuntime.env.gasPool.AddGas(params.PayBidTxGasLimit)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, payBidTx, true)
2024-04-11 10:15:46 +03:00
if err != nil {
log.Error("BidSimulator: failed to commit tx", "builder", bidRuntime.bid.Builder,
"bidHash", bidRuntime.bid.Hash(), "tx", payBidTx.Hash(), "err", err)
2024-04-11 10:15:46 +03:00
err = fmt.Errorf("invalid tx in bid, %v", err)
return
}
// check bid size
if bidRuntime.env.size+blockReserveSize > params.MaxMessageSize {
log.Error("BidSimulator: failed to check bid size", "builder", bidRuntime.bid.Builder,
"bidHash", bidRuntime.bid.Hash(), "env.size", bidRuntime.env.size)
err = errors.New("invalid bid size")
return
}
2024-03-08 06:15:35 +03:00
bestBid := b.GetBestBid(parentHash)
if bestBid == nil {
log.Info("[BID RESULT]", "win", "true[first]", "builder", bidRuntime.bid.Builder, "hash", bidRuntime.bid.Hash().TerminalString())
2024-03-08 06:15:35 +03:00
b.SetBestBid(bidRuntime.bid.ParentHash, bidRuntime)
success = true
return
}
if bidRuntime.bid.Hash() != bestBid.bid.Hash() {
log.Info("[BID RESULT]",
"win", bidRuntime.packedBlockReward.Cmp(bestBid.packedBlockReward) >= 0,
"bidHash", bidRuntime.bid.Hash().TerminalString(),
"bestHash", bestBid.bid.Hash().TerminalString(),
"bidGasFee", weiToEtherStringF6(bidRuntime.packedBlockReward),
"bestGasFee", weiToEtherStringF6(bestBid.packedBlockReward),
"bidBlockTx", bidRuntime.env.tcount,
"bestBlockTx", bestBid.env.tcount,
"simElapsed", time.Since(startTS),
)
}
2024-03-08 06:15:35 +03:00
// this is the simplest strategy: best for all the delegators.
if bidRuntime.packedBlockReward.Cmp(bestBid.packedBlockReward) >= 0 {
2024-03-08 06:15:35 +03:00
b.SetBestBid(bidRuntime.bid.ParentHash, bidRuntime)
success = true
return
}
// only recommit last best bid when newBidCh is empty
if len(b.newBidCh) > 0 {
return
}
select {
case b.newBidCh <- newBidPackage{bid: bestBid.bid}:
log.Debug("BidSimulator: recommit last bid", "builder", bidRuntime.bid.Builder, "bidHash", bidRuntime.bid.Hash().Hex())
default:
}
2024-03-08 06:15:35 +03:00
}
// reportIssue reports the issue to the mev-sentry
func (b *bidSimulator) reportIssue(bidRuntime *BidRuntime, err error) {
2024-03-21 10:03:35 +03:00
metrics.GetOrRegisterCounter(fmt.Sprintf("bid/err/%v", bidRuntime.bid.Builder), nil).Inc(1)
2024-03-08 06:15:35 +03:00
cli := b.builders[bidRuntime.bid.Builder]
if cli != nil {
2024-04-11 10:15:46 +03:00
err = cli.ReportIssue(context.Background(), &types.BidIssue{
2024-03-08 06:15:35 +03:00
Validator: bidRuntime.env.header.Coinbase,
Builder: bidRuntime.bid.Builder,
2024-04-11 10:15:46 +03:00
BidHash: bidRuntime.bid.Hash(),
2024-03-08 06:15:35 +03:00
Message: err.Error(),
})
2024-04-11 10:15:46 +03:00
if err != nil {
log.Warn("BidSimulator: failed to report issue", "builder", bidRuntime.bid.Builder, "err", err)
2024-04-11 10:15:46 +03:00
}
2024-03-08 06:15:35 +03:00
}
}
type BidRuntime struct {
bid *types.Bid
env *environment
expectedBlockReward *big.Int
expectedValidatorReward *big.Int
packedBlockReward *big.Int
packedValidatorReward *big.Int
finished chan struct{}
2024-03-08 06:15:35 +03:00
duration time.Duration
}
func newBidRuntime(newBid *types.Bid, validatorCommission uint64) (*BidRuntime, error) {
// check the block reward and validator reward of the newBid
expectedBlockReward := newBid.GasFee
expectedValidatorReward := new(big.Int).Mul(expectedBlockReward, big.NewInt(int64(validatorCommission)))
expectedValidatorReward.Div(expectedValidatorReward, big.NewInt(10000))
expectedValidatorReward.Sub(expectedValidatorReward, newBid.BuilderFee)
if expectedValidatorReward.Cmp(big.NewInt(0)) < 0 {
// damage self profit, ignore
log.Debug("BidSimulator: invalid bid, validator reward is less than 0, ignore",
"builder", newBid.Builder, "bidHash", newBid.Hash().Hex())
return nil, fmt.Errorf("validator reward is less than 0, value: %s, commissionConfig: %d", expectedValidatorReward, validatorCommission)
}
bidRuntime := &BidRuntime{
bid: newBid,
expectedBlockReward: expectedBlockReward,
expectedValidatorReward: expectedValidatorReward,
packedBlockReward: big.NewInt(0),
packedValidatorReward: big.NewInt(0),
finished: make(chan struct{}),
}
return bidRuntime, nil
}
2024-03-08 06:15:35 +03:00
func (r *BidRuntime) validReward() bool {
return r.packedBlockReward.Cmp(r.expectedBlockReward) >= 0 &&
r.packedValidatorReward.Cmp(r.expectedValidatorReward) >= 0
}
func (r *BidRuntime) isExpectedBetterThan(other *BidRuntime) bool {
return r.expectedBlockReward.Cmp(other.expectedBlockReward) >= 0 &&
r.expectedValidatorReward.Cmp(other.expectedValidatorReward) >= 0
}
2024-03-08 06:15:35 +03:00
// packReward calculates packedBlockReward and packedValidatorReward
func (r *BidRuntime) packReward(validatorCommission uint64) {
r.packedBlockReward = r.env.state.GetBalance(consensus.SystemAddress).ToBig()
r.packedValidatorReward = new(big.Int).Mul(r.packedBlockReward, big.NewInt(int64(validatorCommission)))
r.packedValidatorReward.Div(r.packedValidatorReward, big.NewInt(10000))
r.packedValidatorReward.Sub(r.packedValidatorReward, r.bid.BuilderFee)
}
func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, tx *types.Transaction, unRevertible bool) error {
2024-03-08 06:15:35 +03:00
var (
env = r.env
sc *types.BlobSidecar
2024-03-08 06:15:35 +03:00
)
2024-04-11 10:15:46 +03:00
// Start executing the transaction
r.env.state.SetTxContext(tx.Hash(), r.env.tcount)
2024-03-08 06:15:35 +03:00
if tx.Type() == types.BlobTxType {
sc = types.NewBlobSidecarFromTx(tx)
if sc == nil {
return errors.New("blob transaction without blobs in miner")
2024-03-08 06:15:35 +03:00
}
// Checking against blob gas limit: It's kind of ugly to perform this check here, but there
// isn't really a better place right now. The blob gas limit is checked at block validation time
// and not during execution. This means core.ApplyTransaction will not return an error if the
// tx has too many blobs. So we have to explicitly check it here.
if (env.blobs+len(sc.Blobs))*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock {
return errors.New("max data blobs reached")
}
}
receipt, err := core.ApplyTransaction(chainConfig, chain, &env.coinbase, env.gasPool, env.state, env.header, tx,
&env.header.GasUsed, *chain.GetVMConfig(), core.NewReceiptBloomGenerator())
if err != nil {
return err
} else if unRevertible && receipt.Status == types.ReceiptStatusFailed {
return errors.New("no revertible transaction failed")
2024-03-08 06:15:35 +03:00
}
if tx.Type() == types.BlobTxType {
BlobTx: implement EIP-4844 on BSC (#2279) * ci: temp enable blobtx branch ci run; * Switch ON blobpool & ensure Cancun hardfork can occur (#2223) * feat: support blob storage & miscs; (#2229) * chainconfig: use cancun fork for BSC; * feat: fill WithdrawalsHash when BSC enable cancun fork; * rawdb: support to CRUD blobs; * freezer: support to freeze block blobs; * blockchain: add blob cache & blob query helper; * freezer: refactor addition table logic, add uts; * blobexpiry: add more extra expiry time, and logs; * parlia: implement IsDataAvailable function; * blob: refactor blob transfer logic; * blob: support config blob extra reserve; * blockchian: support to import block with blob & blobGasFee; (#2260) * blob: implement min&max gas price logic; * blockchian: support import side chain; * blobpool: reject the banned address; * blockchain: add chasing head for DA check; * params: update blob related config; * blockchain: opt data available checking performance; * params: modify blob related params; * gasprice: support BEP-336 blob gas price calculate; * blobTx: mining + brodcasting (#2253) * blobtx mining pass (#2282) * Sidecar fetching changes for 4844 (#2283) * ci: temp enable blobtx branch ci run; * Switch ON blobpool & ensure Cancun hardfork can occur (#2223) * feat: support blob storage & miscs; (#2229) * chainconfig: use cancun fork for BSC; feat: fill WithdrawalsHash when BSC enable cancun fork; * rawdb: support to CRUD blobs; * freezer: support to freeze block blobs; * blockchain: add blob cache & blob query helper; * freezer: refactor addition table logic, add uts; * blobexpiry: add more extra expiry time, and logs; * parlia: implement IsDataAvailable function; * blob: refactor blob transfer logic; * blob: support config blob extra reserve; * blockchian: support to import block with blob & blobGasFee; (#2260) * blob: implement min&max gas price logic; * blockchian: support import side chain; * blobpool: reject the banned address; * blockchain: add chasing head for DA check; * params: update blob related config; * blockchain: opt data available checking performance; * params: modify blob related params; * gasprice: support BEP-336 blob gas price calculate; * fix failed check for WithdrawalsHash (#2276) * eth: include sidecars in fitering of body * core: refactor sidecars name * eth: sidecars type refactor * core: remove extra from bad merge * eth: fix handlenewblock test after merge * Implement eth_getBlobSidecars && eth_getBlobSidecarByTxHash (#2286) * execution: add blob gas fee reward to system; * syncing: support blob syncing & DA checking; * naming: rename blobs to sidecars; * fix the semantics of WithXXX (#2293) * config: reduce sidecar cache to 1024 and rename (#2297) * fix: Withdrawals turn into empty from nil when BlockBody has Sidecars (#2301) * internal/api_test: add test case for eth_getBlobSidecars && eth_getBlobSidecarByTxHash (#2300) * consensus/misc: rollback CalcBlobFee (#2306) * flags: add new flags to override blobs' params; * freezer: fix blob ancient save error; * blobsidecar: add new sidecar struct with metadata; (#2315) * core/rawdb: optimize write block with sidecars (#2318) * core: more check for validity of sidecars * mev: add TxIndex for mev bid (#2325) * remove useless Config() (#2326) * fix WithSidecars (#2327) * fix: fix mined block sidecar issue; (#2328) * fix WithSidecars (#2329) --------- Co-authored-by: GalaIO <GalaIO@users.noreply.github.com> Co-authored-by: buddho <galaxystroller@gmail.com> Co-authored-by: Satyajit Das <emailtovamos@gmail.com> Co-authored-by: Eric <45141191+zlacfzy@users.noreply.github.com> Co-authored-by: zzzckck <152148891+zzzckck@users.noreply.github.com>
2024-03-22 17:37:47 +03:00
sc.TxIndex = uint64(len(env.txs))
2024-03-08 06:15:35 +03:00
env.txs = append(env.txs, tx.WithoutBlobTxSidecar())
env.receipts = append(env.receipts, receipt)
env.sidecars = append(env.sidecars, sc)
env.blobs += len(sc.Blobs)
*env.header.BlobGasUsed += receipt.BlobGasUsed
} else {
env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt)
}
2024-04-11 10:15:46 +03:00
r.env.tcount++
r.env.size += uint32(tx.Size())
2024-04-11 10:15:46 +03:00
2024-03-08 06:15:35 +03:00
return nil
}
func weiToEtherStringF6(wei *big.Int) string {
f, _ := new(big.Float).Quo(new(big.Float).SetInt(wei), big.NewFloat(params.Ether)).Float64()
return strconv.FormatFloat(f, 'f', 6, 64)
}