Skip to content

Commit

Permalink
feat: support MEV
Browse files Browse the repository at this point in the history
* core/types: define Bid and related error message
* internal/eth_api: add MevAPI to receive bids from builder
* eth/api_admin: add API to support start and stop mev dynamically
* miner: add bidSimulator to maintain bid simulation and fetch best bid from
* consensus: add func to query the next inturn validator
  • Loading branch information
irrun committed Mar 6, 2024
1 parent 38d592d commit 7877f70
Show file tree
Hide file tree
Showing 22 changed files with 1,423 additions and 6 deletions.
2 changes: 1 addition & 1 deletion cmd/geth/consolecmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

const (
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 parlia:1.0 rpc:1.0 txpool:1.0 web3:1.0"
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 mev:1.0 miner:1.0 net:1.0 parlia:1.0 rpc:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
)

Expand Down
23 changes: 23 additions & 0 deletions common/bidutil/bidutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bidutil

import (
"time"

"github.com/ethereum/go-ethereum/core/types"
)

// BidBetterBefore returns the time when the next bid better be received, considering the delay and bid simulation.
// BidBetterBefore is earlier than BidMustBefore.
func BidBetterBefore(parentHeader *types.Header, blockPeriod uint64, delayLeftOver, simulationLeftOver time.Duration) time.Time {
nextHeaderTime := BidMustBefore(parentHeader, blockPeriod, delayLeftOver)
nextHeaderTime = nextHeaderTime.Add(-simulationLeftOver)
return nextHeaderTime
}

// BidMustBefore returns the time when the next bid must be received,
// only considering the consensus delay but not bid simulation duration.
func BidMustBefore(parentHeader *types.Header, blockPeriod uint64, delayLeftOver time.Duration) time.Time {
nextHeaderTime := time.Unix(int64(parentHeader.Time+blockPeriod), 0)
nextHeaderTime = nextHeaderTime.Add(-delayLeftOver)
return nextHeaderTime
}
5 changes: 5 additions & 0 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [
return abort, results
}

// NextInTurnValidator return the next in-turn validator for header
func (beacon *Beacon) NextInTurnValidator(chain consensus.ChainHeaderReader, header *types.Header) (common.Address, error) {
return common.Address{}, errors.New("not implemented")
}

// Prepare implements consensus.Engine, initializing the difficulty field of a
// header to conform to the beacon protocol. The changes are done inline.
func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
Expand Down
5 changes: 5 additions & 0 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,11 @@ func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*typ
return nil
}

// NextInTurnValidator return the next in-turn validator for header
func (c *Clique) NextInTurnValidator(chain consensus.ChainHeaderReader, header *types.Header) (common.Address, error) {
return common.Address{}, errors.New("not implemented")
}

// Prepare implements consensus.Engine, preparing all the consensus fields of the
// header for running the transactions on top.
func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
Expand Down
3 changes: 3 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ type Engine interface {
// rules of a given engine.
VerifyUncles(chain ChainReader, block *types.Block) error

// NextInTurnValidator return the next in-turn validator for header
NextInTurnValidator(chain ChainHeaderReader, header *types.Header) (common.Address, error)

// Prepare initializes the consensus fields of a block header according to the
// rules of a particular engine. The changes are executed inline.
Prepare(chain ChainHeaderReader, header *types.Header) error
Expand Down
5 changes: 5 additions & 0 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,11 @@ var FrontierDifficultyCalculator = calcDifficultyFrontier
var HomesteadDifficultyCalculator = calcDifficultyHomestead
var DynamicDifficultyCalculator = makeDifficultyCalculator

// NextInTurnValidator return the next in-turn validator for header
func (ethash *Ethash) NextInTurnValidator(chain consensus.ChainHeaderReader, header *types.Header) (common.Address, error) {
return common.Address{}, errors.New("not implemented")
}

// Prepare implements consensus.Engine, initializing the difficulty field of a
// header to conform to the ethash protocol. The changes are done inline.
func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
Expand Down
10 changes: 10 additions & 0 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,16 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head
return nil
}

// NextInTurnValidator return the next in-turn validator for header
func (p *Parlia) NextInTurnValidator(chain consensus.ChainHeaderReader, header *types.Header) (common.Address, error) {
snap, err := p.snapshot(chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return common.Address{}, err
}

return snap.inturnValidator(), nil
}

// Prepare implements consensus.Engine, preparing all the consensus fields of the
// header for running the transactions on top.
func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
Expand Down
7 changes: 7 additions & 0 deletions consensus/parlia/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,13 @@ func (s *Snapshot) inturn(validator common.Address) bool {
return validators[offset] == validator
}

// inturnValidator returns the validator at a given block height.
func (s *Snapshot) inturnValidator() common.Address {
validators := s.validators()
offset := (s.Number + 1) % uint64(len(validators))
return validators[offset]
}

func (s *Snapshot) enoughDistance(validator common.Address, header *types.Header) bool {
idx := s.indexOfVal(validator)
if idx < 0 {
Expand Down
185 changes: 185 additions & 0 deletions core/types/bid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package types

import (
"fmt"
"sync/atomic"
"time"

"github.com/holiman/uint256"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)

const TxDecodeConcurrencyForPerBid = 5

// BidArgs represents the arguments to submit a bid.
type BidArgs struct {
// RawBid from builder directly
RawBid *RawBid
// Signature of the bid from builder
Signature hexutil.Bytes `json:"signature"`

// PayBidTx is a payment tx to builder from sentry, which is optional
PayBidTx hexutil.Bytes `json:"payBidTx"`
PayBidTxGasUsed uint64 `json:"payBidTxGasUsed"`
}

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 = uint256.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"`
Txs []hexutil.Bytes `json:"txs"`
GasUsed uint64 `json:"gasUsed"`
GasFee *uint256.Int `json:"gasFee"`
BuilderFee *uint256.Int `json:"builderFee"`

hash atomic.Value
}

func (b *RawBid) DecodeTxs(signer Signer) ([]*Transaction, error) {
if len(b.Txs) == 0 {
return []*Transaction{}, nil
}

txChan := make(chan int, len(b.Txs))
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, ok := <-txChan
if !ok {
errChan <- nil
return
}

txBytes := b.Txs[txIndex]
tx, err := decode(txBytes)
if err != nil {
errChan <- err
return
}

bidTxs[txIndex] = tx
}
}()
}

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 {
return hash.(common.Hash)
}

h := rlpHash(b)
b.hash.Store(h)

return h
}

// Bid represents a bid.
type Bid struct {
Builder common.Address
BlockNumber uint64
ParentHash common.Hash
Txs Transactions
GasUsed uint64
GasFee *uint256.Int
BuilderFee *uint256.Int

rawBid RawBid
}

// Hash returns the bid hash.
func (b *Bid) Hash() common.Hash {
return b.rawBid.Hash()
}

// BidIssue represents a bid issue.
type BidIssue struct {
Validator common.Address
Builder common.Address
BidHash common.Hash
Message string
}

type MevParams struct {
ValidatorCommission uint64 // 100 means 1%
BidSimulationLeftOver time.Duration
}
45 changes: 45 additions & 0 deletions core/types/bid_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package types

import "errors"

const (
InvalidBidParamError = -38001
InvalidPayBidTxError = -38002
MevNotRunningError = -38003
MevBusyError = -38004
MevNotInTurnError = -38005
)

var (
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)
)

// bidError is an API error that encompasses an invalid bid with JSON error
// code and a binary data blob.
type bidError struct {
error
code int
}

// ErrorCode returns the JSON error code for an invalid bid.
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
func (e *bidError) ErrorCode() int {
return e.code
}

func NewInvalidBidError(message string) *bidError {
return newBidError(errors.New(message), InvalidBidParamError)
}

func NewInvalidPayBidTxError(message string) *bidError {
return newBidError(errors.New(message), InvalidPayBidTxError)
}

func newBidError(err error, code int) *bidError {
return &bidError{
error: err,
code: code,
}
}
29 changes: 29 additions & 0 deletions eth/api_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
Expand Down Expand Up @@ -141,3 +142,31 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) {
}
return true, nil
}

// MevRunning returns true if the validator accept bids from builder
func (api *AdminAPI) MevRunning() bool {
return api.eth.APIBackend.MevRunning()
}

// StartMev starts mev. It notifies the miner to start to receive bids.
func (api *AdminAPI) StartMev() {
api.eth.APIBackend.StartMev()
}

// StopMev stops mev. It notifies the miner to stop receiving bids from this moment,
// but the bids before this moment would still been taken into consideration by mev.
func (api *AdminAPI) StopMev() {
api.eth.APIBackend.StopMev()
}

// AddBuilder adds a builder to the bid simulator.
// url is the endpoint of the builder, for example, "https://mev-builder.amazonaws.com",
// if validator is equipped with sentry, ignore the url.
func (api *AdminAPI) AddBuilder(builder common.Address, url string) error {
return api.eth.APIBackend.AddBuilder(builder, url)
}

// RemoveBuilder removes a builder from the bid simulator.
func (api *AdminAPI) RemoveBuilder(builder common.Address) error {
return api.eth.APIBackend.RemoveBuilder(builder)
}
Loading

0 comments on commit 7877f70

Please sign in to comment.