Merge branch 'develop' into cancun_code_merge_v1.13.12_v1.13.14

This commit is contained in:
buddh0 2024-03-11 15:16:24 +08:00
commit 270793f13a
75 changed files with 1915 additions and 150 deletions

@ -5,6 +5,7 @@ package main
import (
"bytes"
"encoding/hex"
"errors"
"flag"
"fmt"
"os"
@ -78,7 +79,7 @@ func parseExtra(hexData string) (*Extra, error) {
// decode hex into bytes
data, err := hex.DecodeString(strings.TrimPrefix(hexData, "0x"))
if err != nil {
return nil, fmt.Errorf("invalid hex data")
return nil, errors.New("invalid hex data")
}
// parse ExtraVanity and ExtraSeal
@ -99,7 +100,7 @@ func parseExtra(hexData string) (*Extra, error) {
validatorNum := int(data[0])
validatorBytesTotalLength := validatorNumberSize + validatorNum*validatorBytesLength
if dataLength < validatorBytesTotalLength {
return nil, fmt.Errorf("parse validators failed")
return nil, errors.New("parse validators failed")
}
extra.ValidatorSize = uint8(validatorNum)
data = data[validatorNumberSize:]
@ -117,7 +118,7 @@ func parseExtra(hexData string) (*Extra, error) {
// parse Vote Attestation
if dataLength > 0 {
if err := rlp.Decode(bytes.NewReader(data), &extra.VoteAttestation); err != nil {
return nil, fmt.Errorf("parse voteAttestation failed")
return nil, errors.New("parse voteAttestation failed")
}
if extra.ValidatorSize > 0 {
validatorsBitSet := bitset.From([]uint64{uint64(extra.VoteAddressSet)})

@ -266,6 +266,15 @@ func initGenesis(ctx *cli.Context) error {
}
defer chaindb.Close()
// if the trie data dir has been set, new trie db with a new state database
if ctx.IsSet(utils.SeparateDBFlag.Name) {
statediskdb, dbErr := stack.OpenDatabaseWithFreezer(name+"/state", 0, 0, "", "", false, false, false, false)
if dbErr != nil {
utils.Fatalf("Failed to open separate trie database: %v", dbErr)
}
chaindb.SetStateStore(statediskdb)
}
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close()
@ -717,7 +726,6 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth
}
db := utils.MakeChainDatabase(ctx, stack, true, false)
defer db.Close()
scheme, err := rawdb.ParseStateScheme(ctx.String(utils.StateSchemeFlag.Name), db)
if err != nil {
return nil, nil, common.Hash{}, err
@ -726,7 +734,7 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth
fmt.Println("You are using geth dump in path mode, please use `geth dump-roothash` command to get all available blocks.")
}
var header *types.Header
header := &types.Header{}
if ctx.NArg() == 1 {
arg := ctx.Args().First()
if hashish(arg) {
@ -755,7 +763,7 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth
if stateRoot := triedb.Head(); stateRoot != (common.Hash{}) {
header.Root = stateRoot
} else {
return nil, nil, common.Hash{}, fmt.Errorf("no top state root hash in path db")
return nil, nil, common.Hash{}, errors.New("no top state root hash in path db")
}
} else {
header = rawdb.ReadHeadHeader(db)

@ -203,6 +203,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
v := ctx.Uint64(utils.OverrideFeynman.Name)
cfg.Eth.OverrideFeynman = &v
}
if ctx.IsSet(utils.SeparateDBFlag.Name) && !stack.IsSeparatedDB() {
utils.Fatalf("Failed to locate separate database subdirectory when separatedb parameter has been set")
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information

@ -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"
)

@ -18,6 +18,7 @@ package main
import (
"bytes"
"errors"
"fmt"
"math"
"os"
@ -382,7 +383,6 @@ func inspectTrie(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, true, false)
defer db.Close()
var headerBlockHash common.Hash
if ctx.NArg() >= 1 {
if ctx.Args().Get(0) == "latest" {
@ -412,7 +412,7 @@ func inspectTrie(ctx *cli.Context) error {
if blockNumber != math.MaxUint64 {
headerBlockHash = rawdb.ReadCanonicalHash(db, blockNumber)
if headerBlockHash == (common.Hash{}) {
return fmt.Errorf("ReadHeadBlockHash empry hash")
return errors.New("ReadHeadBlockHash empry hash")
}
blockHeader := rawdb.ReadHeader(db, headerBlockHash, blockNumber)
trieRootHash = blockHeader.Root
@ -563,6 +563,11 @@ func dbStats(ctx *cli.Context) error {
defer db.Close()
showLeveldbStats(db)
if db.StateStore() != nil {
fmt.Println("show stats of state store")
showLeveldbStats(db.StateStore())
}
return nil
}
@ -576,13 +581,31 @@ func dbCompact(ctx *cli.Context) error {
log.Info("Stats before compaction")
showLeveldbStats(db)
statediskdb := db.StateStore()
if statediskdb != nil {
fmt.Println("show stats of state store")
showLeveldbStats(statediskdb)
}
log.Info("Triggering compaction")
if err := db.Compact(nil, nil); err != nil {
log.Info("Compact err", "error", err)
log.Error("Compact err", "error", err)
return err
}
if statediskdb != nil {
if err := statediskdb.Compact(nil, nil); err != nil {
log.Error("Compact err", "error", err)
return err
}
}
log.Info("Stats after compaction")
showLeveldbStats(db)
if statediskdb != nil {
fmt.Println("show stats of state store after compaction")
showLeveldbStats(statediskdb)
}
return nil
}
@ -603,8 +626,17 @@ func dbGet(ctx *cli.Context) error {
return err
}
statediskdb := db.StateStore()
data, err := db.Get(key)
if err != nil {
// if separate trie db exist, try to get it from separate db
if statediskdb != nil {
statedata, dberr := statediskdb.Get(key)
if dberr == nil {
fmt.Printf("key %#x: %#x\n", key, statedata)
return nil
}
}
log.Info("Get operation failed", "key", fmt.Sprintf("%#x", key), "error", err)
return err
}
@ -620,8 +652,14 @@ func dbTrieGet(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, false, false)
defer db.Close()
var db ethdb.Database
chaindb := utils.MakeChainDatabase(ctx, stack, true, false)
if chaindb.StateStore() != nil {
db = chaindb.StateStore()
} else {
db = chaindb
}
defer chaindb.Close()
scheme := ctx.String(utils.StateSchemeFlag.Name)
if scheme == "" {
@ -686,8 +724,14 @@ func dbTrieDelete(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, false, false)
defer db.Close()
var db ethdb.Database
chaindb := utils.MakeChainDatabase(ctx, stack, true, false)
if chaindb.StateStore() != nil {
db = chaindb.StateStore()
} else {
db = chaindb
}
defer chaindb.Close()
scheme := ctx.String(utils.StateSchemeFlag.Name)
if scheme == "" {
@ -1077,10 +1121,16 @@ func hbss2pbss(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, false, false)
db.Sync()
stateDiskDb := db.StateStore()
defer db.Close()
// convert hbss trie node to pbss trie node
lastStateID := rawdb.ReadPersistentStateID(db)
var lastStateID uint64
if stateDiskDb != nil {
lastStateID = rawdb.ReadPersistentStateID(stateDiskDb)
} else {
lastStateID = rawdb.ReadPersistentStateID(db)
}
if lastStateID == 0 || force {
config := triedb.HashDefaults
triedb := triedb.NewDatabase(db, config)
@ -1103,7 +1153,7 @@ func hbss2pbss(ctx *cli.Context) error {
if *blockNumber != math.MaxUint64 {
headerBlockHash = rawdb.ReadCanonicalHash(db, *blockNumber)
if headerBlockHash == (common.Hash{}) {
return fmt.Errorf("ReadHeadBlockHash empty hash")
return errors.New("ReadHeadBlockHash empty hash")
}
blockHeader := rawdb.ReadHeader(db, headerBlockHash, *blockNumber)
trieRootHash = blockHeader.Root
@ -1111,7 +1161,7 @@ func hbss2pbss(ctx *cli.Context) error {
}
if (trieRootHash == common.Hash{}) {
log.Error("Empty root hash")
return fmt.Errorf("Empty root hash.")
return errors.New("Empty root hash.")
}
id := trie.StateTrieID(trieRootHash)
@ -1132,18 +1182,34 @@ func hbss2pbss(ctx *cli.Context) error {
}
// repair state ancient offset
if stateDiskDb != nil {
lastStateID = rawdb.ReadPersistentStateID(stateDiskDb)
} else {
lastStateID = rawdb.ReadPersistentStateID(db)
}
if lastStateID == 0 {
log.Error("Convert hbss to pbss trie node error. The last state id is still 0")
}
ancient := stack.ResolveAncient("chaindata", ctx.String(utils.AncientFlag.Name))
var ancient string
if db.StateStore() != nil {
dirName := filepath.Join(stack.ResolvePath("chaindata"), "state")
ancient = filepath.Join(dirName, "ancient")
} else {
ancient = stack.ResolveAncient("chaindata", ctx.String(utils.AncientFlag.Name))
}
err = rawdb.ResetStateFreezerTableOffset(ancient, lastStateID)
if err != nil {
log.Error("Reset state freezer table offset failed", "error", err)
return err
}
// prune hbss trie node
if stateDiskDb != nil {
err = rawdb.PruneHashTrieNodeInDataBase(stateDiskDb)
} else {
err = rawdb.PruneHashTrieNodeInDataBase(db)
}
if err != nil {
log.Error("Prune Hash trie node in database failed", "error", err)
return err

BIN
cmd/geth/geth Executable file

Binary file not shown.

@ -437,13 +437,15 @@ func pruneState(ctx *cli.Context) error {
chaindb := utils.MakeChainDatabase(ctx, stack, false, false)
defer chaindb.Close()
if rawdb.ReadStateScheme(chaindb) != rawdb.HashScheme {
log.Crit("Offline pruning is not required for path scheme")
}
prunerconfig := pruner.Config{
Datadir: stack.ResolvePath(""),
BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name),
}
if rawdb.ReadStateScheme(chaindb) != rawdb.HashScheme {
log.Crit("Offline pruning is not required for path scheme")
}
pruner, err := pruner.NewPruner(chaindb, prunerconfig, ctx.Uint64(utils.TriesInMemoryFlag.Name))
if err != nil {
log.Error("Failed to open snapshot tree", "err", err)

@ -93,6 +93,12 @@ var (
Value: flags.DirectoryString(node.DefaultDataDir()),
Category: flags.EthCategory,
}
SeparateDBFlag = &cli.BoolFlag{
Name: "separatedb",
Usage: "Enable a separated trie database, it will be created within a subdirectory called state, " +
"Users can copy this state directory to another directory or disk, and then create a symbolic link to the state directory under the chaindata",
Category: flags.EthCategory,
}
DirectBroadcastFlag = &cli.BoolFlag{
Name: "directbroadcast",
Usage: "Enable directly broadcast mined block to all peers",
@ -1112,6 +1118,7 @@ var (
DBEngineFlag,
StateSchemeFlag,
HttpHeaderFlag,
SeparateDBFlag,
}
)
@ -2314,6 +2321,11 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFree
chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly)
default:
chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "", readonly, disableFreeze, false, false)
// set the separate state database
if stack.IsSeparatedDB() && err == nil {
stateDiskDb := MakeStateDataBase(ctx, stack, readonly, false)
chainDb.SetStateStore(stateDiskDb)
}
}
if err != nil {
Fatalf("Could not open database: %v", err)
@ -2321,6 +2333,17 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFree
return chainDb
}
// MakeStateDataBase open a separate state database using the flags passed to the client and will hard crash if it fails.
func MakeStateDataBase(ctx *cli.Context, stack *node.Node, readonly, disableFreeze bool) ethdb.Database {
cache := ctx.Int(CacheFlag.Name) * ctx.Int(CacheDatabaseFlag.Name) / 100
handles := MakeDatabaseHandles(ctx.Int(FDLimitFlag.Name)) / 2
statediskdb, err := stack.OpenDatabaseWithFreezer("chaindata/state", cache, handles, "", "", readonly, disableFreeze, false, false)
if err != nil {
Fatalf("Failed to open separate trie database: %v", err)
}
return statediskdb
}
// tryMakeReadOnlyDatabase try to open the chain database in read-only mode,
// or fallback to write mode if the database is not initialized.
//

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 {

@ -3,7 +3,7 @@ package parlia
import (
"container/heap"
"context"
"fmt"
"errors"
"math"
"math/big"
@ -159,7 +159,7 @@ func (p *Parlia) getValidatorElectionInfo(blockNr rpc.BlockNumberOrHash) ([]Vali
return nil, err
}
if totalLength.Int64() != int64(len(validators)) || totalLength.Int64() != int64(len(votingPowers)) || totalLength.Int64() != int64(len(voteAddrs)) {
return nil, fmt.Errorf("validator length not match")
return nil, errors.New("validator length not match")
}
validatorItems := make([]ValidatorItem, len(validators))

@ -435,7 +435,7 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
return nil
}
if attestation.Data == nil {
return fmt.Errorf("invalid attestation, vote data is nil")
return errors.New("invalid attestation, vote data is nil")
}
if len(attestation.Extra) > types.MaxAttestationExtraLength {
return fmt.Errorf("invalid attestation, too large extra length: %d", len(attestation.Extra))
@ -464,7 +464,7 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
}
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, headers)
if err != nil {
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
return errors.New("unexpected error when getting the highest justified number and hash")
}
if sourceNumber != justifiedBlockNumber || sourceHash != justifiedBlockHash {
return fmt.Errorf("invalid attestation, source mismatch, expected block: %d, hash: %s; real block: %d, hash: %s",
@ -486,7 +486,7 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
validators := snap.validators()
validatorsBitSet := bitset.From([]uint64{uint64(attestation.VoteAddressSet)})
if validatorsBitSet.Count() > uint(len(validators)) {
return fmt.Errorf("invalid attestation, vote number larger than validators number")
return errors.New("invalid attestation, vote number larger than validators number")
}
votedAddrs := make([]bls.PublicKey, 0, validatorsBitSet.Count())
for index, val := range validators {
@ -503,7 +503,7 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
// The valid voted validators should be no less than 2/3 validators.
if len(votedAddrs) < cmath.CeilDiv(len(snap.Validators)*2, 3) {
return fmt.Errorf("invalid attestation, not enough validators voted")
return errors.New("invalid attestation, not enough validators voted")
}
// Verify the aggregated signature.
@ -512,7 +512,7 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
return fmt.Errorf("BLS signature converts failed: %v", err)
}
if !aggSig.FastAggregateVerify(votedAddrs, attestation.Data.Hash()) {
return fmt.Errorf("invalid attestation, signature verify failed")
return errors.New("invalid attestation, signature verify failed")
}
return nil
@ -605,18 +605,6 @@ func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
}
}
if !cancun && header.ExcessBlobGas != nil {
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
}
if !cancun && header.BlobGasUsed != nil {
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
}
if cancun {
if err := eip4844.VerifyEIP4844Header(parent, header); err != nil {
return err
}
}
// All basic checks passed, verify cascading fields
return p.verifyCascadingFields(chain, header, parents)
}
@ -904,7 +892,7 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head
// Prepare vote data
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{parent})
if err != nil {
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
return errors.New("unexpected error when getting the highest justified number and hash")
}
attestation := &types.VoteAttestation{
Data: &types.VoteData{
@ -941,7 +929,7 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head
validatorsBitSet := bitset.From([]uint64{uint64(attestation.VoteAddressSet)})
if validatorsBitSet.Count() < uint(len(signatures)) {
log.Warn(fmt.Sprintf("assembleVoteAttestation, check VoteAddress Set failed, expected:%d, real:%d", len(signatures), validatorsBitSet.Count()))
return fmt.Errorf("invalid attestation, check VoteAddress Set failed")
return errors.New("invalid attestation, check VoteAddress Set failed")
}
// Append attestation to header extra field.
@ -960,6 +948,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 {
@ -1344,27 +1342,27 @@ func (p *Parlia) VerifyVote(chain consensus.ChainHeaderReader, vote *types.VoteE
header := chain.GetHeaderByHash(targetHash)
if header == nil {
log.Warn("BlockHeader at current voteBlockNumber is nil", "targetNumber", targetNumber, "targetHash", targetHash)
return fmt.Errorf("BlockHeader at current voteBlockNumber is nil")
return errors.New("BlockHeader at current voteBlockNumber is nil")
}
if header.Number.Uint64() != targetNumber {
log.Warn("unexpected target number", "expect", header.Number.Uint64(), "real", targetNumber)
return fmt.Errorf("target number mismatch")
return errors.New("target number mismatch")
}
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{header})
if err != nil {
log.Error("failed to get the highest justified number and hash", "headerNumber", header.Number, "headerHash", header.Hash())
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
return errors.New("unexpected error when getting the highest justified number and hash")
}
if vote.Data.SourceNumber != justifiedBlockNumber || vote.Data.SourceHash != justifiedBlockHash {
return fmt.Errorf("vote source block mismatch")
return errors.New("vote source block mismatch")
}
number := header.Number.Uint64()
snap, err := p.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
log.Error("failed to get the snapshot from consensus", "error", err)
return fmt.Errorf("failed to get the snapshot from consensus")
return errors.New("failed to get the snapshot from consensus")
}
validators := snap.Validators
@ -1379,7 +1377,7 @@ func (p *Parlia) VerifyVote(chain consensus.ChainHeaderReader, vote *types.VoteE
}
}
return fmt.Errorf("vote verification failed")
return errors.New("vote verification failed")
}
// Authorize injects a private key into the consensus engine to mint new blocks
@ -1838,7 +1836,7 @@ func (p *Parlia) applyTransaction(
// within the branch including `headers` and utilizing the latest element as the head.
func (p *Parlia) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) {
if chain == nil || len(headers) == 0 || headers[len(headers)-1] == nil {
return 0, common.Hash{}, fmt.Errorf("illegal chain or header")
return 0, common.Hash{}, errors.New("illegal chain or header")
}
head := headers[len(headers)-1]
snap, err := p.snapshot(chain, head.Number.Uint64(), head.Hash(), headers)

@ -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 {

@ -2961,7 +2961,7 @@ func (bc *BlockChain) GetTrustedDiffLayer(blockHash common.Hash) *types.DiffLaye
func CalculateDiffHash(d *types.DiffLayer) (common.Hash, error) {
if d == nil {
return common.Hash{}, fmt.Errorf("nil diff layer")
return common.Hash{}, errors.New("nil diff layer")
}
diff := &types.ExtDiffLayer{

@ -456,8 +456,10 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi
excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed)
header.ExcessBlobGas = &excessBlobGas
header.BlobGasUsed = new(uint64)
if cm.config.Parlia == nil {
header.ParentBeaconRoot = new(common.Hash)
}
}
return header
}

@ -452,7 +452,10 @@ func (g *Genesis) ToBlock() *types.Block {
// EIP-4788: The parentBeaconBlockRoot of the genesis block is always
// the zero hash. This is because the genesis block does not have a parent
// by definition.
if conf.Parlia == nil {
head.ParentBeaconRoot = new(common.Hash)
}
// EIP-4844 fields
head.ExcessBlobGas = g.ExcessBlobGas
head.BlobGasUsed = g.BlobGasUsed

@ -288,13 +288,13 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has
// if the state is not present in database.
func ReadStateScheme(db ethdb.Reader) string {
// Check if state in path-based scheme is present
blob, _ := ReadAccountTrieNode(db, nil)
blob, _ := ReadAccountTrieNode(db.StateStoreReader(), nil)
if len(blob) != 0 {
return PathScheme
}
// The root node might be deleted during the initial snap sync, check
// the persistent state id then.
if id := ReadPersistentStateID(db); id != 0 {
if id := ReadPersistentStateID(db.StateStoreReader()); id != 0 {
return PathScheme
}
// In a hash-based scheme, the genesis state is consistently stored
@ -304,7 +304,7 @@ func ReadStateScheme(db ethdb.Reader) string {
if header == nil {
return "" // empty datadir
}
blob = ReadLegacyTrieNode(db, header.Root)
blob = ReadLegacyTrieNode(db.StateStoreReader(), header.Root)
if len(blob) == 0 {
return "" // no state in disk
}

@ -91,7 +91,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
infos = append(infos, info)
case StateFreezerName:
if ReadStateScheme(db) != PathScheme {
if ReadStateScheme(db) != PathScheme || db.StateStore() != nil {
continue
}
datadir, err := db.AncientDatadir()

@ -42,6 +42,14 @@ type freezerdb struct {
ethdb.KeyValueStore
ethdb.AncientStore
diffStore ethdb.KeyValueStore
stateStore ethdb.Database
}
func (frdb *freezerdb) StateStoreReader() ethdb.Reader {
if frdb.stateStore == nil {
return frdb
}
return frdb.stateStore
}
// AncientDatadir returns the path of root ancient directory.
@ -64,6 +72,11 @@ func (frdb *freezerdb) Close() error {
errs = append(errs, err)
}
}
if frdb.stateStore != nil {
if err := frdb.stateStore.Close(); err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 {
return fmt.Errorf("%v", errs)
}
@ -81,6 +94,17 @@ func (frdb *freezerdb) SetDiffStore(diff ethdb.KeyValueStore) {
frdb.diffStore = diff
}
func (frdb *freezerdb) StateStore() ethdb.Database {
return frdb.stateStore
}
func (frdb *freezerdb) SetStateStore(state ethdb.Database) {
if frdb.stateStore != nil {
frdb.stateStore.Close()
}
frdb.stateStore = state
}
// Freeze is a helper method used for external testing to trigger and block until
// a freeze cycle completes, without having to sleep for a minute to trigger the
// automatic background run.
@ -105,6 +129,7 @@ func (frdb *freezerdb) Freeze(threshold uint64) error {
type nofreezedb struct {
ethdb.KeyValueStore
diffStore ethdb.KeyValueStore
stateStore ethdb.Database
}
// HasAncient returns an error as we don't have a backing chain freezer.
@ -170,6 +195,21 @@ func (db *nofreezedb) SetDiffStore(diff ethdb.KeyValueStore) {
db.diffStore = diff
}
func (db *nofreezedb) StateStore() ethdb.Database {
return db.stateStore
}
func (db *nofreezedb) SetStateStore(state ethdb.Database) {
db.stateStore = state
}
func (db *nofreezedb) StateStoreReader() ethdb.Reader {
if db.stateStore != nil {
return db.stateStore
}
return db
}
func (db *nofreezedb) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err error) {
// Unlike other ancient-related methods, this method does not return
// errNotSupported when invoked.
@ -609,6 +649,11 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
it := db.NewIterator(keyPrefix, keyStart)
defer it.Release()
var trieIter ethdb.Iterator
if db.StateStore() != nil {
trieIter = db.StateStore().NewIterator(keyPrefix, nil)
defer trieIter.Release()
}
var (
count int64
start = time.Now()
@ -659,14 +704,14 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
bodies.Add(size)
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
receipts.Add(size)
case IsLegacyTrieNode(key, it.Value()):
legacyTries.Add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
tds.Add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
numHashPairings.Add(size)
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
hashNumPairings.Add(size)
case IsLegacyTrieNode(key, it.Value()):
legacyTries.Add(size)
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
stateLookups.Add(size)
case IsAccountTrieNode(key):
@ -728,6 +773,46 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
logged = time.Now()
}
}
// inspect separate trie db
if trieIter != nil {
count = 0
logged = time.Now()
for trieIter.Next() {
var (
key = trieIter.Key()
value = trieIter.Value()
size = common.StorageSize(len(key) + len(value))
)
switch {
case IsLegacyTrieNode(key, value):
legacyTries.Add(size)
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
stateLookups.Add(size)
case IsAccountTrieNode(key):
accountTries.Add(size)
case IsStorageTrieNode(key):
storageTries.Add(size)
default:
var accounted bool
for _, meta := range [][]byte{
fastTrieProgressKey, persistentStateIDKey, trieJournalKey} {
if bytes.Equal(key, meta) {
metadata.Add(size)
break
}
}
if !accounted {
unaccounted.Add(size)
}
}
count++
if count%1000 == 0 && time.Since(logged) > 8*time.Second {
log.Info("Inspecting separate state database", "count", count, "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
}
}
// Display the database statistic of key-value store.
stats := [][]string{
{"Key-Value store", "Headers", headers.Size(), headers.Count()},
@ -768,6 +853,28 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
}
total += ancient.size()
}
// inspect ancient state in separate trie db if exist
if trieIter != nil {
stateAncients, err := inspectFreezers(db.StateStore())
if err != nil {
return err
}
for _, ancient := range stateAncients {
for _, table := range ancient.sizes {
if ancient.name == "chain" {
break
}
stats = append(stats, []string{
fmt.Sprintf("Ancient store (%s)", strings.Title(ancient.name)),
strings.Title(table.name),
table.size.String(),
fmt.Sprintf("%d", ancient.count()),
})
}
total += ancient.size()
}
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Database", "Category", "Size", "Items"})
table.SetFooter([]string{"", "Total", total.String(), " "})

@ -1004,7 +1004,7 @@ func (t *freezerTable) ResetItemsOffset(virtualTail uint64) error {
}
if stat.Size() == 0 {
return fmt.Errorf("Stat size is zero when ResetVirtualTail.")
return errors.New("Stat size is zero when ResetVirtualTail.")
}
var firstIndex indexEntry

@ -213,6 +213,18 @@ func (t *table) SetDiffStore(diff ethdb.KeyValueStore) {
panic("not implement")
}
func (t *table) StateStore() ethdb.Database {
return nil
}
func (t *table) SetStateStore(state ethdb.Database) {
panic("not implement")
}
func (t *table) StateStoreReader() ethdb.Reader {
return nil
}
// NewBatchWithSize creates a write-only database batch with pre-allocated buffer.
func (t *table) NewBatchWithSize(size int) ethdb.Batch {
return &tableBatch{t.db.NewBatchWithSize(size), t.prefix}

@ -159,13 +159,19 @@ func (p *Pruner) PruneAll(genesis *core.Genesis) error {
}
func pruneAll(maindb ethdb.Database, g *core.Genesis) error {
var pruneDB ethdb.Database
if maindb != nil && maindb.StateStore() != nil {
pruneDB = maindb.StateStore()
} else {
pruneDB = maindb
}
var (
count int
size common.StorageSize
pstart = time.Now()
logged = time.Now()
batch = maindb.NewBatch()
iter = maindb.NewIterator(nil, nil)
batch = pruneDB.NewBatch()
iter = pruneDB.NewIterator(nil, nil)
)
start := time.Now()
for iter.Next() {
@ -195,7 +201,7 @@ func pruneAll(maindb ethdb.Database, g *core.Genesis) error {
batch.Reset()
iter.Release()
iter = maindb.NewIterator(nil, key)
iter = pruneDB.NewIterator(nil, key)
}
}
}
@ -219,7 +225,7 @@ func pruneAll(maindb ethdb.Database, g *core.Genesis) error {
end = nil
}
log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart)))
if err := maindb.Compact(start, end); err != nil {
if err := pruneDB.Compact(start, end); err != nil {
log.Error("Database compaction failed", "error", err)
return err
}
@ -250,13 +256,19 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta
// that the false-positive is low enough(~0.05%). The probability of the
// dangling node is the state root is super low. So the dangling nodes in
// theory will never ever be visited again.
var pruneDB ethdb.Database
if maindb != nil && maindb.StateStore() != nil {
pruneDB = maindb.StateStore()
} else {
pruneDB = maindb
}
var (
skipped, count int
size common.StorageSize
pstart = time.Now()
logged = time.Now()
batch = maindb.NewBatch()
iter = maindb.NewIterator(nil, nil)
batch = pruneDB.NewBatch()
iter = pruneDB.NewIterator(nil, nil)
)
for iter.Next() {
key := iter.Key()
@ -303,7 +315,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta
batch.Reset()
iter.Release()
iter = maindb.NewIterator(nil, key)
iter = pruneDB.NewIterator(nil, key)
}
}
}
@ -348,7 +360,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta
end = nil
}
log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart)))
if err := maindb.Compact(start, end); err != nil {
if err := pruneDB.Compact(start, end); err != nil {
log.Error("Database compaction failed", "error", err)
return err
}
@ -586,10 +598,17 @@ func (p *Pruner) Prune(root common.Hash) error {
// Use the bottom-most diff layer as the target
root = layers[len(layers)-1].Root()
}
// if the separated state db has been set, use this db to prune data
var trienodedb ethdb.Database
if p.db != nil && p.db.StateStore() != nil {
trienodedb = p.db.StateStore()
} else {
trienodedb = p.db
}
// Ensure the root is really present. The weak assumption
// is the presence of root can indicate the presence of the
// entire trie.
if !rawdb.HasLegacyTrieNode(p.db, root) {
if !rawdb.HasLegacyTrieNode(trienodedb, root) {
// The special case is for clique based networks(goerli
// and some other private networks), it's possible that two
// consecutive blocks will have same root. In this case snapshot
@ -603,7 +622,7 @@ func (p *Pruner) Prune(root common.Hash) error {
// as the pruning target.
var found bool
for i := len(layers) - 2; i >= 2; i-- {
if rawdb.HasLegacyTrieNode(p.db, layers[i].Root()) {
if rawdb.HasLegacyTrieNode(trienodedb, layers[i].Root()) {
root = layers[i].Root()
found = true
log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i)
@ -611,7 +630,7 @@ func (p *Pruner) Prune(root common.Hash) error {
}
}
if !found {
if blob := rawdb.ReadLegacyTrieNode(p.db, p.snaptree.DiskRoot()); len(blob) != 0 {
if blob := rawdb.ReadLegacyTrieNode(trienodedb, p.snaptree.DiskRoot()); len(blob) != 0 {
root = p.snaptree.DiskRoot()
found = true
log.Info("Selecting disk-layer as the pruning target", "root", root)

@ -6,7 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common"
)
// sharedPool is used to store maps of originStorage of stateObjects
// StoragePool is used to store maps of originStorage of stateObjects
type StoragePool struct {
sync.RWMutex
sharedMap map[common.Address]*sync.Map

@ -18,6 +18,7 @@
package state
import (
"errors"
"fmt"
"runtime"
"sort"
@ -747,7 +748,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
if s.trie == nil {
tr, err := s.db.OpenTrie(s.originalRoot)
if err != nil {
s.setError(fmt.Errorf("failed to open trie tree"))
s.setError(errors.New("failed to open trie tree"))
return nil
}
s.trie = tr
@ -1003,7 +1004,7 @@ func (s *StateDB) WaitPipeVerification() error {
// Need to wait for the parent trie to commit
if s.snap != nil {
if valid := s.snap.WaitAndGetVerifyRes(); !valid {
return fmt.Errorf("verification on parent snap failed")
return errors.New("verification on parent snap failed")
}
}
return nil

@ -80,7 +80,7 @@ func CheckNoGoroutines(key, value string) error {
var pb bytes.Buffer
profiler := pprof.Lookup("goroutine")
if profiler == nil {
return fmt.Errorf("unable to find profile")
return errors.New("unable to find profile")
}
err := profiler.WriteTo(&pb, 0)
if err != nil {

@ -76,7 +76,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
lastBlock := p.bc.GetBlockByHash(block.ParentHash())
if lastBlock == nil {
return statedb, nil, nil, 0, fmt.Errorf("could not get parent block")
return statedb, nil, nil, 0, errors.New("could not get parent block")
}
if !p.config.IsFeynman(block.Number(), block.Time()) {
// Handle upgrade build-in system contract code

@ -419,8 +419,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header.BlobGasUsed = &used
beaconRoot := common.HexToHash("0xbeac00")
if config.Parlia == nil {
header.ParentBeaconRoot = &beaconRoot
}
}
// Assemble and return the final block for sealing
if config.IsShanghai(header.Number, header.Time) {
return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil))

@ -17,6 +17,7 @@
package core
import (
"errors"
"fmt"
"math"
"math/big"
@ -397,10 +398,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.evm.ChainConfig().IsNano(st.evm.Context.BlockNumber) {
for _, blackListAddr := range types.NanoBlackList {
if blackListAddr == msg.From {
return nil, fmt.Errorf("block blacklist account")
return nil, errors.New("block blacklist account")
}
if msg.To != nil && *msg.To == blackListAddr {
return nil, fmt.Errorf("block blacklist account")
return nil, errors.New("block blacklist account")
}
}
}

@ -18,6 +18,7 @@ package txpool
import (
"crypto/sha256"
"errors"
"fmt"
"math/big"
@ -126,7 +127,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
// data match up before doing any expensive validations
hashes := tx.BlobHashes()
if len(hashes) == 0 {
return fmt.Errorf("blobless blob transaction")
return errors.New("blobless blob transaction")
}
if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob {
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob)

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

@ -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,
}
}

@ -2,6 +2,7 @@ package vm
import (
"encoding/binary"
"errors"
"fmt"
"net/url"
"strings"
@ -68,7 +69,7 @@ func (c *tmHeaderValidate) Run(input []byte) (result []byte, err error) {
}()
if uint64(len(input)) <= precompileContractInputMetaDataLength {
return nil, fmt.Errorf("invalid input")
return nil, errors.New("invalid input")
}
payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-uint64TypeLength : precompileContractInputMetaDataLength])
@ -124,7 +125,7 @@ func (c *iavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
return c.basicIavlMerkleProofValidate.Run(input)
}
// tmHeaderValidate implemented as a native contract.
// tmHeaderValidateNano implemented as a native contract.
type tmHeaderValidateNano struct{}
func (c *tmHeaderValidateNano) RequiredGas(input []byte) uint64 {
@ -132,7 +133,7 @@ func (c *tmHeaderValidateNano) RequiredGas(input []byte) uint64 {
}
func (c *tmHeaderValidateNano) Run(input []byte) (result []byte, err error) {
return nil, fmt.Errorf("suspend")
return nil, errors.New("suspend")
}
type iavlMerkleProofValidateNano struct{}
@ -142,7 +143,7 @@ func (c *iavlMerkleProofValidateNano) RequiredGas(_ []byte) uint64 {
}
func (c *iavlMerkleProofValidateNano) Run(_ []byte) (result []byte, err error) {
return nil, fmt.Errorf("suspend")
return nil, errors.New("suspend")
}
// ------------------------------------------------------------------------------------------------------------------------------------------------
@ -250,7 +251,7 @@ func (c *basicIavlMerkleProofValidate) Run(input []byte) (result []byte, err err
valid := kvmp.Validate()
if !valid {
return nil, fmt.Errorf("invalid merkle proof")
return nil, errors.New("invalid merkle proof")
}
return successfulMerkleResult(), nil
@ -418,7 +419,7 @@ const (
// | 33 bytes | 64 bytes | 32 bytes |
func (c *secp256k1SignatureRecover) Run(input []byte) (result []byte, err error) {
if len(input) != int(secp256k1PubKeyLength)+int(secp256k1SignatureLength)+int(secp256k1SignatureMsgHashLength) {
return nil, fmt.Errorf("invalid input")
return nil, errors.New("invalid input")
}
return c.runTMSecp256k1Signature(
@ -432,7 +433,7 @@ func (c *secp256k1SignatureRecover) runTMSecp256k1Signature(pubkey, signatureStr
tmPubKey := secp256k1.PubKeySecp256k1(pubkey)
ok := tmPubKey.VerifyBytesWithMsgHash(msgHash, signatureStr)
if !ok {
return nil, fmt.Errorf("invalid signature")
return nil, errors.New("invalid signature")
}
return tmPubKey.Address().Bytes(), nil
}

@ -1,6 +1,7 @@
package v1
import (
"errors"
"fmt"
"github.com/bnb-chain/ics23"
@ -71,7 +72,7 @@ func (op CommitmentOp) GetKey() []byte {
// in the CommitmentOp and return the CommitmentRoot of the proof.
func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) {
if _, ok := op.Proof.Proof.(*ics23.CommitmentProof_Exist); !ok {
return nil, fmt.Errorf("only exist proof supported")
return nil, errors.New("only exist proof supported")
}
// calculate root from proof

@ -3,6 +3,7 @@ package v1
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/tendermint/tendermint/crypto/ed25519"
@ -97,7 +98,7 @@ func (cs ConsensusState) EncodeConsensusState() ([]byte, error) {
pos := uint64(0)
if uint64(len(cs.ChainID)) > chainIDLength {
return nil, fmt.Errorf("chainID length should be no more than 32")
return nil, errors.New("chainID length should be no more than 32")
}
copy(encodingBytes[pos:pos+chainIDLength], cs.ChainID)
pos += chainIDLength
@ -115,7 +116,7 @@ func (cs ConsensusState) EncodeConsensusState() ([]byte, error) {
validator := cs.NextValidatorSet.Validators[index]
pubkey, ok := validator.PubKey.(ed25519.PubKeyEd25519)
if !ok {
return nil, fmt.Errorf("invalid pubkey type")
return nil, errors.New("invalid pubkey type")
}
copy(encodingBytes[pos:pos+validatorPubkeyLength], pubkey[:])
@ -177,16 +178,16 @@ func (h *Header) Validate(chainID string) error {
return err
}
if h.ValidatorSet == nil {
return fmt.Errorf("invalid header: validator set is nil")
return errors.New("invalid header: validator set is nil")
}
if h.NextValidatorSet == nil {
return fmt.Errorf("invalid header: next validator set is nil")
return errors.New("invalid header: next validator set is nil")
}
if !bytes.Equal(h.ValidatorsHash, h.ValidatorSet.Hash()) {
return fmt.Errorf("invalid header: validator set does not match hash")
return errors.New("invalid header: validator set does not match hash")
}
if !bytes.Equal(h.NextValidatorsHash, h.NextValidatorSet.Hash()) {
return fmt.Errorf("invalid header: next validator set does not match hash")
return errors.New("invalid header: next validator set does not match hash")
}
return nil
}

@ -4,6 +4,7 @@ package v2
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/cometbft/cometbft/crypto/ed25519"
@ -49,7 +50,7 @@ func (cs ConsensusState) EncodeConsensusState() ([]byte, error) {
pos := uint64(0)
if uint64(len(cs.ChainID)) > chainIDLength {
return nil, fmt.Errorf("chainID length should be no more than 32")
return nil, errors.New("chainID length should be no more than 32")
}
copy(encodingBytes[pos:pos+chainIDLength], cs.ChainID)
pos += chainIDLength
@ -197,7 +198,7 @@ func DecodeConsensusState(input []byte) (ConsensusState, error) {
// 32 bytes | | |
func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.LightBlock, error) {
if uint64(len(input)) <= consensusStateLengthBytesLength {
return nil, nil, fmt.Errorf("invalid input")
return nil, nil, errors.New("invalid input")
}
csLen := binary.BigEndian.Uint64(input[consensusStateLengthBytesLength-uint64TypeLength : consensusStateLengthBytesLength])

@ -20,6 +20,7 @@ import (
"container/heap"
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
@ -81,13 +82,13 @@ func (b *testBackend) EventMux() *event.TypeMux { return b.eventMux }
func (p *mockPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) {
parentHeader := chain.GetHeaderByHash(headers[len(headers)-1].ParentHash)
if parentHeader == nil {
return 0, common.Hash{}, fmt.Errorf("unexpected error")
return 0, common.Hash{}, errors.New("unexpected error")
}
return parentHeader.Number.Uint64(), parentHeader.Hash(), nil
}
func (p *mockInvalidPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) {
return 0, common.Hash{}, fmt.Errorf("not supported")
return 0, common.Hash{}, errors.New("not supported")
}
func (m *mockPOSA) VerifyVote(chain consensus.ChainHeaderReader, vote *types.VoteEnvelope) error {

@ -2,7 +2,6 @@ package vote
import (
"context"
"fmt"
"os"
"time"
@ -38,7 +37,7 @@ func NewVoteSigner(blsPasswordPath, blsWalletPath string) (*VoteSigner, error) {
}
if !dirExists {
log.Error("BLS wallet did not exists.")
return nil, fmt.Errorf("BLS wallet did not exists")
return nil, errors.New("BLS wallet did not exists")
}
walletPassword, err := os.ReadFile(blsPasswordPath)

@ -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()
}

@ -67,6 +67,10 @@ import (
"github.com/ethereum/go-ethereum/triedb/pathdb"
)
const (
ChainDBNamespace = "eth/db/chaindata/"
)
// Config contains the configuration options of the ETH protocol.
// Deprecated: use ethconfig.Config instead.
type Config = ethconfig.Config
@ -134,7 +138,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Assemble the Ethereum object
chainDb, err := stack.OpenAndMergeDatabase("chaindata", config.DatabaseCache, config.DatabaseHandles,
config.DatabaseFreezer, config.DatabaseDiff, "eth/db/chaindata/", false, config.PersistDiff, config.PruneAncientData)
config.DatabaseFreezer, config.DatabaseDiff, ChainDBNamespace, false, config.PersistDiff, config.PruneAncientData)
if err != nil {
return nil, err
}
@ -327,7 +331,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
parlia.VotePool = votePool
}
} else {
return nil, fmt.Errorf("Engine is not Parlia type")
return nil, errors.New("Engine is not Parlia type")
}
log.Info("Create votePool successfully")
eth.handler.votepool = votePool

@ -1,6 +1,7 @@
package eth
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core"
@ -44,7 +45,7 @@ func (h *trustHandler) Handle(peer *trust.Peer, packet trust.Packet) error {
vm.HandleRootResponse(verifyResult, peer.ID())
return nil
}
return fmt.Errorf("verify manager is nil which is unexpected")
return errors.New("verify manager is nil which is unexpected")
default:
return fmt.Errorf("unexpected trust packet type: %T", packet)

@ -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, &params, "mev_params")
if err != nil {
return nil, err
}
return &params, err
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"

@ -155,11 +155,16 @@ type AncientStater interface {
AncientDatadir() (string, error)
}
type StateStoreReader interface {
StateStoreReader() Reader
}
// Reader contains the methods required to read data from both key-value as well as
// immutable ancient data.
type Reader interface {
KeyValueReader
AncientReader
StateStoreReader
}
// Writer contains the methods required to write data to both key-value as well as
@ -189,12 +194,18 @@ type DiffStore interface {
SetDiffStore(diff KeyValueStore)
}
type StateStore interface {
StateStore() Database
SetStateStore(state Database)
}
// Database contains all the methods required by the high level database to not
// only access the key-value data store but also the chain freezer.
type Database interface {
Reader
Writer
DiffStore
StateStore
Batcher
Iteratee
Stater

@ -94,6 +94,18 @@ func (db *Database) SetDiffStore(diff ethdb.KeyValueStore) {
panic("not supported")
}
func (db *Database) StateStore() ethdb.Database {
panic("not supported")
}
func (db *Database) SetStateStore(state ethdb.Database) {
panic("not supported")
}
func (db *Database) StateStoreReader() ethdb.Reader {
return db
}
func (db *Database) ReadAncients(fn func(op ethdb.AncientReaderOp) error) (err error) {
return fn(db)
}

4
go.mod

@ -35,7 +35,7 @@ require (
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46
github.com/gofrs/flock v0.8.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3
github.com/golang/protobuf v1.5.4
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
github.com/google/gofuzz v1.2.0
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b
@ -282,7 +282,7 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apimachinery v0.20.0 // indirect

8
go.sum

@ -609,8 +609,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -2348,8 +2348,8 @@ google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8/go.mod h1:hFxJC
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=

@ -1424,7 +1424,7 @@ func (s *BlockChainAPI) replay(ctx context.Context, block *types.Block, accounts
// GetDiffAccountsWithScope returns detailed changes of some interested accounts in a specific block number.
func (s *BlockChainAPI) GetDiffAccountsWithScope(ctx context.Context, blockNr rpc.BlockNumber, accounts []common.Address) (*types.DiffAccountsInBlock, error) {
if s.b.Chain() == nil {
return nil, fmt.Errorf("blockchain not support get diff accounts")
return nil, errors.New("blockchain not support get diff accounts")
}
block, err := s.b.BlockByNumber(ctx, blockNr)
@ -1937,7 +1937,7 @@ func (s *TransactionAPI) GetTransactionReceiptsByBlockNumber(ctx context.Context
}
txs := block.Transactions()
if len(txs) != len(receipts) {
return nil, fmt.Errorf("txs length doesn't equal to receipts' length")
return nil, errors.New("txs length doesn't equal to receipts' length")
}
txReceipts := make([]map[string]interface{}, 0, len(txs))

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

@ -31,6 +31,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"
@ -52,9 +56,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) {
@ -635,6 +636,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),
},
}
}

@ -412,3 +412,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

@ -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
}

@ -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

@ -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,
}
}

@ -183,7 +183,6 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core
func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) {
// Define the basic configurations for the Ethereum node
datadir, _ := os.MkdirTemp("", "")
config := &node.Config{
Name: "geth",
Version: params.Version,

@ -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
@ -289,9 +295,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()
@ -946,7 +957,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
if genParams.parentHash != (common.Hash{}) {
block := w.chain.GetBlockByHash(genParams.parentHash)
if block == nil {
return nil, fmt.Errorf("missing parent")
return nil, errors.New("missing parent")
}
parent = block.Header()
}
@ -994,8 +1005,10 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
}
header.BlobGasUsed = new(uint64)
header.ExcessBlobGas = &excessBlobGas
if w.chainConfig.Parlia == nil {
header.ParentBeaconRoot = genParams.beaconRoot
}
}
// Run the consensus preparation with the default or customized consensus engine.
if err := w.engine.Prepare(w.chain, header); err != nil {
log.Error("Failed to prepare header for sealing", "err", err)
@ -1280,6 +1293,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
@ -1290,6 +1321,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

@ -786,10 +786,30 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di
if persistDiff {
chainDataHandles = handles * chainDataHandlesPercentage / 100
}
var statediskdb ethdb.Database
var err error
// Open the separated state database if the state directory exists
if n.IsSeparatedDB() {
// Allocate half of the handles and cache to this separate state data database
statediskdb, err = n.OpenDatabaseWithFreezer(name+"/state", cache/2, chainDataHandles/2, "", "eth/db/statedata/", readonly, false, false, pruneAncientData)
if err != nil {
return nil, err
}
// Reduce the handles and cache to this separate database because it is not a complete database with no trie data storing in it.
cache = int(float64(cache) * 0.6)
chainDataHandles = int(float64(chainDataHandles) * 0.6)
}
chainDB, err := n.OpenDatabaseWithFreezer(name, cache, chainDataHandles, freezer, namespace, readonly, false, false, pruneAncientData)
if err != nil {
return nil, err
}
if statediskdb != nil {
chainDB.SetStateStore(statediskdb)
}
if persistDiff {
diffStore, err := n.OpenDiffDatabase(name, handles-chainDataHandles, diff, namespace, readonly)
if err != nil {
@ -837,6 +857,16 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient,
return db, err
}
// IsSeparatedDB check the state subdirectory of db, if subdirectory exists, return true
func (n *Node) IsSeparatedDB() bool {
separateDir := filepath.Join(n.ResolvePath("chaindata"), "state")
fileInfo, err := os.Stat(separateDir)
if os.IsNotExist(err) {
return false
}
return fileInfo.IsDir()
}
func (n *Node) OpenDiffDatabase(name string, handles int, diff, namespace string, readonly bool) (*leveldb.Database, error) {
n.lock.Lock()
defer n.lock.Unlock()

@ -19,6 +19,7 @@ package node
import (
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"net"
@ -299,7 +300,7 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
defer h.mu.Unlock()
if h.rpcAllowed() {
return fmt.Errorf("JSON-RPC over HTTP is already enabled")
return errors.New("JSON-RPC over HTTP is already enabled")
}
// Create RPC server and handler.

@ -44,7 +44,7 @@ func init() {
}
}
// meteredConn is a wrapper around a net.UDPConn that meters both the
// meteredUdpConn is a wrapper around a net.UDPConn that meters both the
// inbound and outbound network traffic.
type meteredUdpConn struct {
UDPConn

@ -26,6 +26,7 @@ import (
"context"
crand "crypto/rand"
"encoding/binary"
"errors"
"fmt"
mrand "math/rand"
"net"
@ -383,7 +384,7 @@ func (tab *Table) doRevalidate(done chan<- struct{}) {
if tab.enrFilter != nil {
if !tab.enrFilter(n.Record()) {
tab.log.Trace("ENR record filter out", "id", last.ID(), "addr", last.addr())
err = fmt.Errorf("filtered node")
err = errors.New("filtered node")
}
}
last = &node{Node: *n, addedAt: last.addedAt, livenessChecks: last.livenessChecks}

@ -365,7 +365,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
return nil, err
}
if respN.ID() != n.ID() {
return nil, fmt.Errorf("invalid ID in response record")
return nil, errors.New("invalid ID in response record")
}
if respN.Seq() < n.Seq() {
return n, nil // response record is older

@ -442,7 +442,7 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
}
}
if _, ok := seen[node.ID()]; ok {
return nil, fmt.Errorf("duplicate record")
return nil, errors.New("duplicate record")
}
seen[node.ID()] = struct{}{}
return node, nil

@ -367,11 +367,11 @@ func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoarey
// key is part of the ID nonce signature.
var remotePubkey = new(ecdsa.PublicKey)
if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
return nil, nil, errors.New("can't find secp256k1 key for recipient")
}
ephkey, err := c.sc.ephemeralKeyGen()
if err != nil {
return nil, nil, fmt.Errorf("can't generate ephemeral key")
return nil, nil, errors.New("can't generate ephemeral key")
}
ephpubkey := EncodePubkey(&ephkey.PublicKey)
auth.pubkey = ephpubkey[:]
@ -395,7 +395,7 @@ func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoarey
// Create session keys.
sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata)
if sec == nil {
return nil, nil, fmt.Errorf("key derivation failed")
return nil, nil, errors.New("key derivation failed")
}
return auth, sec, err
}

@ -191,7 +191,7 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry,
func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
wantHash, err := b32format.DecodeString(hash)
if err != nil {
return nil, fmt.Errorf("invalid base32 hash")
return nil, errors.New("invalid base32 hash")
}
name := hash + "." + domain
txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)

@ -21,6 +21,7 @@ import (
"crypto/ecdsa"
"encoding/base32"
"encoding/base64"
"errors"
"fmt"
"io"
"strings"
@ -341,7 +342,7 @@ func parseLinkEntry(e string) (entry, error) {
func parseLink(e string) (*linkEntry, error) {
if !strings.HasPrefix(e, linkPrefix) {
return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
return nil, errors.New("wrong/missing scheme 'enrtree' in URL")
}
e = e[len(linkPrefix):]

@ -18,7 +18,7 @@ package enode
import (
"crypto/ecdsa"
"fmt"
"errors"
"io"
"github.com/ethereum/go-ethereum/common/math"
@ -67,7 +67,7 @@ func (V4ID) Verify(r *enr.Record, sig []byte) error {
if err := r.Load(&entry); err != nil {
return err
} else if len(entry) != 33 {
return fmt.Errorf("invalid public key")
return errors.New("invalid public key")
}
h := sha3.NewLegacyKeccak256()

@ -17,6 +17,7 @@
package nat
import (
"errors"
"fmt"
"net"
"strings"
@ -47,7 +48,7 @@ func (n *pmp) ExternalIP() (net.IP, error) {
func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
if lifetime <= 0 {
return 0, fmt.Errorf("lifetime must not be <= 0")
return 0, errors.New("lifetime must not be <= 0")
}
// Note order of port arguments is switched between our
// AddMapping and the client's AddPortMapping.

@ -712,7 +712,7 @@ func formatPrimitiveValue(encType string, encValue interface{}) (string, error)
func (t Types) validate() error {
for typeKey, typeArr := range t {
if len(typeKey) == 0 {
return fmt.Errorf("empty type key")
return errors.New("empty type key")
}
for i, typeObj := range typeArr {
if len(typeObj.Type) == 0 {

@ -556,7 +556,7 @@ func runRandTest(rt randTest) error {
checktr.MustUpdate(it.Key, it.Value)
}
if tr.Hash() != checktr.Hash() {
rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash")
rt[i].err = errors.New("hash mismatch in opItercheckhash")
}
case opNodeDiff:
var (
@ -594,19 +594,19 @@ func runRandTest(rt randTest) error {
}
}
if len(insertExp) != len(tr.tracer.inserts) {
rt[i].err = fmt.Errorf("insert set mismatch")
rt[i].err = errors.New("insert set mismatch")
}
if len(deleteExp) != len(tr.tracer.deletes) {
rt[i].err = fmt.Errorf("delete set mismatch")
rt[i].err = errors.New("delete set mismatch")
}
for insert := range tr.tracer.inserts {
if _, present := insertExp[insert]; !present {
rt[i].err = fmt.Errorf("missing inserted node")
rt[i].err = errors.New("missing inserted node")
}
}
for del := range tr.tracer.deletes {
if _, present := deleteExp[del]; !present {
rt[i].err = fmt.Errorf("missing deleted node")
rt[i].err = errors.New("missing deleted node")
}
}
}

@ -93,6 +93,12 @@ type Database struct {
// the legacy hash-based scheme is used by default.
func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
// Sanitize the config and use the default one if it's not specified.
var triediskdb ethdb.Database
if diskdb != nil && diskdb.StateStore() != nil {
triediskdb = diskdb.StateStore()
} else {
triediskdb = diskdb
}
dbScheme := rawdb.ReadStateScheme(diskdb)
if config == nil {
if dbScheme == rawdb.PathScheme {
@ -112,11 +118,11 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
}
var preimages *preimageStore
if config.Preimages {
preimages = newPreimageStore(diskdb)
preimages = newPreimageStore(triediskdb)
}
db := &Database{
config: config,
diskdb: diskdb,
diskdb: triediskdb,
preimages: preimages,
}
/*
@ -125,20 +131,20 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
* 3. Last, use the default scheme, namely hash scheme
*/
if config.HashDB != nil {
if rawdb.ReadStateScheme(diskdb) == rawdb.PathScheme {
if rawdb.ReadStateScheme(triediskdb) == rawdb.PathScheme {
log.Warn("incompatible state scheme", "old", rawdb.PathScheme, "new", rawdb.HashScheme)
}
db.backend = hashdb.New(diskdb, config.HashDB, trie.MerkleResolver{})
db.backend = hashdb.New(triediskdb, config.HashDB, trie.MerkleResolver{})
} else if config.PathDB != nil {
if rawdb.ReadStateScheme(diskdb) == rawdb.HashScheme {
if rawdb.ReadStateScheme(triediskdb) == rawdb.HashScheme {
log.Warn("incompatible state scheme", "old", rawdb.HashScheme, "new", rawdb.PathScheme)
}
db.backend = pathdb.New(diskdb, config.PathDB)
db.backend = pathdb.New(triediskdb, config.PathDB)
} else if strings.Compare(dbScheme, rawdb.PathScheme) == 0 {
if config.PathDB == nil {
config.PathDB = pathdb.Defaults
}
db.backend = pathdb.New(diskdb, config.PathDB)
db.backend = pathdb.New(triediskdb, config.PathDB)
} else {
var resolver hashdb.ChildResolver
if config.IsVerkle {
@ -150,7 +156,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
if config.HashDB == nil {
config.HashDB = hashdb.Defaults
}
db.backend = hashdb.New(diskdb, config.HashDB, resolver)
db.backend = hashdb.New(triediskdb, config.HashDB, resolver)
}
return db
}

@ -215,7 +215,7 @@ func (m *meta) encode() []byte {
// decode unpacks the meta object from byte stream.
func (m *meta) decode(blob []byte) error {
if len(blob) < 1 {
return fmt.Errorf("no version tag")
return errors.New("no version tag")
}
switch blob[0] {
case stateHistoryVersion: