From 2d30834a7abecd2533f5f1c7ed7480a2288a9524 Mon Sep 17 00:00:00 2001 From: raina Date: Thu, 22 Feb 2024 21:17:01 +0800 Subject: [PATCH] refac: bid decode logic --- core/types/bid.go | 145 +++++++++++++++++++++++++++---------- core/types/bid_error.go | 2 +- eth/api_admin.go | 2 +- internal/ethapi/api_mev.go | 16 ++-- miner/bid_simulator.go | 63 ++++++++-------- miner/miner.go | 10 --- miner/miner_mev.go | 111 ++++------------------------ miner/worker.go | 8 +- 8 files changed, 165 insertions(+), 192 deletions(-) diff --git a/core/types/bid.go b/core/types/bid.go index 48fc74892c..b6b75cdd3d 100644 --- a/core/types/bid.go +++ b/core/types/bid.go @@ -8,22 +8,66 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" ) +const TxDecodeConcurrencyForPerBid = 5 + // BidArgs represents the arguments to submit a bid. type BidArgs struct { - // bid - Bid *RawBid - // signed signature of the bid + // RawBid from builder directly + RawBid *RawBid + // Signature of the bid from builder Signature hexutil.Bytes `json:"signature"` - // PayBidTx pays to builder + // PayBidTx is a payment tx to builder from sentry, which is optional PayBidTx hexutil.Bytes `json:"payBidTx"` PayBidTxGasUsed uint64 `json:"payBidTxGasUsed"` } -// RawBid represents a raw bid. +func (b *BidArgs) EcrecoverSender() (common.Address, error) { + pk, err := crypto.SigToPub(b.RawBid.Hash().Bytes(), b.Signature) + if err != nil { + return common.Address{}, err + } + + return crypto.PubkeyToAddress(*pk), nil +} + +func (b *BidArgs) ToBid(builder common.Address, signer Signer) (*Bid, error) { + txs, err := b.RawBid.DecodeTxs(signer) + if err != nil { + return nil, err + } + + if len(b.PayBidTx) != 0 { + var payBidTx = new(Transaction) + err = payBidTx.UnmarshalBinary(b.PayBidTx) + if err != nil { + return nil, err + } + + txs = append(txs, payBidTx) + } + + bid := &Bid{ + Builder: builder, + BlockNumber: b.RawBid.BlockNumber, + ParentHash: b.RawBid.ParentHash, + Txs: txs, + GasUsed: b.RawBid.GasUsed + b.PayBidTxGasUsed, + GasFee: b.RawBid.GasFee, + BuilderFee: b.RawBid.BuilderFee, + rawBid: *b.RawBid, + } + + if bid.BuilderFee == nil { + bid.BuilderFee = big.NewInt(0) + } + + return bid, nil +} + +// RawBid represents a raw bid from builder directly. type RawBid struct { BlockNumber uint64 `json:"blockNumber"` ParentHash common.Hash `json:"parentHash"` @@ -35,6 +79,62 @@ type RawBid struct { hash atomic.Value } +func (b *RawBid) DecodeTxs(signer Signer) ([]*Transaction, error) { + if len(b.Txs) == 0 { + return []*Transaction{}, nil + } + + txChan := make(chan int, TxDecodeConcurrencyForPerBid) + bidTxs := make([]*Transaction, len(b.Txs)) + decode := func(txBytes hexutil.Bytes) (*Transaction, error) { + tx := new(Transaction) + err := tx.UnmarshalBinary(txBytes) + if err != nil { + return nil, err + } + + _, err = Sender(signer, tx) + if err != nil { + return nil, err + } + + return tx, nil + } + + errChan := make(chan error, TxDecodeConcurrencyForPerBid) + for i := 0; i < TxDecodeConcurrencyForPerBid; i++ { + go func() { + for txIndex := range txChan { + txBytes := b.Txs[txIndex] + tx, err := decode(txBytes) + if err != nil { + errChan <- err + return + } + + bidTxs[txIndex] = tx + } + + errChan <- nil + }() + } + + for i := 0; i < len(b.Txs); i++ { + txChan <- i + } + + close(txChan) + + for i := 0; i < TxDecodeConcurrencyForPerBid; i++ { + err := <-errChan + if err != nil { + return nil, fmt.Errorf("failed to decode tx, %v", err) + } + } + + return bidTxs, nil +} + // Hash returns the hash of the bid. func (b *RawBid) Hash() common.Hash { if hash := b.hash.Load(); hash != nil { @@ -47,20 +147,6 @@ func (b *RawBid) Hash() common.Hash { return h } -func EcrecoverBuilder(args *BidArgs) (common.Address, error) { - bid, err := rlp.EncodeToBytes(args.Bid) - if err != nil { - return common.Address{}, fmt.Errorf("fail to encode bid, %v", err) - } - - pk, err := crypto.SigToPub(crypto.Keccak256(bid), args.Signature) - if err != nil { - return common.Address{}, fmt.Errorf("fail to extract pubkey, %v", err) - } - - return crypto.PubkeyToAddress(*pk), nil -} - // Bid represents a bid. type Bid struct { Builder common.Address @@ -74,25 +160,6 @@ type Bid struct { rawBid RawBid } -func FromRawBid(bid *RawBid, builder common.Address, txs Transactions, payBidTxGasUsed uint64) *Bid { - b := &Bid{ - Builder: builder, - BlockNumber: bid.BlockNumber, - ParentHash: bid.ParentHash, - Txs: txs, - GasUsed: bid.GasUsed + payBidTxGasUsed, - GasFee: bid.GasFee, - BuilderFee: big.NewInt(0), - rawBid: *bid, - } - - if bid.BuilderFee != nil { - b.BuilderFee = bid.BuilderFee - } - - return b -} - // Hash returns the bid hash. func (b *Bid) Hash() common.Hash { return b.rawBid.Hash() diff --git a/core/types/bid_error.go b/core/types/bid_error.go index 0715ed8fa1..6b543ae64f 100644 --- a/core/types/bid_error.go +++ b/core/types/bid_error.go @@ -11,7 +11,7 @@ const ( ) var ( - ErrMevNotRunning = newBidError(errors.New("the validator stop serving mev for now, try again later"), MevNotRunningError) + ErrMevNotRunning = newBidError(errors.New("the validator stop accepting bids for now, try again later"), MevNotRunningError) ErrMevBusy = newBidError(errors.New("the validator is working on too many bids, try again later"), MevBusyError) ErrMevNotInTurn = newBidError(errors.New("the validator is not in-turn to propose currently, try again later"), MevNotInTurnError) ) diff --git a/eth/api_admin.go b/eth/api_admin.go index 1a9a9bb325..ce10780fbd 100644 --- a/eth/api_admin.go +++ b/eth/api_admin.go @@ -139,7 +139,7 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) { return true, nil } -// MevRunning returns true if mev is running +// MevRunning returns true if the validator accept bids from builder func (api *AdminAPI) MevRunning() bool { return api.eth.APIBackend.MevRunning() } diff --git a/internal/ethapi/api_mev.go b/internal/ethapi/api_mev.go index e38c85398b..6e2d0f55f0 100644 --- a/internal/ethapi/api_mev.go +++ b/internal/ethapi/api_mev.go @@ -36,26 +36,26 @@ func (m *MevAPI) SendBid(ctx context.Context, args types.BidArgs) (common.Hash, } var ( - bid = args.Bid + rawBid = args.RawBid currentHeader = m.b.CurrentHeader() ) - if bid == nil { - return common.Hash{}, types.NewInvalidBidError("bid should not be nil") + if rawBid == nil { + return common.Hash{}, types.NewInvalidBidError("rawBid should not be nil") } // only support bidding for the next block not for the future block - if bid.BlockNumber != currentHeader.Number.Uint64()+1 { + if rawBid.BlockNumber != currentHeader.Number.Uint64()+1 { return common.Hash{}, types.NewInvalidBidError("stale block number or block in future") } - if bid.ParentHash != currentHeader.Hash() { + if rawBid.ParentHash != currentHeader.Hash() { return common.Hash{}, types.NewInvalidBidError( fmt.Sprintf("non-aligned parent hash: %v", currentHeader.Hash())) } - if bid.BuilderFee != nil { - builderFee := bid.BuilderFee + if rawBid.BuilderFee != nil { + builderFee := rawBid.BuilderFee if builderFee.Cmp(common.Big0) < 0 { return common.Hash{}, types.NewInvalidBidError("builder fee should not be less than 0") } @@ -66,7 +66,7 @@ func (m *MevAPI) SendBid(ctx context.Context, args types.BidArgs) (common.Hash, } } - if builderFee.Cmp(bid.GasFee) >= 0 { + if builderFee.Cmp(rawBid.GasFee) >= 0 { return common.Hash{}, types.NewInvalidBidError("builder fee must be less than gas fee") } diff --git a/miner/bid_simulator.go b/miner/bid_simulator.go index ad554ab0f4..367cdbe9a6 100644 --- a/miner/bid_simulator.go +++ b/miner/bid_simulator.go @@ -2,7 +2,6 @@ package miner import ( "context" - "crypto/tls" "errors" "fmt" "math/big" @@ -28,6 +27,11 @@ const ( maxBidPerBuilderPerBlock = 3 commitInterruptBetterBid = 1 + + // leftOverTimeRate is the rate of left over time to simulate a bid + leftOverTimeRate = 11 + // leftOverTimeScale is the scale of left over time to simulate a bid + leftOverTimeScale = 10 ) var ( @@ -45,7 +49,6 @@ var ( MaxIdleConnsPerHost: 50, MaxConnsPerHost: 50, IdleConnTimeout: 90 * time.Second, - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client = &http.Client{ @@ -132,7 +135,7 @@ func newBidSimulator( b.dialSentryAndBuilders() if len(b.builders) == 0 { - log.Warn("BidSimulator: no validReward builders") + log.Warn("BidSimulator: no valid builders") } } @@ -195,8 +198,8 @@ func (b *bidSimulator) receivingBid() bool { } func (b *bidSimulator) startReceivingBid() { - b.bidReceiving.Store(true) b.dialSentryAndBuilders() + b.bidReceiving.Store(true) } func (b *bidSimulator) stopReceivingBid() { @@ -315,11 +318,8 @@ func (b *bidSimulator) newBidLoop() { if lastBid := b.GetBestBid(bidRuntime.bid.ParentHash); lastBid != nil && lastBid.duration != 0 { simDuration = lastBid.duration } - // simulatingBid's duration is longer than bestBid's duration in most case - if lastBid := b.GetSimulatingBid(bidRuntime.bid.ParentHash); lastBid != nil && lastBid.duration != 0 { - simDuration = lastBid.duration - } - if time.Until(b.bidMustBefore(bidRuntime.bid.ParentHash)) <= simDuration { + + if time.Until(b.bidMustBefore(bidRuntime.bid.ParentHash)) <= simDuration*leftOverTimeRate/leftOverTimeScale { return } @@ -449,28 +449,19 @@ func (b *bidSimulator) clearLoop() { // 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(ctx context.Context, bid *types.Bid) error { - if !b.ExistBuilder(bid.Builder) { - return errors.New("builder is not registered") +func (b *bidSimulator) sendBid(_ context.Context, bid *types.Bid) error { + timer := time.NewTimer(1 * time.Second) + defer timer.Stop() + select { + case b.newBidCh <- bid: + b.AddPending(bid.BlockNumber, bid.Builder, bid.Hash()) + return nil + case <-timer.C: + return types.ErrMevBusy } - - err := b.pendingCheck(bid) - if err != nil { - return err - } - - // pass checking, add bid into alternative chan waiting for judge profit - b.newBidCh <- bid - - return nil } -func (b *bidSimulator) pendingCheck(bid *types.Bid) error { - var ( - builder = bid.Builder - blockNumber = bid.BlockNumber - ) - +func (b *bidSimulator) CheckPending(blockNumber uint64, builder common.Address, bidHash common.Hash) error { b.pendingMu.Lock() defer b.pendingMu.Unlock() @@ -483,7 +474,7 @@ func (b *bidSimulator) pendingCheck(bid *types.Bid) error { b.pending[blockNumber][builder] = make(map[common.Hash]struct{}) } - if _, ok := b.pending[blockNumber][builder][bid.Hash()]; ok { + if _, ok := b.pending[blockNumber][builder][bidHash]; ok { return errors.New("bid already exists") } @@ -491,11 +482,16 @@ func (b *bidSimulator) pendingCheck(bid *types.Bid) error { return errors.New("too many bids") } - b.pending[blockNumber][builder][bid.Hash()] = struct{}{} - 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) { @@ -580,6 +576,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) { _, 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 } @@ -603,7 +600,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) { return } - // TODO(renee-) opt bid comparation + // this is the simplest strategy: best for all the delegators. if bidRuntime.packedBlockReward.Cmp(bestBid.packedBlockReward) > 0 { b.SetBestBid(bidRuntime.bid.ParentHash, bidRuntime) success = true @@ -647,7 +644,7 @@ func (r *BidRuntime) packReward(validatorCommission int64) { r.packedBlockReward = r.env.state.GetBalance(consensus.SystemAddress) r.packedValidatorReward = new(big.Int).Mul(r.packedBlockReward, big.NewInt(validatorCommission)) r.packedValidatorReward.Div(r.packedValidatorReward, big.NewInt(10000)) - r.packedValidatorReward.Sub(r.packedBlockReward, r.bid.BuilderFee) + r.packedValidatorReward.Sub(r.packedValidatorReward, r.bid.BuilderFee) } func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, tx *types.Transaction) ( diff --git a/miner/miner.go b/miner/miner.go index 0b5a30f19e..9b3a067ffb 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -23,8 +23,6 @@ import ( "sync" "time" - "github.com/panjf2000/ants/v2" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -89,7 +87,6 @@ type Miner struct { worker *worker bidSimulator *bidSimulator - antsPool *ants.Pool wg sync.WaitGroup } @@ -104,12 +101,6 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even stopCh: make(chan struct{}), worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, false), } - antsPool, err := ants.NewPool(MevRoutineLimit) - if err != nil { - // could never happen - panic(fmt.Sprintf("Miner: failed to create ants pool, %v", err)) - } - miner.antsPool = antsPool miner.bidSimulator = newBidSimulator(&config.Mev, config.DelayLeftOver, chainConfig, eth.BlockChain(), miner.worker) miner.worker.setBestBidFetcher(miner.bidSimulator) @@ -203,7 +194,6 @@ func (miner *Miner) Stop() { } func (miner *Miner) Close() { - miner.antsPool.Release() close(miner.exitCh) miner.wg.Wait() } diff --git a/miner/miner_mev.go b/miner/miner_mev.go index 12f5b8111b..85c0898df3 100644 --- a/miner/miner_mev.go +++ b/miner/miner_mev.go @@ -6,17 +6,8 @@ import ( "math/big" "time" - "github.com/panjf2000/ants/v2" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" -) - -const ( - MevRoutineLimit = 10000 - TxDecodeConcurrencyForPerBid = 5 - CheckTxDecodeInterval = 3 * time.Millisecond ) type BuilderConfig struct { @@ -57,97 +48,28 @@ func (miner *Miner) RemoveBuilder(builderAddr common.Address) error { return miner.bidSimulator.RemoveBuilder(builderAddr) } -func (miner *Miner) SendBid(ctx context.Context, bid *types.BidArgs) (common.Hash, error) { - builder, err := types.EcrecoverBuilder(bid) +func (miner *Miner) SendBid(ctx context.Context, bidArgs *types.BidArgs) (common.Hash, error) { + builder, err := bidArgs.EcrecoverSender() if err != nil { return common.Hash{}, types.NewInvalidBidError(fmt.Sprintf("invalid signature:%v", err)) } - bidTxs := make([]*BidTx, len(bid.Bid.Txs)) - signer := types.MakeSigner(miner.worker.chainConfig, big.NewInt(int64(bid.Bid.BlockNumber)), uint64(time.Now().Unix())) - - txChan := make(chan int, TxDecodeConcurrencyForPerBid) - for i := 0; i < TxDecodeConcurrencyForPerBid; i++ { - go func() { - for { - select { - case txIndex := <-txChan: - err = miner.antsPool.Submit(func() { - encodedTx := bid.Bid.Txs[txIndex] - tx := new(types.Transaction) - er := tx.UnmarshalBinary(encodedTx) - if er != nil { - bidTxs[txIndex] = &BidTx{tx: nil, err: er} - return - } - - _, er = types.Sender(signer, tx) - if er != nil { - bidTxs[txIndex] = &BidTx{tx: nil, err: er} - return - } - - bidTxs[txIndex] = &BidTx{tx: tx, err: nil} - }) - - if err != nil { - bidTxs[txIndex] = &BidTx{tx: nil, err: err} - } - case <-ctx.Done(): - return - } - } - }() - } - - for i := 0; i < len(bid.Bid.Txs); i++ { - select { - case txChan <- i: - } - } - - for { - if len(bidTxs) == len(bid.Bid.Txs) { - break - } - time.Sleep(CheckTxDecodeInterval) - log.Debug("waiting for txs to be decoded") - } - - txs := make([]*types.Transaction, 0) - for _, v := range bidTxs { - if v == nil { - return common.Hash{}, types.NewInvalidBidError("invalid tx in bid") - } - - if v.err != nil { - if v.err == ants.ErrPoolClosed || v.err == ants.ErrPoolOverload { - return common.Hash{}, types.ErrMevBusy - } - - return common.Hash{}, types.NewInvalidBidError("invalid tx in bid") - } - - if v.tx != nil { - txs = append(txs, v.tx) - } + if !miner.bidSimulator.ExistBuilder(builder) { + return common.Hash{}, types.NewInvalidBidError("builder is not registered") } - if len(txs) != len(bid.Bid.Txs) { - return common.Hash{}, types.NewInvalidBidError("invalid tx in bid") + err = miner.bidSimulator.CheckPending(bidArgs.RawBid.BlockNumber, builder, bidArgs.RawBid.Hash()) + if err != nil { + return common.Hash{}, err } - if len(bid.PayBidTx) != 0 { - var payBidTx = new(types.Transaction) - if err = payBidTx.UnmarshalBinary(bid.PayBidTx); err != nil { - return common.Hash{}, types.NewInvalidBidError(fmt.Sprintf("unmarshal transfer tx err:%v", err)) - } - txs = append(txs, payBidTx) + signer := types.MakeSigner(miner.worker.chainConfig, big.NewInt(int64(bidArgs.RawBid.BlockNumber)), uint64(time.Now().Unix())) + bid, err := bidArgs.ToBid(builder, signer) + if err != nil { + return common.Hash{}, types.NewInvalidBidError(fmt.Sprintf("fail to convert bidArgs to bid, %v", err)) } - innerBid := types.FromRawBid(bid.Bid, builder, txs, bid.PayBidTxGasUsed) - - bidMustBefore := miner.bidSimulator.bidMustBefore(bid.Bid.ParentHash) + bidMustBefore := miner.bidSimulator.bidMustBefore(bidArgs.RawBid.ParentHash) timeout := time.Until(bidMustBefore) if timeout <= 0 { @@ -155,16 +77,11 @@ func (miner *Miner) SendBid(ctx context.Context, bid *types.BidArgs) (common.Has common.PrettyDuration(timeout)) } - err = miner.bidSimulator.sendBid(ctx, innerBid) + err = miner.bidSimulator.sendBid(ctx, bid) if err != nil { return common.Hash{}, err } - return innerBid.Hash(), nil -} - -type BidTx struct { - tx *types.Transaction - err error + return bid.Hash(), nil } diff --git a/miner/worker.go b/miner/worker.go index b95187ae20..0f638afd3b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1160,10 +1160,12 @@ LOOP: bestBid := w.bidFetcher.GetBestBid(bestWork.header.ParentHash) if bestBid != nil && bestBid.packedBlockReward.Cmp(bestReward) > 0 { - localRewardForCoinbase := new(big.Int).Mul(bestReward, big.NewInt(w.config.Mev.ValidatorCommission)) - localRewardForCoinbase.Div(localRewardForCoinbase, big.NewInt(10000)) + // localValidatorReward is the reward for the validator self by the local block. + localValidatorReward := new(big.Int).Mul(bestReward, big.NewInt(w.config.Mev.ValidatorCommission)) + localValidatorReward.Div(localValidatorReward, big.NewInt(10000)) - if bestBid.packedValidatorReward.Cmp(localRewardForCoinbase) > 0 { + // blockReward(benefits delegators) and validatorReward(benefits the validator) are both optimal + if bestBid.packedValidatorReward.Cmp(localValidatorReward) > 0 { bestWork = bestBid.env } }