Compare commits

...

43 Commits

Author SHA1 Message Date
galaio
3a3eb1d34f metrics: add more metrics; 2024-06-12 20:26:16 +08:00
galaio
a702225a27 metrics: add more metrics; 2024-06-12 20:18:23 +08:00
galaio
78b0f2940a metrics: add more metrics; 2024-06-12 15:17:25 +08:00
galaio
31a16b3fb8 metrics: add more metrics; 2024-06-12 14:55:32 +08:00
galaio
adf9b91f8f log: opt some log format; 2024-06-12 14:23:02 +08:00
galaio
312f1208f8 dag: fix some unexpected writes; 2024-06-11 21:07:14 +08:00
galaio
6957903492 log: add some logs; 2024-06-11 18:06:54 +08:00
galaio
2da9923d4f log: add some logs; 2024-06-11 17:43:21 +08:00
galaio
c6368a513c log: add some logs; 2024-06-11 16:57:10 +08:00
galaio
a55650056a log: add some logs; 2024-06-11 16:32:42 +08:00
galaio
71520fb59e log: add some logs; 2024-06-11 16:04:45 +08:00
galaio
7cb7800397 log: add some logs; 2024-06-11 15:55:46 +08:00
galaio
d97a34b0cb log: add some logs; 2024-06-11 14:14:14 +08:00
galaio
c20c0e680c log: add some logs; 2024-06-11 14:08:04 +08:00
galaio
b5194c043b log: add some logs; 2024-06-11 12:20:58 +08:00
galaio
6f8e9c570c log: add some logs; 2024-06-11 11:25:34 +08:00
galaio
68fa227b0c dag: opt read recoder logic; 2024-06-10 22:31:39 +08:00
galaio
2d8613a296 dag: opt read recoder logic; 2024-06-10 21:51:37 +08:00
galaio
533f592a05 dag: opt read recoder logic; 2024-06-10 21:44:44 +08:00
galaio
6d1d2ddea5 dag: opt evaluate function; 2024-06-10 20:53:14 +08:00
galaio
bb578de461 dag: opt evaluate function; 2024-06-09 00:01:14 +08:00
galaio
2d0ddc1f67 dag: support stats; 2024-06-08 14:33:24 +08:00
galaio
eff9d23bd2 mvstates: fix val equal check issue; 2024-06-07 22:46:30 +08:00
galaio
98fa1e6c44 mvstates: fix val equal check issue; 2024-06-07 22:39:44 +08:00
galaio
eafdc65814 statedb: fix some system tx issues; 2024-06-07 17:08:23 +08:00
galaio
1a787b6081 rwset: support collect rwset from statedb;
mvstates: support export DAG;
dag: support travel all execution paths;
2024-06-07 16:46:13 +08:00
galaio
1cb6989a30 dag: add basic data structures; 2024-06-06 16:25:12 +08:00
zzzckck
35e71a769b doc: update url linker (#2499) 2024-05-30 14:08:48 +08:00
zzzckck
6b02ac7ac5 doc: update url linker (#2498) 2024-05-30 12:19:07 +08:00
Nathan
8b9558bb4d params/config: add Bohr hardfork (#2497) 2024-05-30 11:51:34 +08:00
Chris Li
63e7eac394 fix: keep 9W blocks in ancient db when prune block (#2481) 2024-05-29 15:11:52 +08:00
Chris Li
05543e558d fix: fix inspect database error (#2484) 2024-05-27 15:10:59 +08:00
Satyajit Das
b0146261c7 jsutils: faucet successful requests within blocks (#2470) 2024-05-23 15:08:36 +08:00
lx
d7b9866d3b Merge pull request #2487 from bnb-chain/master
merge PRs from master to develop branch
2024-05-22 13:56:19 +08:00
Mars
08769ead2b dev: ensure consistency in BPS bundle result (#2479)
* dev: ensure consistency in BPS bundle result

* fix: remove env operation once the sim is discarded & rename
2024-05-21 12:24:41 +08:00
irrun
c77bb1110d fix: limit the gas price of the mev bid (#2473) 2024-05-20 14:33:47 +08:00
Mars
c856d21719 fix: move mev op to MinerAPI & add command to console (#2475) 2024-05-20 14:00:28 +08:00
Eric
5edd032cdb internal/ethapi: add optional parameter for blobSidecars (#2467) 2024-05-16 19:06:49 +08:00
galaio
6b8cbbe172 sync: fix some sync issues caused by prune-block. (#2466) 2024-05-16 12:07:13 +08:00
setunapo
5ea2ada0ee utils: add check_blobtx.js (#2463) 2024-05-15 18:17:57 +08:00
Fynn
b230a02006 cmd: fix memory leak when big dataset (#2455) 2024-05-15 15:28:57 +08:00
Nathan
86e3a02490 cmd/utils: add a flag to change breathe block interval for testing (#2462) 2024-05-15 15:27:05 +08:00
Nathan
0c0958ff87 eth/handler: check lists in body before broadcast blocks (#2461) 2024-05-15 14:54:25 +08:00
61 changed files with 2013 additions and 450 deletions

View File

@@ -36,7 +36,7 @@ To combine DPoS and PoA for consensus, BNB Smart Chain implement a novel consens
2. Validators take turns to produce blocks in a PoA manner, similar to Ethereum's Clique consensus engine.
3. Validator set are elected in and out based on a staking based governance on BNB Beacon Chain.
4. The validator set change is relayed via a cross-chain communication mechanism.
5. Parlia consensus engine will interact with a set of [system contracts](https://docs.bnbchain.org/docs/learn/system-contract) to achieve liveness slash, revenue distributing and validator set renewing func.
5. Parlia consensus engine will interact with a set of [system contracts](https://docs.bnbchain.org/bnb-smart-chain/staking/overview/#system-contracts) to achieve liveness slash, revenue distributing and validator set renewing func.
### Light Client of BNB Beacon Chain
@@ -183,7 +183,7 @@ This tool is optional and if you leave it out you can always attach to an alread
#### 7. More
More details about [running a node](https://docs.bnbchain.org/docs/validator/fullnode) and [becoming a validator](https://docs.bnbchain.org/docs/validator/create-val)
More details about [running a node](https://docs.bnbchain.org/bnb-smart-chain/developers/node_operators/full_node/) and [becoming a validator](https://docs.bnbchain.org/bnb-smart-chain/validator/create-val/)
*Note: Although some internal protective measures prevent transactions from
crossing over between the main network and test network, you should always

View File

@@ -227,7 +227,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return nil, nil, nil, err
}
vmConfig.Tracer = tracer
statedb.SetTxContext(tx.Hash(), txIndex)
statedb.SetTxContext(tx.Hash(), txIndex, 0)
var (
txContext = core.NewEVMTxContext(msg)

View File

@@ -64,6 +64,7 @@ var (
utils.CachePreimagesFlag,
utils.OverrideCancun,
utils.OverrideHaber,
utils.OverrideBohr,
utils.OverrideVerkle,
}, utils.DatabaseFlags),
Description: `
@@ -261,6 +262,10 @@ func initGenesis(ctx *cli.Context) error {
v := ctx.Uint64(utils.OverrideHaber.Name)
overrides.OverrideHaber = &v
}
if ctx.IsSet(utils.OverrideBohr.Name) {
v := ctx.Uint64(utils.OverrideBohr.Name)
overrides.OverrideBohr = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
overrides.OverrideVerkle = &v

View File

@@ -193,6 +193,10 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
v := ctx.Uint64(utils.OverrideHaber.Name)
cfg.Eth.OverrideHaber = &v
}
if ctx.IsSet(utils.OverrideBohr.Name) {
v := ctx.Uint64(utils.OverrideBohr.Name)
cfg.Eth.OverrideBohr = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v

View File

@@ -106,12 +106,12 @@ Remove blockchain and state databases`,
dbInspectTrieCmd = &cli.Command{
Action: inspectTrie,
Name: "inspect-trie",
ArgsUsage: "<blocknum> <jobnum>",
ArgsUsage: "<blocknum> <jobnum> <topn>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.SyncModeFlag,
},
Usage: "Inspect the MPT tree of the account and contract.",
Usage: "Inspect the MPT tree of the account and contract. 'blocknum' can be latest/snapshot/number. 'topn' means output the top N storage tries info ranked by the total number of TrieNodes",
Description: `This commands iterates the entrie WorldState.`,
}
dbCheckStateContentCmd = &cli.Command{
@@ -386,6 +386,7 @@ func inspectTrie(ctx *cli.Context) error {
blockNumber uint64
trieRootHash common.Hash
jobnum uint64
topN uint64
)
stack, _ := makeConfigNode(ctx)
@@ -411,12 +412,25 @@ func inspectTrie(ctx *cli.Context) error {
if ctx.NArg() == 1 {
jobnum = 1000
topN = 10
} else if ctx.NArg() == 2 {
var err error
jobnum, err = strconv.ParseUint(ctx.Args().Get(1), 10, 64)
if err != nil {
return fmt.Errorf("failed to Parse jobnum, Args[1]: %v, err: %v", ctx.Args().Get(1), err)
}
topN = 10
} else {
var err error
jobnum, err = strconv.ParseUint(ctx.Args().Get(1), 10, 64)
if err != nil {
return fmt.Errorf("failed to Parse jobnum, Args[1]: %v, err: %v", ctx.Args().Get(1), err)
}
topN, err = strconv.ParseUint(ctx.Args().Get(2), 10, 64)
if err != nil {
return fmt.Errorf("failed to Parse topn, Args[1]: %v, err: %v", ctx.Args().Get(1), err)
}
}
if blockNumber != math.MaxUint64 {
@@ -437,6 +451,7 @@ func inspectTrie(ctx *cli.Context) error {
if dbScheme == rawdb.PathScheme {
config = &triedb.Config{
PathDB: utils.PathDBConfigAddJournalFilePath(stack, pathdb.ReadOnly),
Cache: 0,
}
} else if dbScheme == rawdb.HashScheme {
config = triedb.HashDefaults
@@ -448,7 +463,7 @@ func inspectTrie(ctx *cli.Context) error {
fmt.Printf("fail to new trie tree, err: %v, rootHash: %v\n", err, trieRootHash.String())
return err
}
theInspect, err := trie.NewInspector(theTrie, triedb, trieRootHash, blockNumber, jobnum)
theInspect, err := trie.NewInspector(theTrie, triedb, trieRootHash, blockNumber, jobnum, int(topN))
if err != nil {
return err
}

View File

@@ -74,6 +74,7 @@ var (
utils.RialtoHash,
utils.OverrideCancun,
utils.OverrideHaber,
utils.OverrideBohr,
utils.OverrideVerkle,
utils.OverrideFullImmutabilityThreshold,
utils.OverrideMinBlocksForBlobRequests,

View File

@@ -75,7 +75,7 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, ancient
if err != nil {
return nil, err
}
frdb, err := rawdb.NewDatabaseWithFreezer(kvdb, ancient, namespace, readonly, disableFreeze, isLastOffset, pruneAncientData)
frdb, err := rawdb.NewDatabaseWithFreezer(kvdb, ancient, namespace, readonly, disableFreeze, isLastOffset, pruneAncientData, false)
if err != nil {
kvdb.Close()
return nil, err
@@ -178,11 +178,10 @@ func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string, blockRemai
// Force run a freeze cycle
type freezer interface {
Freeze() error
Freeze(threshold uint64) error
Ancients() (uint64, error)
}
blockchain.SetFinalized(blocks[len(blocks)-1].Header())
db.(freezer).Freeze()
db.(freezer).Freeze(10)
frozen, err := db.Ancients()
//make sure there're frozen items

View File

@@ -43,9 +43,11 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
cli "github.com/urfave/cli/v2"
)
@@ -245,7 +247,16 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) {
NoBuild: true,
AsyncBuild: false,
}
snaptree, err := snapshot.New(snapconfig, chaindb, triedb.NewDatabase(chaindb, nil), headBlock.Root(), TriesInMemory, false)
dbScheme := rawdb.ReadStateScheme(chaindb)
var config *triedb.Config
if dbScheme == rawdb.PathScheme {
config = &triedb.Config{
PathDB: utils.PathDBConfigAddJournalFilePath(stack, pathdb.ReadOnly),
}
} else if dbScheme == rawdb.HashScheme {
config = triedb.HashDefaults
}
snaptree, err := snapshot.New(snapconfig, chaindb, triedb.NewDatabase(chaindb, config), headBlock.Root(), TriesInMemory, false)
if err != nil {
log.Error("snaptree error", "err", err)
return nil, err // The relevant snapshot(s) might not exist
@@ -333,6 +344,9 @@ func pruneBlock(ctx *cli.Context) error {
stack, config = makeConfigNode(ctx)
defer stack.Close()
blockAmountReserved = ctx.Uint64(utils.BlockAmountReserved.Name)
if blockAmountReserved < params.FullImmutabilityThreshold {
return fmt.Errorf("block-amount-reserved must be greater than or equal to %d", params.FullImmutabilityThreshold)
}
chaindb, err = accessDb(ctx, stack)
if err != nil {
return err

View File

@@ -0,0 +1,51 @@
import { ethers } from "ethers";
import program from "commander";
// depends on ethjs v6.11.0+ for 4844, https://github.com/ethers-io/ethers.js/releases/tag/v6.11.0
// BSC testnet enabled 4844 on block: 39539137
// Usage:
// nvm use 20
// node check_blobtx.js --rpc https://data-seed-prebsc-1-s1.binance.org:8545 --startNum 39539137
// node check_blobtx.js --rpc https://data-seed-prebsc-1-s1.binance.org:8545 --startNum 39539137 --endNum 40345994
program.option("--rpc <Rpc>", "Rpc Server URL");
program.option("--startNum <Num>", "start block", 0);
program.option("--endNum <Num>", "end block", 0);
program.parse(process.argv);
const provider = new ethers.JsonRpcProvider(program.rpc);
const main = async () => {
var startBlock = parseInt(program.startNum)
var endBlock = parseInt(program.endNum)
if (isNaN(endBlock) || isNaN(startBlock) || startBlock == 0) {
console.error("invalid input, --startNum", program.startNum, "--end", program.endNum)
return
}
// if --endNum is not specified, set it to the latest block number.
if (endBlock == 0) {
endBlock = await provider.getBlockNumber();
}
if (startBlock > endBlock) {
console.error("invalid input, startBlock:",startBlock, " endBlock:", endBlock);
return
}
for (let i = startBlock; i <= endBlock; i++) {
let blockData = await provider.getBlock(i);
console.log("startBlock:",startBlock, "endBlock:", endBlock, "curBlock", i, "blobGasUsed", blockData.blobGasUsed);
if (blockData.blobGasUsed == 0) {
continue
}
for (let txIndex = 0; txIndex<= blockData.transactions.length - 1; txIndex++) {
let txHash = blockData.transactions[txIndex]
let txData = await provider.getTransaction(txHash);
if (txData.type == 3) {
console.log("BlobTx in block:",i, " txIndex:", txIndex, " txHash:", txHash);
}
}
}
};
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,49 @@
import { ethers } from "ethers";
import program from "commander";
// Usage:
// node faucet_request.js --rpc localhost:8545 --startNum 39539137
// node faucet_request.js --rpc localhost:8545 --startNum 39539137 --endNum 40345994
// node faucet_request.js --rpc https://data-seed-prebsc-1-s1.bnbchain.org:8545 --startNum 39539137 --endNum 40345994
program.option("--rpc <Rpc>", "Rpc Server URL");
program.option("--startNum <Num>", "start block", 0);
program.option("--endNum <Num>", "end block", 0);
program.parse(process.argv);
const provider = new ethers.JsonRpcProvider(program.rpc);
const main = async () => {
var startBlock = parseInt(program.startNum)
var endBlock = parseInt(program.endNum)
if (isNaN(endBlock) || isNaN(startBlock) || startBlock == 0) {
console.error("invalid input, --startNum", program.startNum, "--end", program.endNum)
return
}
// if --endNum is not specified, set it to the latest block number.
if (endBlock == 0) {
endBlock = await provider.getBlockNumber();
}
if (startBlock > endBlock) {
console.error("invalid input, startBlock:",startBlock, " endBlock:", endBlock);
return
}
let startBalance = await provider.getBalance("0xaa25Aa7a19f9c426E07dee59b12f944f4d9f1DD3", startBlock)
let endBalance = await provider.getBalance("0xaa25Aa7a19f9c426E07dee59b12f944f4d9f1DD3", endBlock)
const faucetAmount = BigInt(0.3 * 10**18); // Convert 0.3 ether to wei as a BigInt
const numFaucetRequest = (startBalance - endBalance) / faucetAmount;
// Convert BigInt to ether
const startBalanceEth = Number(startBalance) / 10**18;
const endBalanceEth = Number(endBalance) / 10**18;
console.log(`Start Balance: ${startBalanceEth} ETH`);
console.log(`End Balance: ${endBalanceEth} ETH`);
console.log("successful faucet request: ",numFaucetRequest);
};
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -315,6 +315,11 @@ var (
Usage: "Manually specify the Haber fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideBohr = &cli.Uint64Flag{
Name: "override.bohr",
Usage: "Manually specify the Bohr fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideVerkle = &cli.Uint64Flag{
Name: "override.verkle",
Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting",
@@ -1082,6 +1087,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Name: "block-amount-reserved",
Usage: "Sets the expected remained amount of blocks for offline block prune",
Category: flags.BlockHistoryCategory,
Value: params.FullImmutabilityThreshold,
}
CheckSnapshotWithMPT = &cli.BoolFlag{

View File

@@ -163,7 +163,7 @@ func TestHistoryImportAndExport(t *testing.T) {
// Now import Era.
freezer := t.TempDir()
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false, false, false, false)
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false, false, false, false, false)
if err != nil {
panic(err)
}

View File

@@ -1824,7 +1824,7 @@ func (p *Parlia) applyTransaction(
// move to next
*receivedTxs = (*receivedTxs)[1:]
}
state.SetTxContext(expectedTx.Hash(), len(*txs))
state.SetTxContext(expectedTx.Hash(), len(*txs), 0)
gasUsed, err := applyMessage(msg, state, header, p.chainConfig, chainContext)
if err != nil {
return err

View File

@@ -66,6 +66,31 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
return validator
}
// ValidateListsInBody validates that UncleHash, WithdrawalsHash, and WithdrawalsHash correspond to the lists in the block body, respectively.
func ValidateListsInBody(block *types.Block) error {
header := block.Header()
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash)
}
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
}
// Withdrawals are present after the Shanghai fork.
if header.WithdrawalsHash != nil {
// Withdrawals list must be present in body after Shanghai.
if block.Withdrawals() == nil {
return errors.New("missing withdrawals in block body")
}
if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash)
}
} else if block.Withdrawals() != nil { // Withdrawals turn into empty from nil when BlockBody has Sidecars
// Withdrawals are not allowed prior to shanghai fork
return errors.New("withdrawals present in block body")
}
return nil
}
// ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already
// validated at this point.
@@ -83,31 +108,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
return err
}
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash)
}
validateFuns := []func() error{
func() error {
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
}
return nil
return ValidateListsInBody(block)
},
func() error {
// Withdrawals are present after the Shanghai fork.
if header.WithdrawalsHash != nil {
// Withdrawals list must be present in body after Shanghai.
if block.Withdrawals() == nil {
return errors.New("missing withdrawals in block body")
}
if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash)
}
} else if block.Withdrawals() != nil { // Withdrawals turn into empty from nil when BlockBody has Sidecars
// Withdrawals are not allowed prior to shanghai fork
return errors.New("withdrawals present in block body")
}
// Blob transactions may be present after the Cancun fork.
var blobs int
for i, tx := range block.Transactions() {

View File

@@ -2307,7 +2307,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start)
// Report the import stats before returning the various results
stats.processed++
stats.usedGas += usedGas

View File

@@ -511,3 +511,12 @@ func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscr
func (bc *BlockChain) SubscribeFinalizedHeaderEvent(ch chan<- FinalizedHeaderEvent) event.Subscription {
return bc.scope.Track(bc.finalizedHeaderFeed.Subscribe(ch))
}
// AncientTail retrieves the tail the ancients blocks
func (bc *BlockChain) AncientTail() (uint64, error) {
tail, err := bc.db.Tail()
if err != nil {
return 0, err
}
return tail, nil
}

View File

@@ -1832,14 +1832,10 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
}
// Force run a freeze cycle
type freezer interface {
Freeze() error
Freeze(threshold uint64) error
Ancients() (uint64, error)
}
if tt.freezeThreshold < uint64(tt.canonicalBlocks) {
final := uint64(tt.canonicalBlocks) - tt.freezeThreshold
chain.SetFinalized(canonblocks[int(final)-1].Header())
}
db.(freezer).Freeze()
db.(freezer).Freeze(tt.freezeThreshold)
// Set the simulated pivot block
if tt.pivotBlock != nil {

View File

@@ -2045,14 +2045,10 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
// Force run a freeze cycle
type freezer interface {
Freeze() error
Freeze(threshold uint64) error
Ancients() (uint64, error)
}
if tt.freezeThreshold < uint64(tt.canonicalBlocks) {
final := uint64(tt.canonicalBlocks) - tt.freezeThreshold
chain.SetFinalized(canonblocks[int(final)-1].Header())
}
db.(freezer).Freeze()
db.(freezer).Freeze(tt.freezeThreshold)
// Set the simulated pivot block
if tt.pivotBlock != nil {

View File

@@ -974,7 +974,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
// Freezer style fast import the chain.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -1069,7 +1069,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
// makeDb creates a db instance for testing.
makeDb := func() ethdb.Database {
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -1957,7 +1957,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
// Import the shared chain and the original canonical one
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
@@ -2026,7 +2026,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// Import the chain as a ancient-first node and ensure all pointers are updated
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2097,7 +2097,7 @@ func testInsertReceiptChainRollback(t *testing.T, scheme string) {
}
// Set up a BlockChain that uses the ancient store.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2167,7 +2167,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
})
// Import the canonical chain
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
@@ -2384,7 +2384,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
b.OffsetTime(-9) // A higher difficulty
})
// Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2555,7 +2555,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
}
})
// Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -3858,7 +3858,7 @@ func testSetCanonical(t *testing.T, scheme string) {
}
gen.AddTx(tx)
})
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil)
@@ -4483,7 +4483,7 @@ func (c *mockParlia) CalcDifficulty(chain consensus.ChainHeaderReader, time uint
func TestParliaBlobFeeReward(t *testing.T) {
// Have N headers in the freezer
frdir := t.TempDir()
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false)
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}

View File

@@ -119,7 +119,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
if b.gasPool == nil {
b.SetCoinbase(common.Address{})
}
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
b.statedb.SetTxContext(tx.Hash(), len(b.txs), 0)
receipt, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig, NewReceiptBloomGenerator())
if err != nil {
panic(err)

View File

@@ -218,6 +218,7 @@ func (e *GenesisMismatchError) Error() string {
type ChainOverrides struct {
OverrideCancun *uint64
OverrideHaber *uint64
OverrideBohr *uint64
OverrideVerkle *uint64
}
@@ -250,6 +251,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
if overrides != nil && overrides.OverrideHaber != nil {
config.HaberTime = overrides.OverrideHaber
}
if overrides != nil && overrides.OverrideBohr != nil {
config.BohrTime = overrides.OverrideBohr
}
if overrides != nil && overrides.OverrideVerkle != nil {
config.VerkleTime = overrides.OverrideVerkle
}

View File

@@ -518,7 +518,7 @@ func checkBlobSidecarsRLP(have, want types.BlobSidecars) error {
func TestAncientStorage(t *testing.T) {
// Freezer style fast import the chain.
frdir := t.TempDir()
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false, false)
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}
@@ -657,7 +657,7 @@ func TestHashesInRange(t *testing.T) {
func BenchmarkWriteAncientBlocks(b *testing.B) {
// Open freezer database.
frdir := b.TempDir()
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false, false)
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false, false, false)
if err != nil {
b.Fatalf("failed to create database with ancient backend")
}
@@ -1001,7 +1001,7 @@ func TestHeadersRLPStorage(t *testing.T) {
// Have N headers in the freezer
frdir := t.TempDir()
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false, false)
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}

View File

@@ -24,12 +24,12 @@ import (
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
const (
@@ -51,25 +51,31 @@ var (
// The background thread will keep moving ancient chain segments from key-value
// database to flat files for saving space on live database.
type chainFreezer struct {
threshold atomic.Uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests)
*Freezer
quit chan struct{}
wg sync.WaitGroup
trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
freezeEnv atomic.Value
multiDatabase bool
}
// newChainFreezer initializes the freezer for ancient chain data.
func newChainFreezer(datadir string, namespace string, readonly bool, offset uint64) (*chainFreezer, error) {
func newChainFreezer(datadir string, namespace string, readonly bool, offset uint64, multiDatabase bool) (*chainFreezer, error) {
freezer, err := NewChainFreezer(datadir, namespace, readonly, offset)
if err != nil {
return nil, err
}
return &chainFreezer{
cf := chainFreezer{
Freezer: freezer,
quit: make(chan struct{}),
trigger: make(chan chan struct{}),
}, nil
}
cf.threshold.Store(params.FullImmutabilityThreshold)
return &cf, nil
}
// Close closes the chain freezer instance and terminates the background thread.
@@ -185,29 +191,101 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
continue
}
threshold, err := f.freezeThreshold(nfdb)
if err != nil {
backoff = true
log.Debug("Current full block not old enough to freeze", "err", err)
continue
}
frozen := f.frozen.Load()
var (
frozen uint64
threshold uint64
first uint64 // the first block to freeze
last uint64 // the last block to freeze
// Short circuit if the blocks below threshold are already frozen.
if frozen != 0 && frozen-1 >= threshold {
backoff = true
log.Debug("Ancient blocks frozen already", "threshold", threshold, "frozen", frozen)
continue
hash common.Hash
number *uint64
head *types.Header
)
// use finalized block as the chain freeze indicator was used for multiDatabase feature, if multiDatabase is false, keep 9W blocks in db
if f.multiDatabase {
threshold, err = f.freezeThreshold(nfdb)
if err != nil {
backoff = true
log.Debug("Current full block not old enough to freeze", "err", err)
continue
}
frozen = f.frozen.Load()
// Short circuit if the blocks below threshold are already frozen.
if frozen != 0 && frozen-1 >= threshold {
backoff = true
log.Debug("Ancient blocks frozen already", "threshold", threshold, "frozen", frozen)
continue
}
hash = ReadHeadBlockHash(nfdb)
if hash == (common.Hash{}) {
log.Debug("Current full block hash unavailable") // new chain, empty database
backoff = true
continue
}
number = ReadHeaderNumber(nfdb, hash)
if number == nil {
log.Error("Current full block number unavailable", "hash", hash)
backoff = true
continue
}
head = ReadHeader(nfdb, hash, *number)
if head == nil {
log.Error("Current full block unavailable", "number", *number, "hash", hash)
backoff = true
continue
}
first = frozen
last = threshold
if last-first+1 > freezerBatchLimit {
last = freezerBatchLimit + first - 1
}
} else {
// Retrieve the freezing threshold.
hash = ReadHeadBlockHash(nfdb)
if hash == (common.Hash{}) {
log.Debug("Current full block hash unavailable") // new chain, empty database
backoff = true
continue
}
number = ReadHeaderNumber(nfdb, hash)
threshold = f.threshold.Load()
frozen = f.frozen.Load()
switch {
case number == nil:
log.Error("Current full block number unavailable", "hash", hash)
backoff = true
continue
case *number < threshold:
log.Debug("Current full block not old enough to freeze", "number", *number, "hash", hash, "delay", threshold)
backoff = true
continue
case *number-threshold <= frozen:
log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", frozen)
backoff = true
continue
}
head = ReadHeader(nfdb, hash, *number)
if head == nil {
log.Error("Current full block unavailable", "number", *number, "hash", hash)
backoff = true
continue
}
first, _ = f.Ancients()
last = *number - threshold
if last-first > freezerBatchLimit {
last = first + freezerBatchLimit
}
}
// Seems we have data ready to be frozen, process in usable batches
var (
start = time.Now()
first = frozen // the first block to freeze
last = threshold // the last block to freeze
)
if last-first+1 > freezerBatchLimit {
last = freezerBatchLimit + first - 1
}
ancients, err := f.freezeRangeWithBlobs(nfdb, first, last)
if err != nil {
@@ -295,24 +373,6 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
log.Debug("Deep froze chain segment", context...)
env, _ := f.freezeEnv.Load().(*ethdb.FreezerEnv)
hash := ReadHeadBlockHash(nfdb)
if hash == (common.Hash{}) {
log.Debug("Current full block hash unavailable") // new chain, empty database
backoff = true
continue
}
number := ReadHeaderNumber(nfdb, hash)
if number == nil {
log.Error("Current full block number unavailable", "hash", hash)
backoff = true
continue
}
head := ReadHeader(nfdb, hash, *number)
if head == nil {
log.Error("Current full block unavailable", "number", *number, "hash", hash)
backoff = true
continue
}
// try prune blob data after cancun fork
if isCancun(env, head.Number, head.Time) {
f.tryPruneBlobAncientTable(env, *number)

View File

@@ -138,13 +138,22 @@ func (frdb *freezerdb) SetBlockStore(block ethdb.Database) {
frdb.blockStore = block
}
func (frdb *freezerdb) HasSeparateBlockStore() bool {
return frdb.blockStore != nil
}
// 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.
func (frdb *freezerdb) Freeze() error {
func (frdb *freezerdb) Freeze(threshold uint64) error {
if frdb.AncientStore.(*chainFreezer).readonly {
return errReadOnly
}
// Set the freezer threshold to a temporary value
defer func(old uint64) {
frdb.AncientStore.(*chainFreezer).threshold.Store(old)
}(frdb.AncientStore.(*chainFreezer).threshold.Load())
frdb.AncientStore.(*chainFreezer).threshold.Store(threshold)
// Trigger a freeze cycle and block until it's done
trigger := make(chan struct{}, 1)
frdb.AncientStore.(*chainFreezer).trigger <- trigger
@@ -263,6 +272,10 @@ func (db *nofreezedb) SetBlockStore(block ethdb.Database) {
db.blockStore = block
}
func (db *nofreezedb) HasSeparateBlockStore() bool {
return db.blockStore != nil
}
func (db *nofreezedb) BlockStoreReader() ethdb.Reader {
if db.blockStore != nil {
return db.blockStore
@@ -358,7 +371,7 @@ func resolveChainFreezerDir(ancient string) string {
// value data store with a freezer moving immutable chain segments into cold
// storage. The passed ancient indicates the path of root ancient directory
// where the chain freezer can be opened.
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly, disableFreeze, isLastOffset, pruneAncientData bool) (ethdb.Database, error) {
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly, disableFreeze, isLastOffset, pruneAncientData, multiDatabase bool) (ethdb.Database, error) {
var offset uint64
// The offset of ancientDB should be handled differently in different scenarios.
if isLastOffset {
@@ -394,7 +407,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
}
// Create the idle freezer instance
frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly, offset)
frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly, offset, multiDatabase)
if err != nil {
printChainMetadata(db)
return nil, err
@@ -575,6 +588,8 @@ type OpenOptions struct {
// Ephemeral means that filesystem sync operations should be avoided: data integrity in the face of
// a crash is not important. This option should typically be used in tests.
Ephemeral bool
MultiDataBase bool
}
// openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble.
@@ -625,7 +640,7 @@ func Open(o OpenOptions) (ethdb.Database, error) {
if len(o.AncientsDirectory) == 0 {
return kvdb, nil
}
frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly, o.DisableFreeze, o.IsLastOffset, o.PruneAncientData)
frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly, o.DisableFreeze, o.IsLastOffset, o.PruneAncientData, o.MultiDataBase)
if err != nil {
kvdb.Close()
return nil, err
@@ -769,7 +784,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
trieIter = db.StateStore().NewIterator(keyPrefix, nil)
defer trieIter.Release()
}
if db.BlockStore() != db {
if db.HasSeparateBlockStore() {
blockIter = db.BlockStore().NewIterator(keyPrefix, nil)
defer blockIter.Release()
}

View File

@@ -239,7 +239,7 @@ func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
// - if maxBytes is not specified, 'count' items will be returned if they are present.
func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
if table := f.tables[kind]; table != nil {
return table.RetrieveItems(start, count, maxBytes)
return table.RetrieveItems(start-f.offset, count, maxBytes)
}
return nil, errUnknownTable
}
@@ -252,7 +252,7 @@ func (f *Freezer) Ancients() (uint64, error) {
func (f *Freezer) TableAncients(kind string) (uint64, error) {
f.writeLock.RLock()
defer f.writeLock.RUnlock()
return f.tables[kind].items.Load(), nil
return f.tables[kind].items.Load() + f.offset, nil
}
// ItemAmountInAncient returns the actual length of current ancientDB.

View File

@@ -43,6 +43,10 @@ func (t *table) SetBlockStore(block ethdb.Database) {
panic("not implement")
}
func (t *table) HasSeparateBlockStore() bool {
panic("not implement")
}
// NewTable returns a database object that prefixes all keys with a given string.
func NewTable(db ethdb.Database, prefix string) ethdb.Database {
return &table{

View File

@@ -164,7 +164,7 @@ func (ch createObjectChange) dirtied() *common.Address {
func (ch resetObjectChange) revert(s *StateDB) {
s.setStateObject(ch.prev)
if !ch.prevdestruct {
delete(s.stateObjectsDestruct, ch.prev.address)
s.deleteStateObjectsDestruct(ch.prev.address)
}
if ch.prevAccount != nil {
s.accounts[ch.prev.addrHash] = ch.prevAccount

View File

@@ -382,7 +382,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str
log.Info("chainDB opened successfully")
// Get the number of items in old ancient db.
itemsOfAncient, err := chainDb.ItemAmountInAncient()
itemsOfAncient, err := chainDb.BlockStore().ItemAmountInAncient()
log.Info("the number of items in ancientDB is ", "itemsOfAncient", itemsOfAncient)
// If we can't access the freezer or it's empty, abort.

View File

@@ -19,6 +19,7 @@ package state
import (
"bytes"
"fmt"
"golang.org/x/exp/slices"
"io"
"sync"
"time"
@@ -68,6 +69,11 @@ type stateObject struct {
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
data types.StateAccount // Account data with all mutations applied in the scope of block
// dirty account state
dirtyBalance *uint256.Int
dirtyNonce *uint64
dirtyCodeHash []byte
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded
@@ -95,7 +101,7 @@ type stateObject struct {
// empty returns whether the account is considered empty.
func (s *stateObject) empty() bool {
return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes())
return s.Nonce() == 0 && s.Balance().IsZero() && bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes())
}
// newObject creates a state object.
@@ -113,7 +119,7 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
storageMap = db.GetStorage(address)
}
return &stateObject{
s := &stateObject{
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
@@ -125,6 +131,15 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
dirtyStorage: make(Storage),
created: created,
}
// dirty data when create a new account
if acct == nil {
s.dirtyBalance = acct.Balance.Clone()
s.dirtyNonce = new(uint64)
*s.dirtyNonce = acct.Nonce
s.dirtyCodeHash = acct.CodeHash
}
return s
}
// EncodeRLP implements rlp.Encoder.
@@ -219,7 +234,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// 1) resurrect happened, and new slot values were set -- those should
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
if _, destructed := s.db.queryStateObjectsDestruct(s.address); destructed {
return common.Hash{}
}
// If no live objects are available, attempt to use snapshots
@@ -294,6 +309,18 @@ func (s *stateObject) finalise(prefetch bool) {
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
}
}
if s.dirtyNonce != nil {
s.data.Nonce = *s.dirtyNonce
s.dirtyNonce = nil
}
if s.dirtyBalance != nil {
s.data.Balance = s.dirtyBalance
s.dirtyBalance = nil
}
if s.dirtyCodeHash != nil {
s.data.CodeHash = s.dirtyCodeHash
s.dirtyCodeHash = nil
}
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch)
}
@@ -302,6 +329,26 @@ func (s *stateObject) finalise(prefetch bool) {
}
}
func (s *stateObject) finaliseRWSet() {
for key, value := range s.dirtyStorage {
// three are some unclean dirtyStorage from previous reverted txs, it will skip finalise
// so add a new rule, if val has no change, then skip it
if value == s.GetCommittedState(key) {
continue
}
s.db.RecordWrite(types.StorageStateKey(s.address, key), value)
}
if s.dirtyNonce != nil && *s.dirtyNonce != s.data.Nonce {
s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountNonce), *s.dirtyNonce)
}
if s.dirtyBalance != nil && !s.dirtyBalance.Eq(s.data.Balance) {
s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountBalance), s.dirtyBalance.Clone())
}
if s.dirtyCodeHash != nil && !slices.Equal(s.dirtyCodeHash, s.data.CodeHash) {
s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountCodeHash), s.dirtyCodeHash)
}
}
// updateTrie is responsible for persisting cached storage changes into the
// object's storage trie. In case the storage trie is not yet loaded, this
// function will load the trie automatically. If any issues arise during the
@@ -501,13 +548,13 @@ func (s *stateObject) SubBalance(amount *uint256.Int) {
func (s *stateObject) SetBalance(amount *uint256.Int) {
s.db.journal.append(balanceChange{
account: &s.address,
prev: new(uint256.Int).Set(s.data.Balance),
prev: new(uint256.Int).Set(s.Balance()),
})
s.setBalance(amount)
}
func (s *stateObject) setBalance(amount *uint256.Int) {
s.data.Balance = amount
s.dirtyBalance = amount
}
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
@@ -528,6 +575,17 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj.selfDestructed = s.selfDestructed
obj.dirtyCode = s.dirtyCode
obj.deleted = s.deleted
// dirty states
if s.dirtyNonce != nil {
obj.dirtyNonce = new(uint64)
*obj.dirtyNonce = *s.dirtyNonce
}
if s.dirtyBalance != nil {
obj.dirtyBalance = s.dirtyBalance.Clone()
}
obj.dirtyCodeHash = s.dirtyCodeHash
return obj
}
@@ -585,32 +643,44 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
s.code = code
s.data.CodeHash = codeHash[:]
s.dirtyCodeHash = codeHash[:]
s.dirtyCode = true
}
func (s *stateObject) SetNonce(nonce uint64) {
s.db.journal.append(nonceChange{
account: &s.address,
prev: s.data.Nonce,
prev: s.Nonce(),
})
s.setNonce(nonce)
}
func (s *stateObject) setNonce(nonce uint64) {
s.data.Nonce = nonce
s.dirtyNonce = &nonce
}
func (s *stateObject) CodeHash() []byte {
return s.data.CodeHash
if len(s.dirtyCodeHash) > 0 {
return s.dirtyCodeHash
}
ret := s.data.CodeHash
return ret
}
func (s *stateObject) Balance() *uint256.Int {
return s.data.Balance
if s.dirtyBalance != nil {
return s.dirtyBalance
}
ret := s.data.Balance
return ret
}
func (s *stateObject) Nonce() uint64 {
return s.data.Nonce
if s.dirtyNonce != nil {
return *s.dirtyNonce
}
ret := s.data.Nonce
return ret
}
func (s *stateObject) Root() common.Hash {

View File

@@ -95,10 +95,11 @@ type StateDB struct {
// This map holds 'live' objects, which will get modified while processing
// a state transition.
stateObjects map[common.Address]*stateObject
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value
stateObjects map[common.Address]*stateObject
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value
stateObjectsDestructDirty map[common.Address]*types.StateAccount
storagePool *StoragePool // sharedPool to store L1 originStorage of stateObjects
writeOnSharedStorage bool // Write to the shared origin storage of a stateObject while reading from the underlying storage layer.
@@ -115,10 +116,16 @@ type StateDB struct {
refund uint64
// The tx context and all occurred logs in the scope of transaction.
thash common.Hash
txIndex int
logs map[common.Hash][]*types.Log
logSize uint
thash common.Hash
txIndex int
txIncarnation int
logs map[common.Hash][]*types.Log
logSize uint
// parallel EVM related
rwSet *types.RWSet
mvStates *types.MVStates
es *types.ExeStat
// Preimages occurred seen by VM in the scope of block.
preimages map[common.Hash][]byte
@@ -173,23 +180,24 @@ func NewWithSharedPool(root common.Hash, db Database, snaps *snapshot.Tree) (*St
// New creates a new state from a given trie.
func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
sdb := &StateDB{
db: db,
originalRoot: root,
snaps: snaps,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject, defaultNumOfSlots),
stateObjectsPending: make(map[common.Address]struct{}, defaultNumOfSlots),
stateObjectsDirty: make(map[common.Address]struct{}, defaultNumOfSlots),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, defaultNumOfSlots),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
db: db,
originalRoot: root,
snaps: snaps,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject, defaultNumOfSlots),
stateObjectsPending: make(map[common.Address]struct{}, defaultNumOfSlots),
stateObjectsDirty: make(map[common.Address]struct{}, defaultNumOfSlots),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, defaultNumOfSlots),
stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount, defaultNumOfSlots),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
}
if sdb.snaps != nil {
@@ -425,7 +433,10 @@ func (s *StateDB) Empty(addr common.Address) bool {
}
// GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
func (s *StateDB) GetBalance(addr common.Address) (ret *uint256.Int) {
defer func() {
s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), ret)
}()
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Balance()
@@ -434,7 +445,10 @@ func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
}
// GetNonce retrieves the nonce from the given address or 0 if object not found
func (s *StateDB) GetNonce(addr common.Address) uint64 {
func (s *StateDB) GetNonce(addr common.Address) (ret uint64) {
defer func() {
s.RecordRead(types.AccountStateKey(addr, types.AccountNonce), ret)
}()
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Nonce()
@@ -482,7 +496,10 @@ func (s *StateDB) GetCodeSize(addr common.Address) int {
return 0
}
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) {
defer func() {
s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), ret.Bytes())
}()
stateObject := s.getStateObject(addr)
if stateObject != nil {
return common.BytesToHash(stateObject.CodeHash())
@@ -491,7 +508,10 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
}
// GetState retrieves a value from the given account's storage trie.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
func (s *StateDB) GetState(addr common.Address, hash common.Hash) (ret common.Hash) {
defer func() {
s.RecordRead(types.StorageStateKey(addr, hash), ret)
}()
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.GetState(hash)
@@ -500,7 +520,10 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
}
// GetCommittedState retrieves a value from the given account's committed storage trie.
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) (ret common.Hash) {
defer func() {
s.RecordRead(types.StorageStateKey(addr, hash), ret)
}()
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.GetCommittedState(hash)
@@ -529,6 +552,7 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int) {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), stateObject.Balance())
stateObject.AddBalance(amount)
}
}
@@ -537,6 +561,7 @@ func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int) {
func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int) {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), stateObject.Balance())
stateObject.SubBalance(amount)
}
}
@@ -581,8 +606,8 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common
//
// TODO(rjl493456442) this function should only be supported by 'unwritable'
// state and all mutations made should all be discarded afterwards.
if _, ok := s.stateObjectsDestruct[addr]; !ok {
s.stateObjectsDestruct[addr] = nil
if _, ok := s.queryStateObjectsDestruct(addr); !ok {
s.tagStateObjectsDestruct(addr, nil)
}
stateObject := s.getOrNewStateObject(addr)
for k, v := range storage {
@@ -606,7 +631,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
prevbalance: new(uint256.Int).Set(stateObject.Balance()),
})
stateObject.markSelfdestructed()
stateObject.data.Balance = new(uint256.Int)
stateObject.setBalance(new(uint256.Int))
}
func (s *StateDB) Selfdestruct6780(addr common.Address) {
@@ -712,6 +737,7 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
// flag set. This is needed by the state journal to revert to the correct s-
// destructed object instead of wiping all knowledge about the state object.
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
s.RecordRead(types.AccountStateKey(addr, types.AccountSelf), struct{}{})
// Prefer live objects if any is available
if obj := s.stateObjects[addr]; obj != nil {
return obj
@@ -798,9 +824,9 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
// account and storage data should be cleared as well. Note, it must
// be done here, otherwise the destruction event of "original account"
// will be lost.
_, prevdestruct := s.stateObjectsDestruct[prev.address]
_, prevdestruct := s.queryStateObjectsDestruct(prev.address)
if !prevdestruct {
s.stateObjectsDestruct[prev.address] = prev.origin
s.tagStateObjectsDestruct(prev.address, prev.origin)
}
// There may be some cached account/storage data already since IntermediateRoot
// will be called for each transaction before byzantium fork which will always
@@ -841,7 +867,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
func (s *StateDB) CreateAccount(addr common.Address) {
newObj, prev := s.createObject(addr)
if prev != nil {
newObj.setBalance(prev.data.Balance)
newObj.setBalance(prev.Balance())
}
}
@@ -869,15 +895,16 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB {
originalRoot: s.originalRoot,
// fullProcessed: s.fullProcessed,
// pipeCommit: s.pipeCommit,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)),
storagePool: s.storagePool,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)),
stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestructDirty)),
storagePool: s.storagePool,
// writeOnSharedStorage: s.writeOnSharedStorage,
refund: s.refund,
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
@@ -929,6 +956,9 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB {
for addr, value := range s.stateObjectsDestruct {
state.stateObjectsDestruct[addr] = value
}
for addr, value := range s.stateObjectsDestructDirty {
state.stateObjectsDestructDirty[addr] = value
}
// Deep copy the state changes made in the scope of block
// along with their original values.
state.accounts = copySet(s.accounts)
@@ -967,6 +997,12 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB {
// know that they need to explicitly terminate an active copy).
state.prefetcher = state.prefetcher.copy()
}
// parallel EVM related
if s.mvStates != nil {
state.mvStates = s.mvStates
}
return state
}
@@ -1015,6 +1051,11 @@ func (s *StateDB) WaitPipeVerification() error {
// into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties))
// finalise stateObjectsDestruct
for addr, acc := range s.stateObjectsDestructDirty {
s.stateObjectsDestruct[addr] = acc
}
s.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount)
for addr := range s.journal.dirties {
obj, exist := s.stateObjects[addr]
if !exist {
@@ -1249,9 +1290,10 @@ func (s *StateDB) StateIntermediateRoot() common.Hash {
// SetTxContext sets the current transaction hash and index which are
// used when the EVM emits new state logs. It should be invoked before
// transaction execution.
func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
func (s *StateDB) SetTxContext(thash common.Hash, txIndex int, incarnation int) {
s.thash = thash
s.txIndex = ti
s.txIndex = txIndex
s.txIncarnation = incarnation
s.accessList = nil // can't delete this line now, because StateDB.Prepare is not called before processsing a system transaction
}
@@ -1900,6 +1942,131 @@ func (s *StateDB) GetSnap() snapshot.Snapshot {
return s.snap
}
func (s *StateDB) BeforeTxTransition() {
log.Debug("BeforeTxTransition", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil)
if s.mvStates == nil {
return
}
s.rwSet = types.NewRWSet(types.StateVersion{
TxIndex: s.txIndex,
TxIncarnation: s.txIncarnation,
})
}
func (s *StateDB) BeginTxStat(index int) {
if s.mvStates == nil {
return
}
s.es = types.NewExeStat(index).Begin()
}
func (s *StateDB) StopTxStat(usedGas uint64) {
if s.mvStates == nil {
return
}
// record stat first
if s.es != nil {
s.es.Done().WithGas(usedGas).WithRead(len(s.rwSet.ReadSet()))
}
}
func (s *StateDB) RecordRead(key types.RWKey, val interface{}) {
if s.mvStates == nil || s.rwSet == nil {
return
}
// TODO: read from MVStates, record with ver
s.rwSet.RecordRead(key, types.StateVersion{
TxIndex: -1,
}, val)
}
func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) {
if s.mvStates == nil || s.rwSet == nil {
return
}
s.rwSet.RecordWrite(key, val)
}
func (s *StateDB) ResetMVStates(txCount int) {
log.Debug("ResetMVStates", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil)
s.mvStates = types.NewMVStates(txCount)
s.rwSet = nil
}
func (s *StateDB) FinaliseRWSet() error {
log.Debug("FinaliseRWSet", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil)
if s.mvStates == nil || s.rwSet == nil {
return nil
}
// finalise stateObjectsDestruct
for addr, acc := range s.stateObjectsDestructDirty {
s.stateObjectsDestruct[addr] = acc
s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{})
}
for addr := range s.journal.dirties {
obj, exist := s.stateObjects[addr]
if !exist {
continue
}
if obj.selfDestructed || obj.empty() {
// We need to maintain account deletions explicitly (will remain
// set indefinitely). Note only the first occurred self-destruct
// event is tracked.
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
log.Debug("FinaliseRWSet find Destruct", "tx", s.txIndex, "addr", addr, "selfDestructed", obj.selfDestructed)
s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{})
}
} else {
// finalise account & storages
obj.finaliseRWSet()
}
}
ver := types.StateVersion{
TxIndex: s.txIndex,
TxIncarnation: s.txIncarnation,
}
if ver != s.rwSet.Version() {
return errors.New("you finalize a wrong ver of RWSet")
}
log.Debug("FinaliseRWSet", "rwset", s.rwSet)
return s.mvStates.FulfillRWSet(s.rwSet, s.es)
}
func (s *StateDB) queryStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) {
if acc, ok := s.stateObjectsDestructDirty[addr]; ok {
return acc, ok
}
acc, ok := s.stateObjectsDestruct[addr]
return acc, ok
}
func (s *StateDB) tagStateObjectsDestruct(addr common.Address, acc *types.StateAccount) {
s.stateObjectsDestructDirty[addr] = acc
}
func (s *StateDB) deleteStateObjectsDestruct(addr common.Address) {
delete(s.stateObjectsDestructDirty, addr)
}
func (s *StateDB) MVStates2TxDAG() (*types.TxDAG, []*types.ExeStat) {
if s.mvStates == nil {
return nil, nil
}
return s.mvStates.ResolveTxDAG(), s.mvStates.Stats()
}
func (s *StateDB) RecordSystemTxRWSet(index int) {
if s.mvStates == nil {
return
}
s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{
TxIndex: index,
TxIncarnation: 0,
}).WithSerialFlag(), types.NewExeStat(index).WithSerialFlag())
}
// copySet returns a deep-copied set.
func copySet[k comparable](set map[k][]byte) map[k][]byte {
copied := make(map[k][]byte, len(set))

View File

@@ -76,7 +76,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
if err != nil {
return // Also invalid block, bail out
}
newStatedb.SetTxContext(tx.Hash(), txIndex)
newStatedb.SetTxContext(tx.Hash(), txIndex, 0)
precacheTransaction(msg, p.config, gaspool, newStatedb, header, evm)
case <-interruptCh:
@@ -125,7 +125,7 @@ func (p *statePrefetcher) PrefetchMining(txs TransactionsByPriceAndNonce, header
return // Also invalid block, bail out
}
idx++
newStatedb.SetTxContext(tx.Hash(), idx)
newStatedb.SetTxContext(tx.Hash(), idx, 0)
precacheTransaction(msg, p.config, gaspool, newStatedb, header, evm)
gaspool = new(GasPool).AddGas(gasLimit)
case <-stopCh:

View File

@@ -19,8 +19,6 @@ package core
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
@@ -29,7 +27,11 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"math/big"
"time"
)
// StateProcessor is a basic Processor, which takes care of transitioning
@@ -51,6 +53,12 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
}
}
var (
dagExecutionTimer = metrics.NewRegisteredTimer("dag/executiontime", nil)
dagAccountReadTimer = metrics.NewRegisteredTimer("dag/accountreadtime", nil)
dagStorageReadTimer = metrics.NewRegisteredTimer("dag/storagereadtime", nil)
)
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
@@ -59,6 +67,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*state.StateDB, types.Receipts, []*types.Log, uint64, error) {
var (
usedGas = new(uint64)
header = block.Header()
@@ -99,16 +108,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// initialise bloom processors
bloomProcessors := NewAsyncReceiptBloomGenerator(txNum)
statedb.MarkFullProcessed()
statedb.ResetMVStates(len(block.Transactions()))
log.Debug("ResetMVStates", "block", block.NumberU64(), "txs", len(block.Transactions()))
// usually do have two tx, one for validator set contract, another for system reward contract.
systemTxs := make([]*types.Transaction, 0, 2)
start := time.Now()
for i, tx := range block.Transactions() {
statedb.BeginTxStat(i)
if isPoSA {
if isSystemTx, err := posa.IsSystemTransaction(tx, block.Header()); err != nil {
bloomProcessors.Close()
return statedb, nil, nil, 0, err
} else if isSystemTx {
statedb.RecordSystemTxRWSet(i)
systemTxs = append(systemTxs, tx)
continue
}
@@ -125,7 +138,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
bloomProcessors.Close()
return statedb, nil, nil, 0, err
}
statedb.SetTxContext(tx.Hash(), i)
statedb.SetTxContext(tx.Hash(), i, 0)
receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, bloomProcessors)
if err != nil {
@@ -134,7 +147,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}
commonTxs = append(commonTxs, tx)
receipts = append(receipts, receipt)
statedb.StopTxStat(receipt.GasUsed)
}
eTime := time.Since(start)
// this bloomProcessors may take ~20ms
bloomProcessors.Close()
// Fail if Shanghai not enabled and len(withdrawals) is non-zero.
@@ -143,7 +159,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, nil, nil, 0, errors.New("withdrawals before shanghai")
}
// TODO: temporary add time metrics
dag, exrStats := statedb.MVStates2TxDAG()
//log.Info("MVStates2TxDAG", "block", block.NumberU64(), "tx", len(block.Transactions()), "dag", dag)
fmt.Printf("MVStates2TxDAG, block: %v|%v, tx: %v, time: %v\n", block.NumberU64(), block.Hash(), len(block.Transactions()), time.Now().Format(time.DateTime))
fmt.Print(types.EvaluateTxDAGPerformance(dag, exrStats))
fmt.Printf("block: %v, execution: %.2fms, accountRead: %.2fms, storageRead: %.2fms\n",
block.NumberU64(), float64(eTime.Microseconds())/1000, float64((statedb.SnapshotAccountReads+statedb.AccountReads).Microseconds())/1000,
float64((statedb.SnapshotStorageReads+statedb.StorageReads).Microseconds())/1000)
dagExecutionTimer.Update(eTime)
dagAccountReadTimer.Update(statedb.SnapshotAccountReads + statedb.AccountReads)
dagStorageReadTimer.Update(statedb.SnapshotStorageReads + statedb.StorageReads)
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
// TODO: system txs must execute at last
err := p.engine.Finalize(p.bc, header, statedb, &commonTxs, block.Uncles(), withdrawals, &receipts, &systemTxs, usedGas)
if err != nil {
return statedb, receipts, allLogs, *usedGas, err

View File

@@ -367,6 +367,8 @@ func (st *StateTransition) preCheck() error {
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// start record rw set in here
st.state.BeforeTxTransition()
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
@@ -446,6 +448,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
}
// stop record rw set in here
if err := st.state.FinaliseRWSet(); err != nil {
return nil, err
}
var gasRefund uint64
if !rules.IsLondon {
// Before EIP-3529: refunds were capped to gasUsed / 2

View File

@@ -212,7 +212,7 @@ func TestTxIndexer(t *testing.T) {
}
for _, c := range cases {
frdir := t.TempDir()
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false)
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false, false)
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0))
// Index the initial blocks from ancient store

View File

@@ -6,6 +6,8 @@ import (
"sync/atomic"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
@@ -40,6 +42,12 @@ func (b *BidArgs) ToBid(builder common.Address, signer Signer) (*Bid, error) {
return nil, err
}
if len(b.RawBid.UnRevertible) > len(txs) {
return nil, fmt.Errorf("expect NonRevertible no more than %d", len(txs))
}
unRevertibleHashes := mapset.NewThreadUnsafeSetWithSize[common.Hash](len(b.RawBid.UnRevertible))
unRevertibleHashes.Append(b.RawBid.UnRevertible...)
if len(b.PayBidTx) != 0 {
var payBidTx = new(Transaction)
err = payBidTx.UnmarshalBinary(b.PayBidTx)
@@ -51,14 +59,15 @@ func (b *BidArgs) ToBid(builder common.Address, signer Signer) (*Bid, error) {
}
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,
Builder: builder,
BlockNumber: b.RawBid.BlockNumber,
ParentHash: b.RawBid.ParentHash,
Txs: txs,
UnRevertible: unRevertibleHashes,
GasUsed: b.RawBid.GasUsed + b.PayBidTxGasUsed,
GasFee: b.RawBid.GasFee,
BuilderFee: b.RawBid.BuilderFee,
rawBid: *b.RawBid,
}
if bid.BuilderFee == nil {
@@ -70,12 +79,13 @@ func (b *BidArgs) ToBid(builder common.Address, signer Signer) (*Bid, error) {
// 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"`
BlockNumber uint64 `json:"blockNumber"`
ParentHash common.Hash `json:"parentHash"`
Txs []hexutil.Bytes `json:"txs"`
UnRevertible []common.Hash `json:"unRevertible"`
GasUsed uint64 `json:"gasUsed"`
GasFee *big.Int `json:"gasFee"`
BuilderFee *big.Int `json:"builderFee"`
hash atomic.Value
}
@@ -154,13 +164,14 @@ func (b *RawBid) Hash() common.Hash {
// 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
Builder common.Address
BlockNumber uint64
ParentHash common.Hash
Txs Transactions
UnRevertible mapset.Set[common.Hash]
GasUsed uint64
GasFee *big.Int
BuilderFee *big.Int
rawBid RawBid
}

View File

@@ -204,6 +204,8 @@ type Body struct {
Transactions []*Transaction
Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"`
// TODO: add TxDAG in block body
//TxDAG *TxDAG `rlp:"optional"`
}
// Block represents an Ethereum block.

698
core/types/dag.go Normal file
View File

@@ -0,0 +1,698 @@
package types
import (
"encoding/hex"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/holiman/uint256"
"golang.org/x/exp/slices"
"sort"
"strings"
"sync"
"time"
)
// TxDep store the current tx dependency relation with other txs
type TxDep struct {
// It describes the Relation with below txs
// 0: this tx depends on below txs
// 1: this transaction does not depend on below txs, all other previous txs depend on
Relation uint8
TxIndexes []int
}
func (d *TxDep) AppendDep(i int) {
d.TxIndexes = append(d.TxIndexes, i)
}
func (d *TxDep) Exist(i int) bool {
for _, index := range d.TxIndexes {
if index == i {
return true
}
}
return false
}
// TxDAG indicate how to use the dependency of txs
type TxDAG struct {
// The TxDAG type
// 0: delay the distribution of GasFee, it will ignore all gas fee distribution when tx execute
// 1: timely distribution of transaction fees, it will keep partial serial execution when tx cannot delay the distribution
Type uint8
// Tx Dependency List, the list index is equal to TxIndex
TxDeps []TxDep
// It indicates the scheduling priority of the transactions
SchedulePriority []int
}
func NewTxDAG(txLen int) *TxDAG {
return &TxDAG{
Type: 0,
TxDeps: make([]TxDep, txLen),
}
}
func (d *TxDAG) String() string {
builder := strings.Builder{}
exePaths := d.travelExecutionPaths()
for _, path := range exePaths {
builder.WriteString(fmt.Sprintf("%v\n", path))
}
return builder.String()
}
func (d *TxDAG) travelExecutionPaths() [][]int {
// regenerate TxDAG
nd := NewTxDAG(len(d.TxDeps))
for i, txDep := range d.TxDeps {
nd.TxDeps[i].Relation = 0
if txDep.Relation == 0 {
nd.TxDeps[i] = txDep
continue
}
// recover to relation 0
for j := 0; j < i; j++ {
if !txDep.Exist(j) {
nd.TxDeps[i].AppendDep(j)
}
}
}
exePaths := make([][]int, 0)
// travel tx deps with BFS
for i := 0; i < len(nd.TxDeps); i++ {
exePaths = append(exePaths, travelTargetPath(nd.TxDeps, i))
}
return exePaths
}
var (
longestTimeTimer = metrics.NewRegisteredTimer("dag/longesttime", nil)
longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil)
serialTimeTimer = metrics.NewRegisteredTimer("dag/serialtime", nil)
totalTxMeter = metrics.NewRegisteredMeter("dag/txcnt", nil)
totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcntcnt", nil)
total2DepMeter = metrics.NewRegisteredMeter("dag/2depcntcnt", nil)
total4DepMeter = metrics.NewRegisteredMeter("dag/4depcntcnt", nil)
total8DepMeter = metrics.NewRegisteredMeter("dag/8depcntcnt", nil)
total16DepMeter = metrics.NewRegisteredMeter("dag/16depcntcnt", nil)
total32DepMeter = metrics.NewRegisteredMeter("dag/32depcntcnt", nil)
)
func EvaluateTxDAGPerformance(dag *TxDAG, stats []*ExeStat) string {
if len(stats) != len(dag.TxDeps) || len(dag.TxDeps) == 0 {
return ""
}
sb := strings.Builder{}
sb.WriteString("TxDAG:\n")
for i, dep := range dag.TxDeps {
if stats[i].mustSerialFlag {
continue
}
sb.WriteString(fmt.Sprintf("%v: %v\n", i, dep.TxIndexes))
}
sb.WriteString("Parallel Execution Path:\n")
paths := dag.travelExecutionPaths()
// Attention: this is based on best schedule, it will reduce a lot by executing previous txs in parallel
// It assumes that there is no parallel thread limit
var (
maxGasIndex int
maxGas uint64
maxTimeIndex int
maxTime time.Duration
txTimes = make([]time.Duration, len(dag.TxDeps))
txGases = make([]uint64, len(dag.TxDeps))
txReads = make([]int, len(dag.TxDeps))
noDepdencyCount int
)
totalTxMeter.Mark(int64(len(dag.TxDeps)))
for i, path := range paths {
if stats[i].mustSerialFlag {
continue
}
if len(path) <= 1 {
noDepdencyCount++
totalNoDepMeter.Mark(1)
}
if len(path) <= 3 {
total2DepMeter.Mark(1)
}
if len(path) <= 5 {
total4DepMeter.Mark(1)
}
if len(path) <= 9 {
total8DepMeter.Mark(1)
}
if len(path) <= 17 {
total16DepMeter.Mark(1)
}
if len(path) <= 33 {
total32DepMeter.Mark(1)
}
// find the biggest cost time from dependency txs
for j := 0; j < len(path)-1; j++ {
prev := path[j]
if txTimes[prev] > txTimes[i] {
txTimes[i] = txTimes[prev]
}
if txGases[prev] > txGases[i] {
txGases[i] = txGases[prev]
}
if txReads[prev] > txReads[i] {
txReads[i] = txReads[prev]
}
}
txTimes[i] += stats[i].costTime
txGases[i] += stats[i].usedGas
txReads[i] += stats[i].readCount
//sb.WriteString(fmt.Sprintf("Tx%v, %.2fms|%vgas|%vreads\npath: %v\n", i, float64(txTimes[i].Microseconds())/1000, txGases[i], txReads[i], path))
sb.WriteString(fmt.Sprintf("%v: %v\n", i, path))
// try to find max gas
if txGases[i] > maxGas {
maxGas = txGases[i]
maxGasIndex = i
}
if txTimes[i] > maxTime {
maxTime = txTimes[i]
maxTimeIndex = i
}
}
sb.WriteString(fmt.Sprintf("LargestGasPath: %.2fms|%vgas|%vreads\npath: %v\n", float64(txTimes[maxGasIndex].Microseconds())/1000, txGases[maxGasIndex], txReads[maxGasIndex], paths[maxGasIndex]))
sb.WriteString(fmt.Sprintf("LongestTimePath: %.2fms|%vgas|%vreads\npath: %v\n", float64(txTimes[maxTimeIndex].Microseconds())/1000, txGases[maxTimeIndex], txReads[maxTimeIndex], paths[maxTimeIndex]))
longestTimeTimer.Update(txTimes[maxTimeIndex])
longestGasTimer.Update(txTimes[maxGasIndex])
// serial path
var (
sTime time.Duration
sGas uint64
sRead int
sPath []int
)
for i, stat := range stats {
if stat.mustSerialFlag {
continue
}
sPath = append(sPath, i)
sTime += stat.costTime
sGas += stat.usedGas
sRead += stat.readCount
}
if sTime == 0 {
return ""
}
sb.WriteString(fmt.Sprintf("SerialPath: %.2fms|%vgas|%vreads\npath: %v\n", float64(sTime.Microseconds())/1000, sGas, sRead, sPath))
maxParaTime := txTimes[maxTimeIndex]
sb.WriteString(fmt.Sprintf("Estimated saving: %.2fms, %.2f%%, %.2fX, noDepCnt: %v|%.2f%%\n",
float64((sTime-maxParaTime).Microseconds())/1000, float64(sTime-maxParaTime)/float64(sTime)*100,
float64(sTime)/float64(maxParaTime), noDepdencyCount, float64(noDepdencyCount)/float64(len(dag.TxDeps))*100))
serialTimeTimer.Update(sTime)
return sb.String()
}
func travelTargetPath(deps []TxDep, from int) []int {
q := make([]int, 0, len(deps))
path := make([]int, 0, len(deps))
q = append(q, from)
path = append(path, from)
for len(q) > 0 {
t := make([]int, 0, len(deps))
for _, i := range q {
for _, dep := range deps[i].TxIndexes {
if !slices.Contains(path, dep) {
path = append(path, dep)
t = append(t, dep)
}
}
}
q = t
}
sort.Ints(path)
return path
}
type ValidatorExtraItem struct {
ValidatorAddress common.Address
VoteAddress BLSPublicKey
}
type HeaderCustomExtra struct {
ValidatorSet ValidatorExtraItem
TxDAG TxDAG
}
// StateVersion record specific TxIndex & TxIncarnation
// if TxIndex equals to -1, it means the state read from DB.
type StateVersion struct {
TxIndex int
TxIncarnation int
}
// ReadRecord keep read value & its version
type ReadRecord struct {
StateVersion
Val interface{}
}
// WriteRecord keep latest state value & change count
type WriteRecord struct {
Val interface{}
}
// RWSet record all read & write set in txs
// Attention: this is not a concurrent safety structure
type RWSet struct {
ver StateVersion
readSet map[RWKey]*ReadRecord
writeSet map[RWKey]*WriteRecord
// some flags
mustSerial bool
}
func NewRWSet(ver StateVersion) *RWSet {
return &RWSet{
ver: ver,
readSet: make(map[RWKey]*ReadRecord),
writeSet: make(map[RWKey]*WriteRecord),
}
}
func (s *RWSet) RecordRead(key RWKey, ver StateVersion, val interface{}) {
// only record the first read version
if _, exist := s.readSet[key]; exist {
return
}
s.readSet[key] = &ReadRecord{
StateVersion: ver,
Val: val,
}
}
func (s *RWSet) RecordWrite(key RWKey, val interface{}) {
wr, exist := s.writeSet[key]
if !exist {
s.writeSet[key] = &WriteRecord{
Val: val,
}
return
}
wr.Val = val
}
func (s *RWSet) Version() StateVersion {
return s.ver
}
func (s *RWSet) ReadSet() map[RWKey]*ReadRecord {
return s.readSet
}
func (s *RWSet) WriteSet() map[RWKey]*WriteRecord {
return s.writeSet
}
func (s *RWSet) WithSerialFlag() *RWSet {
s.mustSerial = true
return s
}
func (s *RWSet) String() string {
builder := strings.Builder{}
builder.WriteString(fmt.Sprintf("tx: %v, inc: %v\nreadSet: [", s.ver.TxIndex, s.ver.TxIncarnation))
i := 0
for key, _ := range s.readSet {
if i > 0 {
builder.WriteString(fmt.Sprintf(", %v", key.String()))
continue
}
builder.WriteString(fmt.Sprintf("%v", key.String()))
i++
}
builder.WriteString("]\nwriteSet: [")
i = 0
for key, _ := range s.writeSet {
if i > 0 {
builder.WriteString(fmt.Sprintf(", %v", key.String()))
continue
}
builder.WriteString(fmt.Sprintf("%v", key.String()))
i++
}
builder.WriteString("]\n")
return builder.String()
}
const (
AccountStatePrefix = 'a'
StorageStatePrefix = 's'
)
type RWKey [1 + common.AddressLength + common.HashLength]byte
type AccountState byte
const (
AccountSelf AccountState = iota
AccountNonce
AccountBalance
AccountCodeHash
AccountSuicide
)
func AccountStateKey(account common.Address, state AccountState) RWKey {
var key RWKey
key[0] = AccountStatePrefix
copy(key[1:], account.Bytes())
key[1+common.AddressLength] = byte(state)
return key
}
func StorageStateKey(account common.Address, state common.Hash) RWKey {
var key RWKey
key[0] = StorageStatePrefix
copy(key[1:], account.Bytes())
copy(key[1+common.AddressLength:], state.Bytes())
return key
}
func (key *RWKey) IsAccountState() (bool, AccountState) {
return AccountStatePrefix == key[0], AccountState(key[1+common.AddressLength])
}
func (key *RWKey) IsAccountSelf() bool {
ok, s := key.IsAccountState()
if !ok {
return false
}
return s == AccountSelf
}
func (key *RWKey) IsAccountSuicide() bool {
ok, s := key.IsAccountState()
if !ok {
return false
}
return s == AccountSuicide
}
func (key *RWKey) ToAccountSelf() RWKey {
return AccountStateKey(key.Addr(), AccountSelf)
}
func (key *RWKey) IsStorageState() bool {
return StorageStatePrefix == key[0]
}
func (key *RWKey) String() string {
return hex.EncodeToString(key[:])
}
func (key *RWKey) Addr() common.Address {
return common.BytesToAddress(key[1 : 1+common.AddressLength])
}
type PendingWrite struct {
Ver StateVersion
Val interface{}
}
func NewPendingWrite(ver StateVersion, wr *WriteRecord) *PendingWrite {
return &PendingWrite{
Ver: ver,
Val: wr.Val,
}
}
func (w *PendingWrite) TxIndex() int {
return w.Ver.TxIndex
}
func (w *PendingWrite) TxIncarnation() int {
return w.Ver.TxIncarnation
}
type PendingWrites struct {
list []*PendingWrite
}
func NewPendingWrites() *PendingWrites {
return &PendingWrites{
list: make([]*PendingWrite, 0),
}
}
func (w *PendingWrites) Append(pw *PendingWrite) {
if i, found := w.SearchTxIndex(pw.TxIndex()); found {
w.list[i] = pw
return
}
w.list = append(w.list, pw)
for i := len(w.list) - 1; i > 0; i-- {
if w.list[i].TxIndex() > w.list[i-1].TxIndex() {
break
}
w.list[i-1], w.list[i] = w.list[i], w.list[i-1]
}
}
func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) {
n := len(w.list)
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1)
// i ≤ h < j
if w.list[h].TxIndex() < txIndex {
i = h + 1
} else {
j = h
}
}
return i, i < n && w.list[i].TxIndex() == txIndex
}
func (w *PendingWrites) FindLastWrite(txIndex int) *PendingWrite {
var i, _ = w.SearchTxIndex(txIndex)
for j := i - 1; j >= 0; j-- {
if w.list[j].TxIndex() < txIndex {
return w.list[j]
}
}
return nil
}
type MVStates struct {
rwSets []*RWSet
stats []*ExeStat
pendingWriteSet map[RWKey]*PendingWrites
lock sync.RWMutex
}
func NewMVStates(txCount int) *MVStates {
return &MVStates{
rwSets: make([]*RWSet, txCount),
stats: make([]*ExeStat, txCount),
pendingWriteSet: make(map[RWKey]*PendingWrites, txCount*8),
}
}
func (s *MVStates) RWSets() []*RWSet {
s.lock.RLock()
defer s.lock.RUnlock()
return s.rwSets
}
func (s *MVStates) Stats() []*ExeStat {
s.lock.RLock()
defer s.lock.RUnlock()
return s.stats
}
func (s *MVStates) RWSet(index int) *RWSet {
s.lock.RLock()
defer s.lock.RUnlock()
return s.rwSets[index]
}
func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error {
s.lock.Lock()
defer s.lock.Unlock()
index := rwSet.ver.TxIndex
if index >= len(s.rwSets) {
return errors.New("refill out of bound")
}
if s := s.rwSets[index]; s != nil {
return errors.New("refill a exist RWSet")
}
if stat != nil {
if stat.txIndex != index {
return errors.New("wrong execution stat")
}
s.stats[index] = stat
}
for k, v := range rwSet.writeSet {
// ignore no changed write record
checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet)
// this will be handled by state object
//if rwSet.readSet[k] != nil && isEqualRWVal(k, rwSet.readSet[k].Val, v.Val) {
// delete(rwSet.writeSet, k)
// continue
//}
if _, exist := s.pendingWriteSet[k]; !exist {
s.pendingWriteSet[k] = NewPendingWrites()
}
s.pendingWriteSet[k].Append(NewPendingWrite(rwSet.ver, v))
}
s.rwSets[index] = rwSet
return nil
}
func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*ReadRecord, writeSet map[RWKey]*WriteRecord) bool {
var (
readOk bool
writeOk bool
r *WriteRecord
)
if k.IsAccountSuicide() {
_, readOk = readSet[k.ToAccountSelf()]
} else {
_, readOk = readSet[k]
}
r, writeOk = writeSet[k]
if readOk != writeOk {
// check if it's correct? read nil, write non-nil
log.Info("checkRWSetInconsistent find inconsistent", "tx", index, "k", k.String(), "read", readOk, "write", writeOk, "val", r.Val)
return true
}
return false
}
func isEqualRWVal(key RWKey, src interface{}, compared interface{}) bool {
if ok, state := key.IsAccountState(); ok {
switch state {
case AccountBalance:
if src != nil && compared != nil {
return equalUint256(src.(*uint256.Int), compared.(*uint256.Int))
}
return src == compared
case AccountNonce:
return src.(uint64) == compared.(uint64)
case AccountCodeHash:
if src != nil && compared != nil {
return slices.Equal(src.([]byte), compared.([]byte))
}
return src == compared
}
return false
}
if src != nil && compared != nil {
return src.(common.Hash) == compared.(common.Hash)
}
return src == compared
}
func equalUint256(s, c *uint256.Int) bool {
if s != nil && c != nil {
return s.Eq(c)
}
return s == c
}
func (s *MVStates) ResolveTxDAG() *TxDAG {
rwSets := s.RWSets()
txDAG := NewTxDAG(len(rwSets))
for i := len(rwSets) - 1; i >= 0; i-- {
txDAG.TxDeps[i].TxIndexes = []int{}
if rwSets[i].mustSerial {
txDAG.TxDeps[i].Relation = 1
continue
}
readSet := rwSets[i].ReadSet()
// TODO: check if there are RW with system address
// check if there has written op before i
for j := 0; j < i; j++ {
if checkDependency(rwSets[j].writeSet, readSet) {
txDAG.TxDeps[i].AppendDep(j)
}
}
}
return txDAG
}
func checkDependency(writeSet map[RWKey]*WriteRecord, readSet map[RWKey]*ReadRecord) bool {
// check tx dependency, only check key, skip version
for k, _ := range writeSet {
// check suicide, add read address flag, it only for check suicide quickly, and cannot for other scenarios.
if k.IsAccountSuicide() {
if _, ok := readSet[k.ToAccountSelf()]; ok {
return true
}
continue
}
if _, ok := readSet[k]; ok {
return true
}
}
return false
}
type ExeStat struct {
txIndex int
usedGas uint64
readCount int
startTime time.Time
costTime time.Duration
// TODO: consider system tx, gas fee issues, may need to use different flag
mustSerialFlag bool
}
func NewExeStat(txIndex int) *ExeStat {
return &ExeStat{
txIndex: txIndex,
}
}
func (s *ExeStat) Begin() *ExeStat {
s.startTime = time.Now()
return s
}
func (s *ExeStat) Done() *ExeStat {
s.costTime = time.Since(s.startTime)
return s
}
func (s *ExeStat) WithSerialFlag() *ExeStat {
s.mustSerialFlag = true
return s
}
func (s *ExeStat) WithGas(gas uint64) *ExeStat {
s.usedGas = gas
return s
}
func (s *ExeStat) WithRead(rc int) *ExeStat {
s.readCount = rc
return s
}

239
core/types/dag_test.go Normal file
View File

@@ -0,0 +1,239 @@
package types
import (
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
"testing"
)
var (
mockAddr = common.HexToAddress("0x482bA86399ab6Dcbe54071f8d22258688B4509b1")
mockHash = common.HexToHash("0xdc13f8d7bdb8ec4de02cd4a50a1aa2ab73ec8814e0cdb550341623be3dd8ab7a")
)
func TestTxDAG(t *testing.T) {
dag := mockSimpleDAG()
t.Log(dag.String())
dag = mockSystemTxDAG()
t.Log(dag.String())
}
func TestEvaluateTxDAG(t *testing.T) {
dag := mockSystemTxDAG()
stats := make([]*ExeStat, len(dag.TxDeps))
for i, dep := range dag.TxDeps {
stats[i] = NewExeStat(i).WithGas(uint64(i)).WithRead(i)
stats[i].costTime = int64(i)
if dep.Relation == 1 {
stats[i].WithSerialFlag()
}
}
t.Log(EvaluateTxDAGPerformance(dag, stats))
}
func TestSimpleMVStates2TxDAG(t *testing.T) {
ms := NewMVStates(10)
ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"})
ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"})
ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"})
ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"})
ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"})
ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"})
ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"})
ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"})
ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"})
ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"})
dag := ms.ResolveTxDAG()
require.Equal(t, mockSimpleDAG(), dag)
t.Log(dag.String())
}
func TestSystemTxMVStates2TxDAG(t *testing.T) {
ms := NewMVStates(12)
ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"})
ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"})
ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"})
ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"})
ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"})
ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"})
ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"})
ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"})
ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"})
ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"})
ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag()
ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag()
dag := ms.ResolveTxDAG()
require.Equal(t, mockSystemTxDAG(), dag)
t.Log(dag.String())
}
func TestIsEqualRWVal(t *testing.T) {
tests := []struct {
key RWKey
src interface{}
compared interface{}
isEqual bool
}{
{
key: AccountStateKey(mockAddr, AccountNonce),
src: uint64(0),
compared: uint64(0),
isEqual: true,
},
{
key: AccountStateKey(mockAddr, AccountNonce),
src: uint64(0),
compared: uint64(1),
isEqual: false,
},
{
key: AccountStateKey(mockAddr, AccountBalance),
src: new(uint256.Int).SetUint64(1),
compared: new(uint256.Int).SetUint64(1),
isEqual: true,
},
{
key: AccountStateKey(mockAddr, AccountBalance),
src: nil,
compared: new(uint256.Int).SetUint64(1),
isEqual: false,
},
{
key: AccountStateKey(mockAddr, AccountBalance),
src: (*uint256.Int)(nil),
compared: new(uint256.Int).SetUint64(1),
isEqual: false,
},
{
key: AccountStateKey(mockAddr, AccountBalance),
src: (*uint256.Int)(nil),
compared: (*uint256.Int)(nil),
isEqual: true,
},
{
key: AccountStateKey(mockAddr, AccountCodeHash),
src: []byte{1},
compared: []byte{1},
isEqual: true,
},
{
key: AccountStateKey(mockAddr, AccountCodeHash),
src: nil,
compared: []byte{1},
isEqual: false,
},
{
key: AccountStateKey(mockAddr, AccountCodeHash),
src: ([]byte)(nil),
compared: []byte{1},
isEqual: false,
},
{
key: AccountStateKey(mockAddr, AccountCodeHash),
src: ([]byte)(nil),
compared: ([]byte)(nil),
isEqual: true,
},
{
key: AccountStateKey(mockAddr, AccountSuicide),
src: struct{}{},
compared: struct{}{},
isEqual: false,
},
{
key: AccountStateKey(mockAddr, AccountSuicide),
src: nil,
compared: struct{}{},
isEqual: false,
},
{
key: StorageStateKey(mockAddr, mockHash),
src: mockHash,
compared: mockHash,
isEqual: true,
},
{
key: StorageStateKey(mockAddr, mockHash),
src: nil,
compared: mockHash,
isEqual: false,
},
}
for i, item := range tests {
require.Equal(t, item.isEqual, isEqualRWVal(item.key, item.src, item.compared), i)
}
}
func mockSimpleDAG() *TxDAG {
dag := NewTxDAG(10)
dag.TxDeps[0].TxIndexes = []int{}
dag.TxDeps[1].TxIndexes = []int{}
dag.TxDeps[2].TxIndexes = []int{}
dag.TxDeps[3].TxIndexes = []int{0}
dag.TxDeps[4].TxIndexes = []int{0}
dag.TxDeps[5].TxIndexes = []int{1, 2}
dag.TxDeps[6].TxIndexes = []int{2, 5}
dag.TxDeps[7].TxIndexes = []int{6}
dag.TxDeps[8].TxIndexes = []int{}
dag.TxDeps[9].TxIndexes = []int{8}
return dag
}
func mockSystemTxDAG() *TxDAG {
dag := NewTxDAG(12)
dag.TxDeps[0].TxIndexes = []int{}
dag.TxDeps[1].TxIndexes = []int{}
dag.TxDeps[2].TxIndexes = []int{}
dag.TxDeps[3].TxIndexes = []int{0}
dag.TxDeps[4].TxIndexes = []int{0}
dag.TxDeps[5].TxIndexes = []int{1, 2}
dag.TxDeps[6].TxIndexes = []int{2, 5}
dag.TxDeps[7].TxIndexes = []int{6}
dag.TxDeps[8].TxIndexes = []int{}
dag.TxDeps[9].TxIndexes = []int{8}
dag.TxDeps[10] = TxDep{
Relation: 1,
TxIndexes: []int{},
}
dag.TxDeps[11] = TxDep{
Relation: 1,
TxIndexes: []int{},
}
return dag
}
func mockRWSet(index int, read []string, write []string) *RWSet {
ver := StateVersion{
TxIndex: index,
}
set := NewRWSet(ver)
for _, k := range read {
key := RWKey{}
if len(k) > len(key) {
k = k[:len(key)]
}
copy(key[:], k)
set.readSet[key] = &ReadRecord{
StateVersion: ver,
Val: struct{}{},
}
}
for _, k := range write {
key := RWKey{}
if len(k) > len(key) {
k = k[:len(key)]
}
copy(key[:], k)
set.writeSet[key] = &WriteRecord{
Val: struct{}{},
}
}
return set
}

View File

@@ -79,6 +79,10 @@ type StateDB interface {
AddLog(*types.Log)
AddPreimage(common.Hash, []byte)
// parallel DAG related
BeforeTxTransition()
FinaliseRWSet() error
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM

View File

@@ -24,7 +24,6 @@ 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"
@@ -142,31 +141,3 @@ 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)
}

View File

@@ -89,3 +89,31 @@ func (api *MinerAPI) SetEtherbase(etherbase common.Address) bool {
func (api *MinerAPI) SetRecommitInterval(interval int) {
api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
}
// MevRunning returns true if the validator accept bids from builder
func (api *MinerAPI) MevRunning() bool {
return api.e.APIBackend.MevRunning()
}
// StartMev starts mev. It notifies the miner to start to receive bids.
func (api *MinerAPI) StartMev() {
api.e.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 *MinerAPI) StopMev() {
api.e.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 *MinerAPI) AddBuilder(builder common.Address, url string) error {
return api.e.APIBackend.AddBuilder(builder, url)
}
// RemoveBuilder removes a builder from the bid simulator.
func (api *MinerAPI) RemoveBuilder(builder common.Address) error {
return api.e.APIBackend.RemoveBuilder(builder)
}

View File

@@ -187,6 +187,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
chainConfig.HaberTime = config.OverrideHaber
overrides.OverrideHaber = config.OverrideHaber
}
if config.OverrideBohr != nil {
chainConfig.BohrTime = config.OverrideBohr
overrides.OverrideBohr = config.OverrideBohr
}
if config.OverrideVerkle != nil {
chainConfig.VerkleTime = config.OverrideVerkle
overrides.OverrideVerkle = config.OverrideVerkle

View File

@@ -209,6 +209,9 @@ type BlockChain interface {
// UpdateChasingHead update remote best chain head, used by DA check now.
UpdateChasingHead(head *types.Header)
// AncientTail retrieves the tail the ancients blocks
AncientTail() (uint64, error)
}
type DownloadOption func(downloader *Downloader) *Downloader
@@ -797,6 +800,11 @@ func (d *Downloader) findAncestor(p *peerConnection, localHeight uint64, remoteH
// We're above the max reorg threshold, find the earliest fork point
floor = int64(localHeight - maxForkAncestry)
}
// if we have pruned too much history, reset the floor
if tail, err := d.blockchain.AncientTail(); err == nil && tail > uint64(floor) {
floor = int64(tail)
}
// If we're doing a light sync, ensure the floor doesn't go below the CHT, as
// all headers before that point will be missing.
if mode == LightSync {

View File

@@ -60,7 +60,7 @@ func newTester(t *testing.T) *downloadTester {
// newTester creates a new downloader test mocker.
func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
freezer := t.TempDir()
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false, false, false, false)
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false, false, false, false, false)
if err != nil {
panic(err)
}

View File

@@ -194,6 +194,9 @@ type Config struct {
// OverrideHaber (TODO: remove after the fork)
OverrideHaber *uint64 `toml:",omitempty"`
// OverrideBohr (TODO: remove after the fork)
OverrideBohr *uint64 `toml:",omitempty"`
// OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"`

View File

@@ -72,6 +72,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
RPCTxFeeCap float64
OverrideCancun *uint64 `toml:",omitempty"`
OverrideHaber *uint64 `toml:",omitempty"`
OverrideBohr *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"`
BlobExtraReserve uint64
}
@@ -131,6 +132,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.RPCTxFeeCap = c.RPCTxFeeCap
enc.OverrideCancun = c.OverrideCancun
enc.OverrideHaber = c.OverrideHaber
enc.OverrideBohr = c.OverrideBohr
enc.OverrideVerkle = c.OverrideVerkle
enc.BlobExtraReserve = c.BlobExtraReserve
return &enc, nil
@@ -194,6 +196,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
RPCTxFeeCap *float64
OverrideCancun *uint64 `toml:",omitempty"`
OverrideHaber *uint64 `toml:",omitempty"`
OverrideBohr *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"`
BlobExtraReserve *uint64
}
@@ -366,6 +369,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.OverrideHaber != nil {
c.OverrideHaber = dec.OverrideHaber
}
if dec.OverrideBohr != nil {
c.OverrideBohr = dec.OverrideBohr
}
if dec.OverrideVerkle != nil {
c.OverrideVerkle = dec.OverrideVerkle
}

View File

@@ -320,26 +320,22 @@ func newHandler(config *handlerConfig) (*handler, error) {
}
broadcastBlockWithCheck := func(block *types.Block, propagate bool) {
// All the block fetcher activities should be disabled
// after the transition. Print the warning log.
if h.merger.PoSFinalized() {
log.Warn("Unexpected validation activity", "hash", block.Hash(), "number", block.Number())
return
}
// Reject all the PoS style headers in the first place. No matter
// the chain has finished the transition or not, the PoS headers
// should only come from the trusted consensus layer instead of
// p2p network.
if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
if beacon.IsPoSHeader(block.Header()) {
log.Warn("unexpected post-merge header")
return
}
}
if propagate {
if err := core.IsDataAvailable(h.chain, block); err != nil {
log.Error("Propagating block with invalid sidecars", "number", block.Number(), "hash", block.Hash(), "err", err)
return
checkErrs := make(chan error, 2)
go func() {
checkErrs <- core.ValidateListsInBody(block)
}()
go func() {
checkErrs <- core.IsDataAvailable(h.chain, block)
}()
for i := 0; i < cap(checkErrs); i++ {
err := <-checkErrs
if err != nil {
log.Error("Propagating invalid block", "number", block.Number(), "hash", block.Hash(), "err", err)
return
}
}
}
h.BroadcastBlock(block, propagate)

View File

@@ -258,7 +258,7 @@ func (c *mockParlia) CalcDifficulty(chain consensus.ChainHeaderReader, time uint
func newTestParliaHandlerAfterCancun(t *testing.T, config *params.ChainConfig, mode downloader.SyncMode, preCancunBlks, postCancunBlks uint64) *testHandler {
// Have N headers in the freezer
frdir := t.TempDir()
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false)
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false, false, false)
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}

View File

@@ -281,7 +281,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
}
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
statedb.SetTxContext(tx.Hash(), idx)
statedb.SetTxContext(tx.Hash(), idx, 0)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}

View File

@@ -587,7 +587,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
}
}
statedb.SetTxContext(tx.Hash(), i)
statedb.SetTxContext(tx.Hash(), i, 0)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
// We intentionally don't return the error here: if we do, then the RPC server will not
@@ -786,7 +786,7 @@ txloop:
// Generate the next state snapshot fast without tracing
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i)
statedb.SetTxContext(tx.Hash(), i, 0)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
failed = err
@@ -919,7 +919,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
// Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
statedb.SetTxContext(tx.Hash(), i)
statedb.SetTxContext(tx.Hash(), i, 0)
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
if writer != nil {
writer.Flush()
@@ -1127,7 +1127,7 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
}
// Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex, 0)
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}

View File

@@ -183,6 +183,7 @@ type StateStoreReader interface {
type BlockStore interface {
BlockStore() Database
SetBlockStore(block Database)
HasSeparateBlockStore() bool
}
type BlockStoreReader interface {

View File

@@ -44,6 +44,10 @@ func (db *Database) BlockStore() ethdb.Database {
return db
}
func (db *Database) HasSeparateBlockStore() bool {
return false
}
func (db *Database) SetBlockStore(block ethdb.Database) {
panic("not supported")
}

View File

@@ -659,6 +659,30 @@ web3._extend({
name: 'stop',
call: 'miner_stop'
}),
new web3._extend.Method({
name: 'mevRunning',
call: 'miner_mevRunning'
}),
new web3._extend.Method({
name: 'startMev',
call: 'miner_startMev'
}),
new web3._extend.Method({
name: 'stopMev',
call: 'miner_stopMev'
}),
new web3._extend.Method({
name: 'addBuilder',
call: 'miner_addBuilder',
params: 2,
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null]
}),
new web3._extend.Method({
name: 'removeBuilder',
call: 'miner_removeBuilder',
params: 1,
inputFormatter: [web3._extend.formatters.inputAddressFormatter]
}),
new web3._extend.Method({
name: 'setEtherbase',
call: 'miner_setEtherbase',

View File

@@ -78,6 +78,7 @@ type simBidReq struct {
type bidSimulator struct {
config *MevConfig
delayLeftOver time.Duration
minGasPrice *big.Int
chain *core.BlockChain
chainConfig *params.ChainConfig
engine consensus.Engine
@@ -114,6 +115,7 @@ type bidSimulator struct {
func newBidSimulator(
config *MevConfig,
delayLeftOver time.Duration,
minGasPrice *big.Int,
chain *core.BlockChain,
chainConfig *params.ChainConfig,
engine consensus.Engine,
@@ -122,6 +124,7 @@ func newBidSimulator(
b := &bidSimulator{
config: config,
delayLeftOver: delayLeftOver,
minGasPrice: minGasPrice,
chain: chain,
chainConfig: chainConfig,
engine: engine,
@@ -592,6 +595,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
return
}
// commit transactions in bid
for _, tx := range bidRuntime.bid.Txs {
select {
case <-interruptCh:
@@ -609,7 +613,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
break
}
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, tx)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, tx, bidRuntime.bid.UnRevertible.Contains(tx.Hash()))
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)
@@ -617,15 +621,32 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
}
}
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
// check if bid reward is valid
{
bidRuntime.packReward(b.config.ValidatorCommission)
if !bidRuntime.validReward() {
err = errors.New("reward does not achieve the expectation")
return
}
}
// fill transactions from mempool
// check if bid gas price is lower than min gas price
{
bidGasUsed := uint64(0)
bidGasFee := bidRuntime.env.state.GetBalance(consensus.SystemAddress)
for _, receipt := range bidRuntime.env.receipts {
bidGasUsed += receipt.GasUsed
}
bidGasPrice := new(big.Int).Div(bidGasFee.ToBig(), new(big.Int).SetUint64(bidGasUsed))
if bidGasPrice.Cmp(b.minGasPrice) < 0 {
err = errors.New("bid gas price is lower than min gas price")
return
}
}
// if enable greedy merge, fill bid env with transactions from mempool
if b.config.GreedyMergeTx {
delay := b.engine.Delay(b.chain, bidRuntime.env.header, &b.delayLeftOver)
if delay != nil && *delay > 0 {
@@ -645,8 +666,9 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
}
}
// commit payBidTx at the end of the block
bidRuntime.env.gasPool.AddGas(params.PayBidTxGasLimit)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, payBidTx)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, payBidTx, true)
if err != nil {
log.Error("BidSimulator: failed to commit tx", "builder", bidRuntime.bid.Builder,
"bidHash", bidRuntime.bid.Hash(), "tx", payBidTx.Hash(), "err", err)
@@ -727,16 +749,14 @@ func (r *BidRuntime) packReward(validatorCommission uint64) {
r.packedValidatorReward.Sub(r.packedValidatorReward, r.bid.BuilderFee)
}
func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, tx *types.Transaction) error {
func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, tx *types.Transaction, unRevertible bool) error {
var (
env = r.env
snap = env.state.Snapshot()
gp = env.gasPool.Gas()
sc *types.BlobSidecar
env = r.env
sc *types.BlobSidecar
)
// Start executing the transaction
r.env.state.SetTxContext(tx.Hash(), r.env.tcount)
r.env.state.SetTxContext(tx.Hash(), r.env.tcount, 0)
if tx.Type() == types.BlobTxType {
sc = types.NewBlobSidecarFromTx(tx)
@@ -755,9 +775,9 @@ func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *para
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
} else if unRevertible && receipt.Status == types.ReceiptStatusFailed {
return errors.New("no revertible transaction failed")
}
if tx.Type() == types.BlobTxType {

View File

@@ -102,7 +102,7 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, false),
}
miner.bidSimulator = newBidSimulator(&config.Mev, config.DelayLeftOver, eth.BlockChain(), chainConfig, engine, miner.worker)
miner.bidSimulator = newBidSimulator(&config.Mev, config.DelayLeftOver, config.GasPrice, eth.BlockChain(), chainConfig, engine, miner.worker)
miner.worker.setBestBidFetcher(miner.bidSimulator)
miner.wg.Add(1)

View File

@@ -903,7 +903,7 @@ LOOP:
continue
}
// Start executing the transaction
env.state.SetTxContext(tx.Hash(), env.tcount)
env.state.SetTxContext(tx.Hash(), env.tcount, 0)
logs, err := w.commitTransaction(env, tx, bloomProcessors)
switch {

View File

@@ -773,12 +773,13 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r
db = rawdb.NewMemoryDatabase()
} else {
db, err = rawdb.Open(rawdb.OpenOptions{
Type: n.config.DBEngine,
Directory: n.ResolvePath(name),
Namespace: namespace,
Cache: cache,
Handles: handles,
ReadOnly: readonly,
Type: n.config.DBEngine,
Directory: n.ResolvePath(name),
Namespace: namespace,
Cache: cache,
Handles: handles,
ReadOnly: readonly,
MultiDataBase: n.CheckIfMultiDataBase(),
})
}

View File

@@ -153,6 +153,7 @@ var (
FeynmanFixTime: newUint64(1713419340), // 2024-04-18 05:49:00 AM UTC
CancunTime: newUint64(1718863500), // 2024-06-20 06:05:00 AM UTC
HaberTime: newUint64(1718863500), // 2024-06-20 06:05:00 AM UTC
BohrTime: nil,
Parlia: &ParliaConfig{
Period: 3,
@@ -192,6 +193,7 @@ var (
FeynmanFixTime: newUint64(1711342800), // 2024-03-25 5:00:00 AM UTC
CancunTime: newUint64(1713330442), // 2024-04-17 05:07:22 AM UTC
HaberTime: newUint64(1716962820), // 2024-05-29 06:07:00 AM UTC
BohrTime: nil,
Parlia: &ParliaConfig{
Period: 3,
@@ -232,6 +234,7 @@ var (
FeynmanFixTime: newUint64(0),
CancunTime: newUint64(0),
HaberTime: newUint64(0),
BohrTime: newUint64(0),
Parlia: &ParliaConfig{
Period: 3,
@@ -509,6 +512,7 @@ type ChainConfig struct {
FeynmanFixTime *uint64 `json:"feynmanFixTime,omitempty"` // FeynmanFix switch time (nil = no fork, 0 = already activated)
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
HaberTime *uint64 `json:"haberTime,omitempty"` // Haber switch time (nil = no fork, 0 = already on haber)
BohrTime *uint64 `json:"bohrTime,omitempty"` // Bohr switch time (nil = no fork, 0 = already on bohr)
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
@@ -619,7 +623,12 @@ func (c *ChainConfig) String() string {
HaberTime = big.NewInt(0).SetUint64(*c.HaberTime)
}
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Ramanujan: %v, Niels: %v, MirrorSync: %v, Bruno: %v, Berlin: %v, YOLO v3: %v, CatalystBlock: %v, London: %v, ArrowGlacier: %v, MergeFork:%v, Euler: %v, Gibbs: %v, Nano: %v, Moran: %v, Planck: %v,Luban: %v, Plato: %v, Hertz: %v, Hertzfix: %v, ShanghaiTime: %v, KeplerTime: %v, FeynmanTime: %v, FeynmanFixTime: %v, CancunTime: %v, HaberTime: %v, Engine: %v}",
var BohrTime *big.Int
if c.BohrTime != nil {
BohrTime = big.NewInt(0).SetUint64(*c.BohrTime)
}
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Ramanujan: %v, Niels: %v, MirrorSync: %v, Bruno: %v, Berlin: %v, YOLO v3: %v, CatalystBlock: %v, London: %v, ArrowGlacier: %v, MergeFork:%v, Euler: %v, Gibbs: %v, Nano: %v, Moran: %v, Planck: %v,Luban: %v, Plato: %v, Hertz: %v, Hertzfix: %v, ShanghaiTime: %v, KeplerTime: %v, FeynmanTime: %v, FeynmanFixTime: %v, CancunTime: %v, HaberTime: %v, BohrTime: %v, Engine: %v}",
c.ChainID,
c.HomesteadBlock,
c.DAOForkBlock,
@@ -657,6 +666,7 @@ func (c *ChainConfig) String() string {
FeynmanFixTime,
CancunTime,
HaberTime,
BohrTime,
engine,
)
}
@@ -929,6 +939,20 @@ func (c *ChainConfig) IsHaber(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.HaberTime, time)
}
// IsBohr returns whether time is either equal to the Bohr fork time or greater.
func (c *ChainConfig) IsBohr(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.BohrTime, time)
}
// IsOnBohr returns whether currentBlockTime is either equal to the Bohr fork time or greater firstly.
func (c *ChainConfig) IsOnBohr(currentBlockNumber *big.Int, lastBlockTime uint64, currentBlockTime uint64) bool {
lastBlockNumber := new(big.Int)
if currentBlockNumber.Cmp(big.NewInt(1)) >= 0 {
lastBlockNumber.Sub(currentBlockNumber, big.NewInt(1))
}
return !c.IsBohr(lastBlockNumber, lastBlockTime) && c.IsBohr(currentBlockNumber, currentBlockTime)
}
// IsPrague returns whether num is either equal to the Prague fork time or greater.
func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.PragueTime, time)
@@ -993,6 +1017,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{name: "feynmanFixTime", timestamp: c.FeynmanFixTime},
{name: "cancunTime", timestamp: c.CancunTime},
{name: "haberTime", timestamp: c.HaberTime},
{name: "bohrTime", timestamp: c.BohrTime},
{name: "pragueTime", timestamp: c.PragueTime, optional: true},
{name: "verkleTime", timestamp: c.VerkleTime, optional: true},
} {
@@ -1323,7 +1348,7 @@ type Rules struct {
IsHertz bool
IsHertzfix bool
IsShanghai, IsKepler, IsFeynman, IsCancun, IsHaber bool
IsPrague, IsVerkle bool
IsBohr, IsPrague, IsVerkle bool
}
// Rules ensures c's ChainID is not nil.
@@ -1359,6 +1384,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsFeynman: c.IsFeynman(num, timestamp),
IsCancun: c.IsCancun(num, timestamp),
IsHaber: c.IsHaber(num, timestamp),
IsBohr: c.IsBohr(num, timestamp),
IsPrague: c.IsPrague(num, timestamp),
IsVerkle: c.IsVerkle(num, timestamp),
}

View File

@@ -4,17 +4,15 @@ import (
"bytes"
"errors"
"fmt"
"math/big"
"os"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
@@ -26,63 +24,113 @@ import (
"golang.org/x/sync/semaphore"
)
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
type Database interface {
database.Database
Scheme() string
Cap(limit common.StorageSize) error
DiskDB() ethdb.Database
}
const TopN = 3
type Inspector struct {
trie *Trie // traverse trie
db Database
stateRootHash common.Hash
blocknum uint64
blockNum uint64
root node // root of triedb
totalNum uint64
wg sync.WaitGroup
statLock sync.RWMutex
result map[string]*TrieTreeStat
sem *semaphore.Weighted
eoaAccountNums uint64
wg sync.WaitGroup
results stat
topN int
totalAccountNum atomic.Uint64
totalStorageNum atomic.Uint64
lastTime mclock.AbsTime
}
type TrieTreeStat struct {
is_account_trie bool
theNodeStatByLevel [15]NodeStat
totalNodeStat NodeStat
type stat struct {
lock sync.RWMutex
account *trieStat
storageTopN []*trieStat
storageTopNTotal []uint64
storageTotal nodeStat
storageTrieNum uint64
}
type NodeStat struct {
ShortNodeCnt uint64
FullNodeCnt uint64
ValueNodeCnt uint64
type trieStat struct {
owner common.Hash
totalNodeStat nodeStat
nodeStatByLevel [16]nodeStat
}
func (trieStat *TrieTreeStat) AtomicAdd(theNode node, height uint32) {
type nodeStat struct {
ShortNodeCnt atomic.Uint64
FullNodeCnt atomic.Uint64
ValueNodeCnt atomic.Uint64
}
func (ns *nodeStat) IsEmpty() bool {
if ns.FullNodeCnt.Load() == 0 && ns.ShortNodeCnt.Load() == 0 && ns.ValueNodeCnt.Load() == 0 {
return true
}
return false
}
func (s *stat) add(ts *trieStat, topN int) {
s.lock.Lock()
defer s.lock.Unlock()
if ts.owner == (common.Hash{}) {
s.account = ts
return
}
total := ts.totalNodeStat.ValueNodeCnt.Load() + ts.totalNodeStat.FullNodeCnt.Load() + ts.totalNodeStat.ShortNodeCnt.Load()
if len(s.storageTopNTotal) == 0 || total > s.storageTopNTotal[len(s.storageTopNTotal)-1] {
var (
i int
t uint64
)
for i, t = range s.storageTopNTotal {
if total < t {
continue
}
break
}
s.storageTopNTotal = append(s.storageTopNTotal[:i], append([]uint64{total}, s.storageTopNTotal[i:]...)...)
s.storageTopN = append(s.storageTopN[:i], append([]*trieStat{ts}, s.storageTopN[i:]...)...)
if len(s.storageTopN) > topN {
s.storageTopNTotal = s.storageTopNTotal[:topN]
s.storageTopN = s.storageTopN[:topN]
}
}
s.storageTotal.ShortNodeCnt.Add(ts.totalNodeStat.ShortNodeCnt.Load())
s.storageTotal.ValueNodeCnt.Add(ts.totalNodeStat.ValueNodeCnt.Load())
s.storageTotal.FullNodeCnt.Add(ts.totalNodeStat.FullNodeCnt.Load())
s.storageTrieNum++
}
func (trieStat *trieStat) add(theNode node, height int) {
switch (theNode).(type) {
case *shortNode:
atomic.AddUint64(&trieStat.totalNodeStat.ShortNodeCnt, 1)
atomic.AddUint64(&(trieStat.theNodeStatByLevel[height].ShortNodeCnt), 1)
trieStat.totalNodeStat.ShortNodeCnt.Add(1)
trieStat.nodeStatByLevel[height].ShortNodeCnt.Add(1)
case *fullNode:
atomic.AddUint64(&trieStat.totalNodeStat.FullNodeCnt, 1)
atomic.AddUint64(&trieStat.theNodeStatByLevel[height].FullNodeCnt, 1)
trieStat.totalNodeStat.FullNodeCnt.Add(1)
trieStat.nodeStatByLevel[height].FullNodeCnt.Add(1)
case valueNode:
atomic.AddUint64(&trieStat.totalNodeStat.ValueNodeCnt, 1)
atomic.AddUint64(&((trieStat.theNodeStatByLevel[height]).ValueNodeCnt), 1)
default:
panic(errors.New("Invalid node type to statistics"))
trieStat.totalNodeStat.ValueNodeCnt.Add(1)
trieStat.nodeStatByLevel[height].ValueNodeCnt.Add(1)
}
}
func (trieStat *TrieTreeStat) Display(ownerAddress string, treeType string) {
table := tablewriter.NewWriter(os.Stdout)
func (trieStat *trieStat) Display(ownerAddress string, treeType string) string {
sw := new(strings.Builder)
table := tablewriter.NewWriter(sw)
table.SetHeader([]string{"-", "Level", "ShortNodeCnt", "FullNodeCnt", "ValueNodeCnt"})
if ownerAddress == "" {
table.SetCaption(true, fmt.Sprintf("%v", treeType))
@@ -90,38 +138,27 @@ func (trieStat *TrieTreeStat) Display(ownerAddress string, treeType string) {
table.SetCaption(true, fmt.Sprintf("%v-%v", treeType, ownerAddress))
}
table.SetAlignment(1)
for i := 0; i < len(trieStat.theNodeStatByLevel); i++ {
nodeStat := trieStat.theNodeStatByLevel[i]
if nodeStat.FullNodeCnt == 0 && nodeStat.ShortNodeCnt == 0 && nodeStat.ValueNodeCnt == 0 {
break
for i := range trieStat.nodeStatByLevel {
if trieStat.nodeStatByLevel[i].IsEmpty() {
continue
}
table.AppendBulk([][]string{
{"-", strconv.Itoa(i), nodeStat.ShortNodeCount(), nodeStat.FullNodeCount(), nodeStat.ValueNodeCount()},
{"-", fmt.Sprintf("%d", i),
fmt.Sprintf("%d", trieStat.nodeStatByLevel[i].ShortNodeCnt.Load()),
fmt.Sprintf("%d", trieStat.nodeStatByLevel[i].FullNodeCnt.Load()),
fmt.Sprintf("%d", trieStat.nodeStatByLevel[i].ValueNodeCnt.Load())},
})
}
table.AppendBulk([][]string{
{"Total", "-", trieStat.totalNodeStat.ShortNodeCount(), trieStat.totalNodeStat.FullNodeCount(), trieStat.totalNodeStat.ValueNodeCount()},
{"Total", "-", fmt.Sprintf("%d", trieStat.totalNodeStat.ShortNodeCnt.Load()), fmt.Sprintf("%d", trieStat.totalNodeStat.FullNodeCnt.Load()), fmt.Sprintf("%d", trieStat.totalNodeStat.ValueNodeCnt.Load())},
})
table.Render()
}
func Uint64ToString(cnt uint64) string {
return fmt.Sprintf("%v", cnt)
}
func (nodeStat *NodeStat) ShortNodeCount() string {
return Uint64ToString(nodeStat.ShortNodeCnt)
}
func (nodeStat *NodeStat) FullNodeCount() string {
return Uint64ToString(nodeStat.FullNodeCnt)
}
func (nodeStat *NodeStat) ValueNodeCount() string {
return Uint64ToString(nodeStat.ValueNodeCnt)
return sw.String()
}
// NewInspector return a inspector obj
func NewInspector(tr *Trie, db Database, stateRootHash common.Hash, blocknum uint64, jobnum uint64) (*Inspector, error) {
func NewInspector(tr *Trie, db Database, stateRootHash common.Hash, blockNum uint64, jobNum uint64, topN int) (*Inspector, error) {
if tr == nil {
return nil, errors.New("trie is nil")
}
@@ -131,15 +168,20 @@ func NewInspector(tr *Trie, db Database, stateRootHash common.Hash, blocknum uin
}
ins := &Inspector{
trie: tr,
db: db,
stateRootHash: stateRootHash,
blocknum: blocknum,
root: tr.root,
result: make(map[string]*TrieTreeStat),
totalNum: (uint64)(0),
wg: sync.WaitGroup{},
sem: semaphore.NewWeighted(int64(jobnum)),
trie: tr,
db: db,
stateRootHash: stateRootHash,
blockNum: blockNum,
root: tr.root,
results: stat{},
topN: topN,
totalAccountNum: atomic.Uint64{},
totalStorageNum: atomic.Uint64{},
lastTime: mclock.Now(),
sem: semaphore.NewWeighted(int64(jobNum)),
wg: sync.WaitGroup{},
eoaAccountNums: 0,
}
@@ -147,155 +189,123 @@ func NewInspector(tr *Trie, db Database, stateRootHash common.Hash, blocknum uin
}
// Run statistics, external call
func (inspect *Inspector) Run() {
accountTrieStat := &TrieTreeStat{
is_account_trie: true,
}
if inspect.db.Scheme() == rawdb.HashScheme {
ticker := time.NewTicker(30 * time.Second)
go func() {
defer ticker.Stop()
for range ticker.C {
inspect.db.Cap(DEFAULT_TRIEDBCACHE_SIZE)
func (s *Inspector) Run() {
ticker := time.NewTicker(30 * time.Second)
go func() {
defer ticker.Stop()
for range ticker.C {
if s.db.Scheme() == rawdb.HashScheme {
s.db.Cap(DEFAULT_TRIEDBCACHE_SIZE)
}
}()
}
runtime.GC()
}
}()
if _, ok := inspect.result[""]; !ok {
inspect.result[""] = accountTrieStat
}
log.Info("Find Account Trie Tree", "rootHash: ", inspect.trie.Hash().String(), "BlockNum: ", inspect.blocknum)
log.Info("Find Account Trie Tree", "rootHash: ", s.trie.Hash().String(), "BlockNum: ", s.blockNum)
inspect.ConcurrentTraversal(inspect.trie, accountTrieStat, inspect.root, 0, []byte{})
inspect.wg.Wait()
ts := &trieStat{
owner: common.Hash{},
}
s.traversal(s.trie, ts, s.root, 0, []byte{})
s.results.add(ts, s.topN)
s.wg.Wait()
}
func (inspect *Inspector) SubConcurrentTraversal(theTrie *Trie, theTrieTreeStat *TrieTreeStat, theNode node, height uint32, path []byte) {
inspect.ConcurrentTraversal(theTrie, theTrieTreeStat, theNode, height, path)
inspect.wg.Done()
}
func (inspect *Inspector) ConcurrentTraversal(theTrie *Trie, theTrieTreeStat *TrieTreeStat, theNode node, height uint32, path []byte) {
// print process progress
total_num := atomic.AddUint64(&inspect.totalNum, 1)
if total_num%100000 == 0 {
fmt.Printf("Complete progress: %v, go routines Num: %v\n", total_num, runtime.NumGoroutine())
}
func (s *Inspector) traversal(trie *Trie, ts *trieStat, n node, height int, path []byte) {
// nil node
if theNode == nil {
if n == nil {
return
}
switch current := (theNode).(type) {
ts.add(n, height)
switch current := (n).(type) {
case *shortNode:
inspect.ConcurrentTraversal(theTrie, theTrieTreeStat, current.Val, height, append(path, current.Key...))
s.traversal(trie, ts, current.Val, height, append(path, current.Key...))
case *fullNode:
for idx, child := range current.Children {
if child == nil {
continue
}
childPath := append(path, byte(idx))
if inspect.sem.TryAcquire(1) {
inspect.wg.Add(1)
dst := make([]byte, len(childPath))
copy(dst, childPath)
go inspect.SubConcurrentTraversal(theTrie, theTrieTreeStat, child, height+1, dst)
} else {
inspect.ConcurrentTraversal(theTrie, theTrieTreeStat, child, height+1, childPath)
}
p := common.CopyBytes(append(path, byte(idx)))
s.traversal(trie, ts, child, height+1, p)
}
case hashNode:
n, err := theTrie.resloveWithoutTrack(current, path)
tn, err := trie.resloveWithoutTrack(current, path)
if err != nil {
fmt.Printf("Resolve HashNode error: %v, TrieRoot: %v, Height: %v, Path: %v\n", err, theTrie.Hash().String(), height+1, path)
fmt.Printf("Resolve HashNode error: %v, TrieRoot: %v, Height: %v, Path: %v\n", err, trie.Hash().String(), height+1, path)
return
}
inspect.ConcurrentTraversal(theTrie, theTrieTreeStat, n, height, path)
return
s.PrintProgress(trie)
s.traversal(trie, ts, tn, height, path)
case valueNode:
if !hasTerm(path) {
break
}
var account Account
var account types.StateAccount
if err := rlp.Decode(bytes.NewReader(current), &account); err != nil {
break
}
if common.BytesToHash(account.CodeHash) == types.EmptyCodeHash {
inspect.eoaAccountNums++
s.eoaAccountNums++
}
if account.Root == (common.Hash{}) || account.Root == types.EmptyRootHash {
break
}
ownerAddress := common.BytesToHash(hexToCompact(path))
contractTrie, err := New(StorageTrieID(inspect.stateRootHash, ownerAddress, account.Root), inspect.db)
contractTrie, err := New(StorageTrieID(s.stateRootHash, ownerAddress, account.Root), s.db)
if err != nil {
fmt.Printf("New contract trie node: %v, error: %v, Height: %v, Path: %v\n", theNode, err, height, path)
break
panic(err)
}
contractTrie.tracer.reset()
trieStat := &TrieTreeStat{
is_account_trie: false,
}
inspect.statLock.Lock()
if _, ok := inspect.result[ownerAddress.String()]; !ok {
inspect.result[ownerAddress.String()] = trieStat
if s.sem.TryAcquire(1) {
s.wg.Add(1)
go func() {
t := &trieStat{
owner: ownerAddress,
}
s.traversal(contractTrie, t, contractTrie.root, 0, []byte{})
s.results.add(t, s.topN)
s.sem.Release(1)
s.wg.Done()
}()
} else {
t := &trieStat{
owner: ownerAddress,
}
s.traversal(contractTrie, t, contractTrie.root, 0, []byte{})
s.results.add(t, s.topN)
}
inspect.statLock.Unlock()
// log.Info("Find Contract Trie Tree, rootHash: ", contractTrie.Hash().String(), "")
inspect.wg.Add(1)
go inspect.SubConcurrentTraversal(contractTrie, trieStat, contractTrie.root, 0, []byte{})
default:
panic(errors.New("Invalid node type to traverse."))
panic(errors.New("invalid node type to traverse"))
}
theTrieTreeStat.AtomicAdd(theNode, height)
}
func (inspect *Inspector) DisplayResult() {
func (s *Inspector) PrintProgress(t *Trie) {
var (
elapsed = mclock.Now().Sub(s.lastTime)
)
if t.owner == (common.Hash{}) {
s.totalAccountNum.Add(1)
} else {
s.totalStorageNum.Add(1)
}
if elapsed > 4*time.Second {
log.Info("traversal progress", "TotalAccountNum", s.totalAccountNum.Load(), "TotalStorageNum", s.totalStorageNum.Load(), "Goroutine", runtime.NumGoroutine())
s.lastTime = mclock.Now()
}
}
func (s *Inspector) DisplayResult() {
// display root hash
if _, ok := inspect.result[""]; !ok {
log.Info("Display result error", "missing account trie")
return
}
inspect.result[""].Display("", "AccountTrie")
fmt.Println(s.results.account.Display("", "AccountTrie"))
fmt.Println("EOA accounts num: ", s.eoaAccountNums)
type SortedTrie struct {
totalNum uint64
ownerAddress string
}
// display contract trie
var sortedTriesByNums []SortedTrie
var totalContactsNodeStat NodeStat
var contractTrieCnt uint64 = 0
for ownerAddress, stat := range inspect.result {
if ownerAddress == "" {
continue
}
contractTrieCnt++
totalContactsNodeStat.ShortNodeCnt += stat.totalNodeStat.ShortNodeCnt
totalContactsNodeStat.FullNodeCnt += stat.totalNodeStat.FullNodeCnt
totalContactsNodeStat.ValueNodeCnt += stat.totalNodeStat.ValueNodeCnt
totalNodeCnt := stat.totalNodeStat.ShortNodeCnt + stat.totalNodeStat.ValueNodeCnt + stat.totalNodeStat.FullNodeCnt
sortedTriesByNums = append(sortedTriesByNums, SortedTrie{totalNum: totalNodeCnt, ownerAddress: ownerAddress})
}
sort.Slice(sortedTriesByNums, func(i, j int) bool {
return sortedTriesByNums[i].totalNum > sortedTriesByNums[j].totalNum
})
fmt.Println("EOA accounts num: ", inspect.eoaAccountNums)
// only display top 5
for i, t := range sortedTriesByNums {
if i > 5 {
break
}
if stat, ok := inspect.result[t.ownerAddress]; !ok {
log.Error("Storage trie stat not found", "ownerAddress", t.ownerAddress)
} else {
stat.Display(t.ownerAddress, "ContractTrie")
}
for _, st := range s.results.storageTopN {
fmt.Println(st.Display(st.owner.String(), "StorageTrie"))
}
fmt.Printf("Contract Trie, total trie num: %v, ShortNodeCnt: %v, FullNodeCnt: %v, ValueNodeCnt: %v\n",
contractTrieCnt, totalContactsNodeStat.ShortNodeCnt, totalContactsNodeStat.FullNodeCnt, totalContactsNodeStat.ValueNodeCnt)
s.results.storageTrieNum, s.results.storageTotal.ShortNodeCnt.Load(), s.results.storageTotal.FullNodeCnt.Load(), s.results.storageTotal.ValueNodeCnt.Load())
}

View File

@@ -98,7 +98,7 @@ type tester struct {
func newTester(t *testing.T, historyLimit uint64) *tester {
var (
disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false)
disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false, false, false, false, false)
db = New(disk, &Config{
StateHistory: historyLimit,
CleanCacheSize: 256 * 1024,

View File

@@ -152,12 +152,13 @@ func (kr *JournalKVReader) Close() {
}
func newJournalWriter(file string, db ethdb.Database, journalType JournalType) JournalWriter {
log.Info("New journal writer", "path", file, "journalType", journalType)
if journalType == JournalKVType {
log.Info("New journal writer for journal kv")
return &JournalKVWriter{
diskdb: db,
}
} else {
log.Info("New journal writer for journal file", "path", file)
fd, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil
@@ -169,8 +170,8 @@ func newJournalWriter(file string, db ethdb.Database, journalType JournalType) J
}
func newJournalReader(file string, db ethdb.Database, journalType JournalType) (JournalReader, error) {
log.Info("New journal reader", "path", file, "journalType", journalType)
if journalType == JournalKVType {
log.Info("New journal reader for journal kv")
journal := rawdb.ReadTrieJournal(db)
if len(journal) == 0 {
return nil, errMissJournal
@@ -179,6 +180,7 @@ func newJournalReader(file string, db ethdb.Database, journalType JournalType) (
journalBuf: bytes.NewBuffer(journal),
}, nil
} else {
log.Info("New journal reader for journal file", "path", file)
fd, err := os.Open(file)
if errors.Is(err, fs.ErrNotExist) {
return nil, errMissJournal