Skip to content

Commit

Permalink
refac: bid decode logic
Browse files Browse the repository at this point in the history
  • Loading branch information
irrun committed Feb 27, 2024
1 parent fed4b23 commit 2d30834
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 192 deletions.
145 changes: 106 additions & 39 deletions core/types/bid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion core/types/bid_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
2 changes: 1 addition & 1 deletion eth/api_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
16 changes: 8 additions & 8 deletions internal/ethapi/api_mev.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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")
}

Expand Down
63 changes: 30 additions & 33 deletions miner/bid_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package miner

import (
"context"
"crypto/tls"
"errors"
"fmt"
"math/big"
Expand All @@ -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 (
Expand All @@ -45,7 +49,6 @@ var (
MaxIdleConnsPerHost: 50,
MaxConnsPerHost: 50,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}

client = &http.Client{
Expand Down Expand Up @@ -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")
}
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()

Expand All @@ -483,19 +474,24 @@ 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")
}

if len(b.pending[blockNumber][builder]) >= maxBidPerBuilderPerBlock {
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) {
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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) (
Expand Down
Loading

0 comments on commit 2d30834

Please sign in to comment.