feat: support MEV (#2224)
This commit is contained in:
parent
0dc4b1f119
commit
89c4ab2a05
@ -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"
|
||||
)
|
||||
|
||||
|
23
common/bidutil/bidutil.go
Normal file
23
common/bidutil/bidutil.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
184
core/types/bid.go
Normal file
184
core/types/bid.go
Normal file
@ -0,0 +1,184 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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 = 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"`
|
||||
Txs []hexutil.Bytes `json:"txs"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
GasFee *big.Int `json:"gasFee"`
|
||||
BuilderFee *big.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 *big.Int
|
||||
BuilderFee *big.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
core/types/bid_error.go
Normal file
45
core/types/bid_error.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
@ -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"
|
||||
@ -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)
|
||||
}
|
||||
|
@ -456,3 +456,39 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
|
||||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
||||
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) MevRunning() bool {
|
||||
return b.Miner().MevRunning()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) MevParams() *types.MevParams {
|
||||
return b.Miner().MevParams()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StartMev() {
|
||||
b.Miner().StartMev()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StopMev() {
|
||||
b.Miner().StopMev()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) AddBuilder(builder common.Address, url string) error {
|
||||
return b.Miner().AddBuilder(builder, url)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) RemoveBuilder(builder common.Address) error {
|
||||
return b.Miner().RemoveBuilder(builder)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) SendBid(ctx context.Context, bid *types.BidArgs) (common.Hash, error) {
|
||||
return b.Miner().SendBid(ctx, bid)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) BestBidGasFee(parentHash common.Hash) *big.Int {
|
||||
return b.Miner().BestPackedBlockReward(parentHash)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) MinerInTurn() bool {
|
||||
return b.Miner().InTurn()
|
||||
}
|
||||
|
@ -52,6 +52,16 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
|
||||
return NewClient(c), nil
|
||||
}
|
||||
|
||||
// DialOptions creates a new RPC client for the given URL. You can supply any of the
|
||||
// pre-defined client options to configure the underlying transport.
|
||||
func DialOptions(ctx context.Context, rawurl string, opts ...rpc.ClientOption) (*Client, error) {
|
||||
c, err := rpc.DialOptions(ctx, rawurl, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(c), nil
|
||||
}
|
||||
|
||||
// NewClient creates a client that uses the given RPC client.
|
||||
func NewClient(c *rpc.Client) *Client {
|
||||
return &Client{c}
|
||||
@ -715,6 +725,43 @@ func (ec *Client) SendTransactionConditional(ctx context.Context, tx *types.Tran
|
||||
return ec.c.CallContext(ctx, nil, "eth_sendRawTransactionConditional", hexutil.Encode(data), opts)
|
||||
}
|
||||
|
||||
// MevRunning returns whether MEV is running
|
||||
func (ec *Client) MevRunning(ctx context.Context) (bool, error) {
|
||||
var result bool
|
||||
err := ec.c.CallContext(ctx, &result, "mev_running")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// SendBid sends a bid
|
||||
func (ec *Client) SendBid(ctx context.Context, args types.BidArgs) (common.Hash, error) {
|
||||
var hash common.Hash
|
||||
err := ec.c.CallContext(ctx, &hash, "mev_sendBid", args)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// BestBidGasFee returns the gas fee of the best bid for the given parent hash.
|
||||
func (ec *Client) BestBidGasFee(ctx context.Context, parentHash common.Hash) (*big.Int, error) {
|
||||
var fee *big.Int
|
||||
err := ec.c.CallContext(ctx, &fee, "mev_bestBidGasFee", parentHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fee, nil
|
||||
}
|
||||
|
||||
// MevParams returns the static params of mev
|
||||
func (ec *Client) MevParams(ctx context.Context) (*types.MevParams, error) {
|
||||
var params types.MevParams
|
||||
err := ec.c.CallContext(ctx, ¶ms, "mev_params")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ¶ms, err
|
||||
}
|
||||
|
||||
func toBlockNumArg(number *big.Int) string {
|
||||
if number == nil {
|
||||
return "latest"
|
||||
|
111
internal/ethapi/api_mev.go
Normal file
111
internal/ethapi/api_mev.go
Normal file
@ -0,0 +1,111 @@
|
||||
package ethapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
TransferTxGasLimit = 25000
|
||||
)
|
||||
|
||||
// MevAPI implements the interfaces that defined in the BEP-322.
|
||||
// It offers methods for the interaction between builders and validators.
|
||||
type MevAPI struct {
|
||||
b Backend
|
||||
}
|
||||
|
||||
// NewMevAPI creates a new MevAPI.
|
||||
func NewMevAPI(b Backend) *MevAPI {
|
||||
return &MevAPI{b}
|
||||
}
|
||||
|
||||
// SendBid receives bid from the builders.
|
||||
// If mev is not running or bid is invalid, return error.
|
||||
// Otherwise, creates a builder bid for the given argument, submit it to the miner.
|
||||
func (m *MevAPI) SendBid(ctx context.Context, args *types.BidArgs) (common.Hash, error) {
|
||||
if !m.b.MevRunning() {
|
||||
return common.Hash{}, types.ErrMevNotRunning
|
||||
}
|
||||
|
||||
if !m.b.MinerInTurn() {
|
||||
return common.Hash{}, types.ErrMevNotInTurn
|
||||
}
|
||||
|
||||
var (
|
||||
rawBid = args.RawBid
|
||||
currentHeader = m.b.CurrentHeader()
|
||||
)
|
||||
|
||||
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 rawBid.BlockNumber != currentHeader.Number.Uint64()+1 {
|
||||
return common.Hash{}, types.NewInvalidBidError("stale block number or block in future")
|
||||
}
|
||||
|
||||
if rawBid.ParentHash != currentHeader.Hash() {
|
||||
return common.Hash{}, types.NewInvalidBidError(
|
||||
fmt.Sprintf("non-aligned parent hash: %v", currentHeader.Hash()))
|
||||
}
|
||||
|
||||
if rawBid.GasFee == nil || rawBid.GasFee.Cmp(common.Big0) == 0 || rawBid.GasUsed == 0 {
|
||||
return common.Hash{}, types.NewInvalidBidError("empty gasFee or empty gasUsed")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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) ||
|
||||
(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")
|
||||
}
|
||||
}
|
||||
|
||||
return m.b.SendBid(ctx, args)
|
||||
}
|
||||
|
||||
func (m *MevAPI) BestBidGasFee(_ context.Context, parentHash common.Hash) *big.Int {
|
||||
return m.b.BestBidGasFee(parentHash)
|
||||
}
|
||||
|
||||
func (m *MevAPI) Params() *types.MevParams {
|
||||
return m.b.MevParams()
|
||||
}
|
||||
|
||||
// Running returns true if mev is running
|
||||
func (m *MevAPI) Running() bool {
|
||||
return m.b.MevRunning()
|
||||
}
|
@ -30,6 +30,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
@ -50,9 +54,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/internal/blocktest"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func testTransactionMarshal(t *testing.T, tests []txData, config *params.ChainConfig) {
|
||||
@ -633,6 +634,23 @@ func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.Match
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (b *testBackend) MevRunning() bool { return false }
|
||||
func (b *testBackend) MevParams() *types.MevParams {
|
||||
return &types.MevParams{}
|
||||
}
|
||||
func (b *testBackend) StartMev() {}
|
||||
func (b *testBackend) StopMev() {}
|
||||
func (b *testBackend) AddBuilder(builder common.Address, builderUrl string) error { return nil }
|
||||
func (b *testBackend) RemoveBuilder(builder common.Address) error { return nil }
|
||||
func (b *testBackend) SendBid(ctx context.Context, bid *types.BidArgs) (common.Hash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b *testBackend) MinerInTurn() bool { return false }
|
||||
func (b *testBackend) BestBidGasFee(parentHash common.Hash) *big.Int {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestEstimateGas(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
|
@ -101,6 +101,25 @@ type Backend interface {
|
||||
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
|
||||
SubscribeFinalizedHeaderEvent(ch chan<- core.FinalizedHeaderEvent) event.Subscription
|
||||
SubscribeNewVoteEvent(chan<- core.NewVoteEvent) event.Subscription
|
||||
|
||||
// MevRunning return true if mev is running
|
||||
MevRunning() bool
|
||||
// MevParams returns the static params of mev
|
||||
MevParams() *types.MevParams
|
||||
// StartMev starts mev
|
||||
StartMev()
|
||||
// StopMev stops mev
|
||||
StopMev()
|
||||
// AddBuilder adds a builder to the bid simulator.
|
||||
AddBuilder(builder common.Address, builderUrl string) error
|
||||
// RemoveBuilder removes a builder from the bid simulator.
|
||||
RemoveBuilder(builder common.Address) error
|
||||
// SendBid receives bid from the builders.
|
||||
SendBid(ctx context.Context, bid *types.BidArgs) (common.Hash, error)
|
||||
// BestBidGasFee returns the gas fee of the best bid for the given parent hash.
|
||||
BestBidGasFee(parentHash common.Hash) *big.Int
|
||||
// MinerInTurn returns true if the validator is in turn to propose the block.
|
||||
MinerInTurn() bool
|
||||
}
|
||||
|
||||
func GetAPIs(apiBackend Backend) []rpc.API {
|
||||
@ -127,6 +146,9 @@ func GetAPIs(apiBackend Backend) []rpc.API {
|
||||
}, {
|
||||
Namespace: "personal",
|
||||
Service: NewPersonalAccountAPI(apiBackend, nonceLock),
|
||||
}, {
|
||||
Namespace: "mev",
|
||||
Service: NewMevAPI(apiBackend),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -414,3 +414,19 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
||||
}
|
||||
|
||||
func (b *backendMock) Engine() consensus.Engine { return nil }
|
||||
|
||||
func (b *backendMock) MevRunning() bool { return false }
|
||||
func (b *backendMock) MevParams() *types.MevParams {
|
||||
return &types.MevParams{}
|
||||
}
|
||||
func (b *backendMock) StartMev() {}
|
||||
func (b *backendMock) StopMev() {}
|
||||
func (b *backendMock) AddBuilder(builder common.Address, builderUrl string) error { return nil }
|
||||
func (b *backendMock) RemoveBuilder(builder common.Address) error { return nil }
|
||||
func (b *backendMock) SendBid(ctx context.Context, bid *types.BidArgs) (common.Hash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b *backendMock) MinerInTurn() bool { return false }
|
||||
func (b *backendMock) BestBidGasFee(parentHash common.Hash) *big.Int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
688
miner/bid_simulator.go
Normal file
688
miner/bid_simulator.go
Normal file
@ -0,0 +1,688 @@
|
||||
package miner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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
|
||||
|
||||
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 (
|
||||
diffInTurn = big.NewInt(2) // the difficulty of a block that proposed by an in-turn validator
|
||||
)
|
||||
|
||||
var (
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
type WorkPreparer interface {
|
||||
prepareWork(params *generateParams) (*environment, error)
|
||||
etherbase() common.Address
|
||||
}
|
||||
|
||||
// simBidReq is the request for simulating a bid
|
||||
type simBidReq struct {
|
||||
bid *BidRuntime
|
||||
interruptCh chan int32
|
||||
}
|
||||
|
||||
// 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
|
||||
chain *core.BlockChain
|
||||
chainConfig *params.ChainConfig
|
||||
workPreparer WorkPreparer
|
||||
|
||||
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 *types.Bid
|
||||
|
||||
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,
|
||||
chainConfig *params.ChainConfig,
|
||||
chain *core.BlockChain,
|
||||
workPreparer WorkPreparer,
|
||||
) *bidSimulator {
|
||||
b := &bidSimulator{
|
||||
config: config,
|
||||
delayLeftOver: delayLeftOver,
|
||||
chainConfig: chainConfig,
|
||||
chain: chain,
|
||||
workPreparer: workPreparer,
|
||||
exitCh: make(chan struct{}),
|
||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
||||
builders: make(map[common.Address]*builderclient.Client),
|
||||
simBidCh: make(chan *simBidReq),
|
||||
newBidCh: make(chan *types.Bid, 100),
|
||||
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 = chain.SubscribeChainHeadEvent(b.chainHeadCh)
|
||||
|
||||
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()
|
||||
|
||||
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 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 {
|
||||
return
|
||||
}
|
||||
|
||||
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}:
|
||||
case <-b.exitCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case newBid := <-b.newBidCh:
|
||||
if !b.isRunning() {
|
||||
continue
|
||||
}
|
||||
|
||||
// check the block reward and validator reward of the newBid
|
||||
expectedBlockReward := newBid.GasFee
|
||||
expectedValidatorReward := new(big.Int).Mul(expectedBlockReward, big.NewInt(int64(b.config.ValidatorCommission)))
|
||||
expectedValidatorReward.Div(expectedValidatorReward, big.NewInt(10000))
|
||||
expectedValidatorReward.Sub(expectedValidatorReward, newBid.BuilderFee)
|
||||
|
||||
if expectedValidatorReward.Cmp(big.NewInt(0)) < 0 {
|
||||
// damage self profit, ignore
|
||||
continue
|
||||
}
|
||||
|
||||
bidRuntime := &BidRuntime{
|
||||
bid: newBid,
|
||||
expectedBlockReward: expectedBlockReward,
|
||||
expectedValidatorReward: expectedValidatorReward,
|
||||
packedBlockReward: big.NewInt(0),
|
||||
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 {
|
||||
// bestBid is nil means bid is the first bid
|
||||
bestBid := b.GetBestBid(newBid.ParentHash)
|
||||
if bestBid == nil {
|
||||
commit(commitInterruptBetterBid, bidRuntime)
|
||||
continue
|
||||
}
|
||||
|
||||
// if bestBid is not nil, check if newBid is better than bestBid
|
||||
if bidRuntime.expectedBlockReward.Cmp(bestBid.expectedBlockReward) > 0 &&
|
||||
bidRuntime.expectedValidatorReward.Cmp(bestBid.expectedValidatorReward) > 0 {
|
||||
// if both reward are better than last simulating newBid, commit for simulation
|
||||
commit(commitInterruptBetterBid, bidRuntime)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// simulatingBid must be better than bestBid, if newBid is better than simulatingBid, commit for simulation
|
||||
if bidRuntime.expectedBlockReward.Cmp(simulatingBid.expectedBlockReward) > 0 &&
|
||||
bidRuntime.expectedValidatorReward.Cmp(simulatingBid.expectedValidatorReward) > 0 {
|
||||
// if both reward are better than last simulating newBid, commit for simulation
|
||||
commit(commitInterruptBetterBid, bidRuntime)
|
||||
continue
|
||||
}
|
||||
|
||||
case <-b.exitCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bidSimulator) bidMustBefore(parentHash common.Hash) time.Time {
|
||||
parentHeader := b.chain.GetHeaderByHash(parentHash)
|
||||
return bidutil.BidMustBefore(parentHeader, b.chainConfig.Parlia.Period, b.delayLeftOver)
|
||||
}
|
||||
|
||||
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-core.TriesInMemory {
|
||||
v.env.discard()
|
||||
delete(b.bestBid, k)
|
||||
}
|
||||
}
|
||||
b.bestBidMu.Unlock()
|
||||
|
||||
b.simBidMu.Lock()
|
||||
if bid, ok := b.simulatingBid[parentHash]; ok {
|
||||
bid.env.discard()
|
||||
}
|
||||
delete(b.simulatingBid, parentHash)
|
||||
for k, v := range b.simulatingBid {
|
||||
if v.bid.BlockNumber <= blockNumber-core.TriesInMemory {
|
||||
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()
|
||||
select {
|
||||
case b.newBidCh <- bid:
|
||||
b.AddPending(bid.BlockNumber, bid.Builder, bid.Hash())
|
||||
return nil
|
||||
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 (
|
||||
blockNumber = bidRuntime.bid.BlockNumber
|
||||
parentHash = bidRuntime.bid.ParentHash
|
||||
builder = bidRuntime.bid.Builder
|
||||
err error
|
||||
success bool
|
||||
)
|
||||
|
||||
// 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)
|
||||
log.Debug("bid simulation failed", logCtx...)
|
||||
|
||||
go b.reportIssue(bidRuntime, err)
|
||||
}
|
||||
|
||||
if success {
|
||||
bidRuntime.duration = time.Since(simStart)
|
||||
}
|
||||
|
||||
b.RemoveSimulatingBid(parentHash)
|
||||
}(time.Now())
|
||||
|
||||
// prepareWork will configure header with a suitable time according to consensus
|
||||
// prepareWork will start trie prefetching
|
||||
if bidRuntime.env, err = b.workPreparer.prepareWork(&generateParams{
|
||||
parentHash: bidRuntime.bid.ParentHash,
|
||||
coinbase: b.workPreparer.etherbase(),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gasLimit := bidRuntime.env.header.GasLimit
|
||||
if bidRuntime.env.gasPool == nil {
|
||||
bidRuntime.env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
||||
bidRuntime.env.gasPool.SubGas(params.SystemTxsGas)
|
||||
}
|
||||
|
||||
if bidRuntime.bid.GasUsed > bidRuntime.env.gasPool.Gas() {
|
||||
err = errors.New("gas used exceeds gas limit")
|
||||
return
|
||||
}
|
||||
|
||||
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:
|
||||
}
|
||||
|
||||
// Start executing the transaction
|
||||
bidRuntime.env.state.SetTxContext(tx.Hash(), bidRuntime.env.tcount)
|
||||
|
||||
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)
|
||||
|
||||
// return if bid is invalid, reportIssue issue to mev-sentry/builder if simulation is fully done
|
||||
if !bidRuntime.validReward() {
|
||||
err = errors.New("reward does not achieve the expectation")
|
||||
return
|
||||
}
|
||||
|
||||
bestBid := b.GetBestBid(parentHash)
|
||||
|
||||
if bestBid == nil {
|
||||
b.SetBestBid(bidRuntime.bid.ParentHash, bidRuntime)
|
||||
success = true
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// reportIssue reports the issue to the mev-sentry
|
||||
func (b *bidSimulator) reportIssue(bidRuntime *BidRuntime, err error) {
|
||||
cli := b.builders[bidRuntime.bid.Builder]
|
||||
if cli != nil {
|
||||
cli.ReportIssue(context.Background(), &types.BidIssue{
|
||||
Validator: bidRuntime.env.header.Coinbase,
|
||||
Builder: bidRuntime.bid.Builder,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type BidRuntime struct {
|
||||
bid *types.Bid
|
||||
|
||||
env *environment
|
||||
|
||||
expectedBlockReward *big.Int
|
||||
expectedValidatorReward *big.Int
|
||||
|
||||
packedBlockReward *big.Int
|
||||
packedValidatorReward *big.Int
|
||||
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func (r *BidRuntime) validReward() bool {
|
||||
return r.packedBlockReward.Cmp(r.expectedBlockReward) >= 0 &&
|
||||
r.packedValidatorReward.Cmp(r.expectedValidatorReward) >= 0
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
var (
|
||||
env = r.env
|
||||
snap = env.state.Snapshot()
|
||||
gp = env.gasPool.Gas()
|
||||
sc *types.BlobTxSidecar
|
||||
)
|
||||
|
||||
if tx.Type() == types.BlobTxType {
|
||||
sc := tx.BlobTxSidecar()
|
||||
if sc == nil {
|
||||
return errors.New("blob transaction without blobs in miner")
|
||||
}
|
||||
// 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 {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
env.gasPool.SetGas(gp)
|
||||
return err
|
||||
}
|
||||
|
||||
if tx.Type() == types.BlobTxType {
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
33
miner/builderclient/builderclient.go
Normal file
33
miner/builderclient/builderclient.go
Normal file
@ -0,0 +1,33 @@
|
||||
package builderclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Client defines typed wrappers for the Ethereum RPC API.
|
||||
type Client struct {
|
||||
c *rpc.Client
|
||||
}
|
||||
|
||||
// DialOptions creates a new RPC client for the given URL. You can supply any of the
|
||||
// pre-defined client options to configure the underlying transport.
|
||||
func DialOptions(ctx context.Context, rawurl string, opts ...rpc.ClientOption) (*Client, error) {
|
||||
c, err := rpc.DialOptions(ctx, rawurl, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newClient(c), nil
|
||||
}
|
||||
|
||||
// newClient creates a client that uses the given RPC client.
|
||||
func newClient(c *rpc.Client) *Client {
|
||||
return &Client{c}
|
||||
}
|
||||
|
||||
// ReportIssue reports an issue
|
||||
func (ec *Client) ReportIssue(ctx context.Context, args *types.BidIssue) error {
|
||||
return ec.c.CallContext(ctx, nil, "mev_reportIssue", args)
|
||||
}
|
@ -56,6 +56,8 @@ type Config struct {
|
||||
|
||||
NewPayloadTimeout time.Duration // The maximum time allowance for creating a new payload
|
||||
DisableVoteAttestation bool // Whether to skip assembling vote attestation
|
||||
|
||||
Mev MevConfig // Mev configuration
|
||||
}
|
||||
|
||||
// DefaultConfig contains default settings for miner.
|
||||
@ -70,6 +72,8 @@ var DefaultConfig = Config{
|
||||
Recommit: 3 * time.Second,
|
||||
NewPayloadTimeout: 2 * time.Second,
|
||||
DelayLeftOver: 50 * time.Millisecond,
|
||||
|
||||
Mev: DefaultMevConfig,
|
||||
}
|
||||
|
||||
// Miner creates blocks and searches for proof-of-work values.
|
||||
@ -82,6 +86,8 @@ type Miner struct {
|
||||
stopCh chan struct{}
|
||||
worker *worker
|
||||
|
||||
bidSimulator *bidSimulator
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
@ -95,6 +101,10 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
|
||||
stopCh: make(chan struct{}),
|
||||
worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, false),
|
||||
}
|
||||
|
||||
miner.bidSimulator = newBidSimulator(&config.Mev, config.DelayLeftOver, chainConfig, eth.BlockChain(), miner.worker)
|
||||
miner.worker.setBestBidFetcher(miner.bidSimulator)
|
||||
|
||||
miner.wg.Add(1)
|
||||
go miner.update()
|
||||
return miner
|
||||
@ -129,6 +139,7 @@ func (miner *Miner) update() {
|
||||
case downloader.StartEvent:
|
||||
wasMining := miner.Mining()
|
||||
miner.worker.stop()
|
||||
miner.bidSimulator.stop()
|
||||
canStart = false
|
||||
if wasMining {
|
||||
// Resume mining after sync was finished
|
||||
@ -141,6 +152,7 @@ func (miner *Miner) update() {
|
||||
canStart = true
|
||||
if shouldStart {
|
||||
miner.worker.start()
|
||||
miner.bidSimulator.start()
|
||||
}
|
||||
miner.worker.syncing.Store(false)
|
||||
|
||||
@ -148,6 +160,7 @@ func (miner *Miner) update() {
|
||||
canStart = true
|
||||
if shouldStart {
|
||||
miner.worker.start()
|
||||
miner.bidSimulator.start()
|
||||
}
|
||||
miner.worker.syncing.Store(false)
|
||||
|
||||
@ -157,13 +170,16 @@ func (miner *Miner) update() {
|
||||
case <-miner.startCh:
|
||||
if canStart {
|
||||
miner.worker.start()
|
||||
miner.bidSimulator.start()
|
||||
}
|
||||
shouldStart = true
|
||||
case <-miner.stopCh:
|
||||
shouldStart = false
|
||||
miner.worker.stop()
|
||||
miner.bidSimulator.stop()
|
||||
case <-miner.exitCh:
|
||||
miner.worker.close()
|
||||
miner.bidSimulator.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -186,6 +202,10 @@ func (miner *Miner) Mining() bool {
|
||||
return miner.worker.isRunning()
|
||||
}
|
||||
|
||||
func (miner *Miner) InTurn() bool {
|
||||
return miner.worker.inTurn()
|
||||
}
|
||||
|
||||
func (miner *Miner) Hashrate() uint64 {
|
||||
if pow, ok := miner.engine.(consensus.PoW); ok {
|
||||
return uint64(pow.Hashrate())
|
||||
|
111
miner/miner_mev.go
Normal file
111
miner/miner_mev.go
Normal file
@ -0,0 +1,111 @@
|
||||
package miner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
type BuilderConfig struct {
|
||||
Address common.Address
|
||||
URL string
|
||||
}
|
||||
|
||||
type MevConfig struct {
|
||||
Enabled bool // Whether to enable Mev or not
|
||||
SentryURL string // The url of Mev sentry
|
||||
Builders []BuilderConfig // The list of builders
|
||||
ValidatorCommission uint64 // 100 means 1%
|
||||
BidSimulationLeftOver time.Duration
|
||||
}
|
||||
|
||||
var DefaultMevConfig = MevConfig{
|
||||
Enabled: false,
|
||||
SentryURL: "",
|
||||
Builders: nil,
|
||||
ValidatorCommission: 100,
|
||||
BidSimulationLeftOver: 50 * time.Millisecond,
|
||||
}
|
||||
|
||||
// MevRunning return true if mev is running.
|
||||
func (miner *Miner) MevRunning() bool {
|
||||
return miner.bidSimulator.isRunning() && miner.bidSimulator.receivingBid()
|
||||
}
|
||||
|
||||
// StartMev starts mev.
|
||||
func (miner *Miner) StartMev() {
|
||||
miner.bidSimulator.startReceivingBid()
|
||||
}
|
||||
|
||||
// StopMev stops mev.
|
||||
func (miner *Miner) StopMev() {
|
||||
miner.bidSimulator.stopReceivingBid()
|
||||
}
|
||||
|
||||
// AddBuilder adds a builder to the bid simulator.
|
||||
func (miner *Miner) AddBuilder(builder common.Address, url string) error {
|
||||
return miner.bidSimulator.AddBuilder(builder, url)
|
||||
}
|
||||
|
||||
// RemoveBuilder removes a builder from the bid simulator.
|
||||
func (miner *Miner) RemoveBuilder(builderAddr common.Address) error {
|
||||
return miner.bidSimulator.RemoveBuilder(builderAddr)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
if !miner.bidSimulator.ExistBuilder(builder) {
|
||||
return common.Hash{}, types.NewInvalidBidError("builder is not registered")
|
||||
}
|
||||
|
||||
err = miner.bidSimulator.CheckPending(bidArgs.RawBid.BlockNumber, builder, bidArgs.RawBid.Hash())
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
bidBetterBefore := miner.bidSimulator.bidBetterBefore(bidArgs.RawBid.ParentHash)
|
||||
timeout := time.Until(bidBetterBefore)
|
||||
|
||||
if timeout <= 0 {
|
||||
return common.Hash{}, fmt.Errorf("too late, expected befor %s, appeared %s later", bidBetterBefore,
|
||||
common.PrettyDuration(timeout))
|
||||
}
|
||||
|
||||
err = miner.bidSimulator.sendBid(ctx, bid)
|
||||
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
return bid.Hash(), nil
|
||||
}
|
||||
|
||||
func (miner *Miner) BestPackedBlockReward(parentHash common.Hash) *big.Int {
|
||||
bidRuntime := miner.bidSimulator.GetBestBid(parentHash)
|
||||
if bidRuntime == nil {
|
||||
return big.NewInt(0)
|
||||
}
|
||||
|
||||
return bidRuntime.packedBlockReward
|
||||
}
|
||||
|
||||
func (miner *Miner) MevParams() *types.MevParams {
|
||||
return &types.MevParams{
|
||||
ValidatorCommission: miner.worker.config.Mev.ValidatorCommission,
|
||||
BidSimulationLeftOver: miner.worker.config.Mev.BidSimulationLeftOver,
|
||||
}
|
||||
}
|
@ -24,6 +24,9 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/holiman/uint256"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
@ -40,8 +43,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -162,9 +163,14 @@ type getWorkReq struct {
|
||||
result chan *newPayloadResult // non-blocking channel
|
||||
}
|
||||
|
||||
type bidFetcher interface {
|
||||
GetBestBid(parentHash common.Hash) *BidRuntime
|
||||
}
|
||||
|
||||
// worker is the main object which takes care of submitting new work to consensus engine
|
||||
// and gathering the sealing result.
|
||||
type worker struct {
|
||||
bidFetcher bidFetcher
|
||||
prefetcher core.Prefetcher
|
||||
config *Config
|
||||
chainConfig *params.ChainConfig
|
||||
@ -287,9 +293,14 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
|
||||
if init {
|
||||
worker.startCh <- struct{}{}
|
||||
}
|
||||
|
||||
return worker
|
||||
}
|
||||
|
||||
func (w *worker) setBestBidFetcher(fetcher bidFetcher) {
|
||||
w.bidFetcher = fetcher
|
||||
}
|
||||
|
||||
// setEtherbase sets the etherbase used to initialize the block coinbase field.
|
||||
func (w *worker) setEtherbase(addr common.Address) {
|
||||
w.mu.Lock()
|
||||
@ -1218,6 +1229,24 @@ LOOP:
|
||||
bestReward = balance
|
||||
}
|
||||
}
|
||||
|
||||
// when out-turn, use bestWork to prevent bundle leakage.
|
||||
// when in-turn, compare with remote work.
|
||||
if w.bidFetcher != nil && bestWork.header.Difficulty.Cmp(diffInTurn) == 0 {
|
||||
bestBid := w.bidFetcher.GetBestBid(bestWork.header.ParentHash)
|
||||
|
||||
if bestBid != nil && bestReward.CmpBig(bestBid.packedBlockReward) < 0 {
|
||||
// localValidatorReward is the reward for the validator self by the local block.
|
||||
localValidatorReward := new(uint256.Int).Mul(bestReward, uint256.NewInt(w.config.Mev.ValidatorCommission))
|
||||
localValidatorReward.Div(localValidatorReward, uint256.NewInt(10000))
|
||||
|
||||
// blockReward(benefits delegators) and validatorReward(benefits the validator) are both optimal
|
||||
if localValidatorReward.CmpBig(bestBid.packedValidatorReward) < 0 {
|
||||
bestWork = bestBid.env
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.commit(bestWork, w.fullTaskHook, true, start)
|
||||
|
||||
// Swap out the old work with the new one, terminating any leftover
|
||||
@ -1228,6 +1257,12 @@ LOOP:
|
||||
w.current = bestWork
|
||||
}
|
||||
|
||||
// inTurn return true if the current worker is in turn.
|
||||
func (w *worker) inTurn() bool {
|
||||
validator, _ := w.engine.NextInTurnValidator(w.chain, w.chain.CurrentBlock())
|
||||
return validator != common.Address{} && validator == w.etherbase()
|
||||
}
|
||||
|
||||
// commit runs any post-transaction state modifications, assembles the final block
|
||||
// and commits new work if consensus engine is running.
|
||||
// Note the assumption is held that the mutation is allowed to the passed env, do
|
||||
|
Loading…
Reference in New Issue
Block a user