Compare commits

...

24 Commits

Author SHA1 Message Date
emailtovamos
e149161d54 jsutil: correct the division 2024-05-21 15:42:58 +01:00
emailtovamos
cb0b34b7c2 jsutils: update logs 2024-05-16 14:34:11 +01:00
emailtovamos
5b7a71e38e jsutils: faucet balance 2024-05-16 12:25:25 +01: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
Eric
d436f9e2e8 eth/fetcher/block_fetcher.go: add metrics for import failed block (#2459) 2024-05-14 19:29:43 +08:00
Eric
97c3b9b267 internal/api.go: add choice about not show full blob (#2458) 2024-05-14 17:17:58 +08:00
Nathan
0560685460 core: clearup feynman testflag and rialto code (#2457) 2024-05-14 16:37:17 +08:00
rjl493456442
bf16a39876 ethdb/pebble: print warning log if pebble performance degrades (#29478) 2024-05-14 15:26:21 +08:00
Devon Bear
2c8720016d go.mod: bump pebble db to official release (#29038)
bump pebble
2024-05-14 15:26:21 +08:00
Nathan
23f6194fad eth/handler: check blobs before broadcast blocks (#2450) 2024-05-13 16:20:50 +08:00
Martin HS
691d195526 core/rawdb: add sanity-limit to header accessor (#29534) 2024-05-11 10:24:39 +08:00
Nathan
b57c779759 eth/filters: enforce topic-limit early on filter criterias (#29535) (#2448)
This PR adds a limit of 1000 to the "inner" topics in a filter-criteria

Co-authored-by: Martin HS <martin@swende.se>
2024-05-11 10:24:19 +08:00
zzzckck
a7d5b02919 release: prepare for release v1.4.7 (#2442)
* release: prepare for release v1.4.7

* code: avoid golang-lint error for TxLookupLimit
2024-05-09 19:47:52 +08:00
zzzckck
1ce9bb044d config: setup Mainnet Tycho(Cancun) hardfork date (#2439)
expected hard fork date:
Mainnet: 2024-06-20 06:05:00 AM UTC
2024-05-09 16:24:04 +08:00
dependabot[bot]
7948950f7a build(deps): bump golang.org/x/net from 0.19.0 to 0.23.0 (#2411) 2024-05-09 16:06:28 +08:00
knowmost
0c101e618a chore: fix some typos (#2433) 2024-05-09 16:05:46 +08:00
zzzckck
571ea2c4b9 nancy: add files .nancy-ignore (#2440) 2024-05-09 15:58:06 +08:00
galaio
7bc5a3353d txpool: limit max gas when mining is enabled; (#2435) 2024-05-09 15:54:31 +08:00
Chris Li
901ea2e0d2 fix: performance issue when load journal (#2438) 2024-05-08 15:36:01 +08:00
buddho
1d81f3316f metrics: add blockInsertMgaspsGauge to trace mgasps (#2396) 2024-04-30 17:58:04 +08:00
63 changed files with 695 additions and 475 deletions

1
.nancy-ignore Normal file
View File

@@ -0,0 +1 @@
CVE-2024-34478 # "CWE-754: Improper Check for Unusual or Exceptional Conditions." This vulnerability is BTC only, BSC does not have the issue.

View File

@@ -1,4 +1,18 @@
# Changelog
## v1.4.7
### FEATURE
* [\#2439](https://github.com/bnb-chain/bsc/pull/2439) config: setup Mainnet Tycho(Cancun) hardfork date
### IMPROVEMENT
* [\#2396](https://github.com/bnb-chain/bsc/pull/2396) metrics: add blockInsertMgaspsGauge to trace mgasps
* [\#2411](https://github.com/bnb-chain/bsc/pull/2411) build(deps): bump golang.org/x/net from 0.19.0 to 0.23.0
* [\#2435](https://github.com/bnb-chain/bsc/pull/2435) txpool: limit max gas when mining is enabled
* [\#2438](https://github.com/bnb-chain/bsc/pull/2438) fix: performance issue when load journal
* [\#2440](https://github.com/bnb-chain/bsc/pull/2440) nancy: add files .nancy-ignore
### BUGFIX
NA
## v1.4.6
### FEATURE
* [\#2227](https://github.com/bnb-chain/bsc/pull/2227) core: separated databases for block data

View File

@@ -193,14 +193,6 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v
}
if ctx.IsSet(utils.OverrideFeynman.Name) {
v := ctx.Uint64(utils.OverrideFeynman.Name)
cfg.Eth.OverrideFeynman = &v
}
if ctx.IsSet(utils.OverrideFeynmanFix.Name) {
v := ctx.Uint64(utils.OverrideFeynmanFix.Name)
cfg.Eth.OverrideFeynmanFix = &v
}
if ctx.IsSet(utils.OverrideFullImmutabilityThreshold.Name) {
params.FullImmutabilityThreshold = ctx.Uint64(utils.OverrideFullImmutabilityThreshold.Name)
downloader.FullMaxForkAncestry = ctx.Uint64(utils.OverrideFullImmutabilityThreshold.Name)
@@ -211,6 +203,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
if ctx.IsSet(utils.OverrideDefaultExtraReserveForBlobRequests.Name) {
params.DefaultExtraReserveForBlobRequests = ctx.Uint64(utils.OverrideDefaultExtraReserveForBlobRequests.Name)
}
if ctx.IsSet(utils.OverrideBreatheBlockInterval.Name) {
params.BreatheBlockInterval = ctx.Uint64(utils.OverrideBreatheBlockInterval.Name)
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)

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

@@ -25,6 +25,8 @@ import (
"strings"
"time"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
@@ -72,11 +74,10 @@ var (
utils.RialtoHash,
utils.OverrideCancun,
utils.OverrideVerkle,
utils.OverrideFeynman,
utils.OverrideFeynmanFix,
utils.OverrideFullImmutabilityThreshold,
utils.OverrideMinBlocksForBlobRequests,
utils.OverrideDefaultExtraReserveForBlobRequests,
utils.OverrideBreatheBlockInterval,
utils.EnablePersonal,
utils.TxPoolLocalsFlag,
utils.TxPoolNoLocalsFlag,
@@ -456,6 +457,10 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon
// Set the gas price to the limits from the CLI and start mining
gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
ethBackend.TxPool().SetGasTip(gasprice)
gasCeil := ethBackend.Miner().GasCeil()
if gasCeil > params.SystemTxsGas {
ethBackend.TxPool().SetMaxGas(gasCeil - params.SystemTxsGas)
}
if err := ethBackend.StartMining(); err != nil {
utils.Fatalf("Failed to start mining: %v", err)
}

View File

@@ -409,7 +409,7 @@ func pruneBlock(ctx *cli.Context) error {
}
if _, err := os.Stat(newAncientPath); err == nil {
// No file lock found for old ancientDB but new ancientDB exsisted, indicating the geth was interrupted
// No file lock found for old ancientDB but new ancientDB existed, indicating the geth was interrupted
// after old ancientDB removal, this happened after backup successfully, so just rename the new ancientDB
if err := blockpruner.AncientDbReplacer(); err != nil {
log.Error("Failed to rename new ancient directory")

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,15 +315,6 @@ var (
Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideFeynman = &cli.Uint64Flag{
Name: "override.feynman",
Usage: "Manually specify the Feynman fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideFeynmanFix = &cli.Uint64Flag{
Name: "override.feynmanfix",
Usage: "Manually specify the FeynmanFix fork timestamp, overriding the bundled setting",
}
OverrideFullImmutabilityThreshold = &cli.Uint64Flag{
Name: "override.immutabilitythreshold",
Usage: "It is the number of blocks after which a chain segment is considered immutable, only for testing purpose",
@@ -342,6 +333,12 @@ var (
Value: params.DefaultExtraReserveForBlobRequests,
Category: flags.EthCategory,
}
OverrideBreatheBlockInterval = &cli.Uint64Flag{
Name: "override.breatheblockinterval",
Usage: "It changes the interval between breathe blocks, only for testing purpose",
Value: params.BreatheBlockInterval,
Category: flags.EthCategory,
}
SyncModeFlag = &flags.TextMarshalerFlag{
Name: "syncmode",
Usage: `Blockchain sync mode ("snap" or "full")`,

View File

@@ -15,14 +15,13 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
const SecondsPerDay uint64 = 86400
// the params should be two blocks' time(timestamp)
func sameDayInUTC(first, second uint64) bool {
return first/SecondsPerDay == second/SecondsPerDay
return first/params.BreatheBlockInterval == second/params.BreatheBlockInterval
}
func isBreatheBlock(lastBlockTime, blockTime uint64) bool {

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

@@ -71,6 +71,8 @@ var (
justifiedBlockGauge = metrics.NewRegisteredGauge("chain/head/justified", nil)
finalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
blockInsertMgaspsGauge = metrics.NewRegisteredGauge("chain/insert/mgasps", nil)
chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)

View File

@@ -58,11 +58,13 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
end := chain[index]
// Assemble the log context and send it to the logger
mgasps := float64(st.usedGas) * 1000 / float64(elapsed)
context := []interface{}{
"number", end.Number(), "hash", end.Hash(), "miner", end.Coinbase(),
"blocks", st.processed, "txs", txs, "blobs", blobs, "mgas", float64(st.usedGas) / 1000000,
"elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed),
"elapsed", common.PrettyDuration(elapsed), "mgasps", mgasps,
}
blockInsertMgaspsGauge.Update(int64(mgasps))
if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute {
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
}

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

@@ -216,10 +216,8 @@ func (e *GenesisMismatchError) Error() string {
// ChainOverrides contains the changes to chain config
// Typically, these modifications involve hardforks that are not enabled on the BSC mainnet, intended for testing purposes.
type ChainOverrides struct {
OverrideCancun *uint64
OverrideVerkle *uint64
OverrideFeynman *uint64
OverrideFeynmanFix *uint64
OverrideCancun *uint64
OverrideVerkle *uint64
}
// SetupGenesisBlock writes or updates the genesis block in db.
@@ -251,12 +249,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
if overrides != nil && overrides.OverrideVerkle != nil {
config.VerkleTime = overrides.OverrideVerkle
}
if overrides != nil && overrides.OverrideFeynman != nil {
config.FeynmanTime = overrides.OverrideFeynman
}
if overrides != nil && overrides.OverrideFeynmanFix != nil {
config.FeynmanFixTime = overrides.OverrideFeynmanFix
}
}
}
// Just commit the new block if there is no stored genesis block.

View File

@@ -316,8 +316,8 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu
if count == 0 {
return rlpHeaders
}
// read remaining from ancients
data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 0)
// read remaining from ancients, cap at 2M
data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 2*1024*1024)
if err != nil {
log.Error("Failed to read headers from freezer", "err", err)
return rlpHeaders

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

@@ -402,7 +402,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str
var oldOffSet uint64
if interrupt {
// The interrupt scecario within this function is specific for old and new ancientDB exsisted concurrently,
// The interrupt scecario within this function is specific for old and new ancientDB existed concurrently,
// should use last version of offset for oldAncientDB, because current offset is
// actually of the new ancientDB_Backup, but what we want is the offset of ancientDB being backup.
oldOffSet = rawdb.ReadOffSetOfLastAncientFreezer(chainDb)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -57,31 +57,3 @@ var (
//go:embed chapel/TokenRecoverPortalContract
ChapelTokenRecoverPortalContract string
)
// contract codes for Rialto upgrade
var (
//go:embed rialto/ValidatorContract
RialtoValidatorContract string
//go:embed rialto/SlashContract
RialtoSlashContract string
//go:embed rialto/TokenHubContract
RialtoTokenHubContract string
//go:embed rialto/GovHubContract
RialtoGovHubContract string
//go:embed rialto/CrossChainContract
RialtoCrossChainContract string
//go:embed rialto/StakingContract
RialtoStakingContract string
//go:embed rialto/StakeHubContract
RialtoStakeHubContract string
//go:embed rialto/StakeCreditContract
RialtoStakeCreditContract string
//go:embed rialto/GovernorContract
RialtoGovernorContract string
//go:embed rialto/GovTokenContract
RialtoGovTokenContract string
//go:embed rialto/TimelockContract
RialtoTimelockContract string
//go:embed rialto/TokenRecoverPortalContract
RialtoTokenRecoverPortalContract string
)

View File

@@ -680,72 +680,6 @@ func init() {
},
}
feynmanUpgrade[rialtoNet] = &Upgrade{
UpgradeName: "feynman",
Configs: []*UpgradeConfig{
{
ContractAddr: common.HexToAddress(ValidatorContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoValidatorContract,
},
{
ContractAddr: common.HexToAddress(SlashContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoSlashContract,
},
{
ContractAddr: common.HexToAddress(TokenHubContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoTokenHubContract,
},
{
ContractAddr: common.HexToAddress(GovHubContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoGovHubContract,
},
{
ContractAddr: common.HexToAddress(CrossChainContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoCrossChainContract,
},
{
ContractAddr: common.HexToAddress(StakingContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoStakingContract,
},
{
ContractAddr: common.HexToAddress(StakeHubContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoStakeHubContract,
},
{
ContractAddr: common.HexToAddress(StakeCreditContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.MainnetStakeCreditContract,
},
{
ContractAddr: common.HexToAddress(GovernorContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoGovernorContract,
},
{
ContractAddr: common.HexToAddress(GovTokenContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.MainnetGovTokenContract,
},
{
ContractAddr: common.HexToAddress(TimelockContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoTimelockContract,
},
{
ContractAddr: common.HexToAddress(TokenRecoverPortalContract),
CommitUrl: "https://github.com/bnb-chain/bsc-genesis-contract/commit/2d6372ddba77902ef01e45887a425938376d5a5c",
Code: feynman.RialtoTokenRecoverPortalContract,
},
},
}
// This upgrade is to fix an error on testnet only. So the upgrade config of mainnet is empty.
feynmanFixUpgrade[mainNet] = &Upgrade{
UpgradeName: "feynmanFix",
@@ -767,11 +701,6 @@ func init() {
},
},
}
feynmanFixUpgrade[rialtoNet] = &Upgrade{
UpgradeName: "feynmanFix",
Configs: []*UpgradeConfig{},
}
}
func UpgradeBuildInSystemContract(config *params.ChainConfig, blockNumber *big.Int, lastBlockTime uint64, blockTime uint64, statedb *state.StateDB) {

View File

@@ -26,7 +26,7 @@ func TestAllCodesHash(t *testing.T) {
allCodes := make([]byte, 0, 10_000_000)
for _, hardfork := range upgradesList {
for _, network := range []string{mainNet, chapelNet, rialtoNet} {
for _, network := range []string{mainNet, chapelNet} {
allCodes = append(allCodes, []byte(network)...)
if hardfork[network] != nil {
for _, addressConfig := range hardfork[network].Configs {
@@ -37,6 +37,5 @@ func TestAllCodesHash(t *testing.T) {
}
}
allCodeHash := sha256.Sum256(allCodes)
require.Equal(t, allCodeHash[:], common.Hex2Bytes("3d68c07faa6b9385e981a45bd539f15d4cbb712426c604b9cab22591af446fc8"))
require.Equal(t, allCodeHash[:], common.Hex2Bytes("833cc0fc87c46ad8a223e44ccfdc16a51a7e7383525136441bd0c730f06023df"))
}

View File

@@ -27,6 +27,7 @@ import (
"path/filepath"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -305,6 +306,7 @@ type BlobPool struct {
head *types.Header // Current head of the chain
state *state.StateDB // Current state at the head of the chain
gasTip *uint256.Int // Currently accepted minimum gas tip
maxGas atomic.Uint64 // Currently accepted max gas, it will be modified by MinerAPI
lookup map[common.Hash]uint64 // Lookup table mapping hashes to tx billy entries
index map[common.Address][]*blobTxMeta // Blob transactions grouped by accounts, sorted by nonce
@@ -1098,6 +1100,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error {
Accept: 1 << types.BlobTxType,
MaxSize: txMaxSize,
MinTip: p.gasTip.ToBig(),
MaxGas: p.GetMaxGas(),
}
if err := txpool.ValidateTransaction(tx, p.head, p.signer, baseOpts); err != nil {
return err
@@ -1671,3 +1674,11 @@ func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus {
}
return txpool.TxStatusUnknown
}
func (p *BlobPool) SetMaxGas(maxGas uint64) {
p.maxGas.Store(maxGas)
}
func (p *BlobPool) GetMaxGas() uint64 {
return p.maxGas.Load()
}

View File

@@ -219,6 +219,7 @@ type LegacyPool struct {
scope event.SubscriptionScope
signer types.Signer
mu sync.RWMutex
maxGas atomic.Uint64 // Currently accepted max gas, it will be modified by MinerAPI
currentHead atomic.Pointer[types.Header] // Current head of the blockchain
currentState *state.StateDB // Current state in the blockchain head
@@ -670,6 +671,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
1<<types.DynamicFeeTxType,
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
MaxGas: pool.GetMaxGas(),
}
if local {
opts.MinTip = new(big.Int)
@@ -1769,6 +1771,14 @@ func (pool *LegacyPool) demoteUnexecutables() {
}
}
func (pool *LegacyPool) GetMaxGas() uint64 {
return pool.maxGas.Load()
}
func (pool *LegacyPool) SetMaxGas(maxGas uint64) {
pool.maxGas.Store(maxGas)
}
// addressByHeartbeat is an account address tagged with its last activity timestamp.
type addressByHeartbeat struct {
address common.Address

View File

@@ -166,4 +166,7 @@ type SubPool interface {
// Status returns the known status (unknown/pending/queued) of a transaction
// identified by their hashes.
Status(hash common.Hash) TxStatus
// SetMaxGas limit max acceptable tx gas when mine is enabled
SetMaxGas(maxGas uint64)
}

View File

@@ -284,6 +284,12 @@ func (p *TxPool) SetGasTip(tip *big.Int) {
}
}
func (p *TxPool) SetMaxGas(gas uint64) {
for _, subpool := range p.subpools {
subpool.SetMaxGas(gas)
}
}
// Has returns an indicator whether the pool has a transaction cached with the
// given hash.
func (p *TxPool) Has(hash common.Hash) bool {

View File

@@ -45,6 +45,7 @@ type ValidationOptions struct {
Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool
MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
MaxGas uint64 // Max acceptable transaction gas in the txpool
}
// ValidateTransaction is a helper method to check whether a transaction is valid
@@ -86,6 +87,12 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if head.GasLimit < tx.Gas() {
return ErrGasLimit
}
// Ensure the transaction doesn't exceed the current miner max acceptable limit gas
if opts.MaxGas > 0 && opts.MaxGas < tx.Gas() {
return ErrGasLimit
}
// Sanity check for extremely large numbers (supported by RLP or RPC)
if tx.GasFeeCap().BitLen() > 256 {
return core.ErrFeeCapVeryHigh

View File

@@ -20,6 +20,8 @@ import (
"math/big"
"time"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
@@ -71,6 +73,9 @@ func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
// SetGasLimit sets the gaslimit to target towards during mining.
func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool {
api.e.Miner().SetGasCeil(uint64(gasLimit))
if api.e.Miner().Mining() && uint64(gasLimit) > params.SystemTxsGas {
api.e.TxPool().SetMaxGas(uint64(gasLimit) - params.SystemTxsGas)
}
return true
}

View File

@@ -187,14 +187,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
chainConfig.VerkleTime = config.OverrideVerkle
overrides.OverrideVerkle = config.OverrideVerkle
}
if config.OverrideFeynman != nil {
chainConfig.FeynmanTime = config.OverrideFeynman
overrides.OverrideFeynman = config.OverrideFeynman
}
if config.OverrideFeynmanFix != nil {
chainConfig.FeynmanFixTime = config.OverrideFeynmanFix
overrides.OverrideFeynmanFix = config.OverrideFeynmanFix
}
// startup ancient freeze
if err = chainDb.SetupFreezerEnv(&ethdb.FreezerEnv{

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

@@ -194,12 +194,6 @@ type Config struct {
// OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"`
// OverrideFeynman (TODO: remove after the fork)
OverrideFeynman *uint64 `toml:",omitempty"`
// OverrideFeynmanFix (TODO: remove after the fork)
OverrideFeynmanFix *uint64 `toml:",omitempty"`
// blob setting
BlobExtraReserve uint64
}

View File

@@ -32,11 +32,12 @@ func (c Config) MarshalTOML() (interface{}, error) {
EnableTrustProtocol bool
PipeCommit bool
RangeLimit bool
TxLookupLimit uint64 `toml:",omitempty"`
TransactionHistory uint64 `toml:",omitempty"`
StateHistory uint64 `toml:",omitempty"`
StateScheme string `toml:",omitempty"`
PathSyncFlush bool `toml:",omitempty"`
TxLookupLimit uint64 `toml:",omitempty"`
TransactionHistory uint64 `toml:",omitempty"`
StateHistory uint64 `toml:",omitempty"`
StateScheme string `toml:",omitempty"`
PathSyncFlush bool `toml:",omitempty"`
JournalFileEnabled bool
RequiredBlocks map[uint64]common.Hash `toml:"-"`
LightServ int `toml:",omitempty"`
LightIngress int `toml:",omitempty"`
@@ -71,8 +72,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
RPCTxFeeCap float64
OverrideCancun *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"`
OverrideFeynman *uint64 `toml:",omitempty"`
OverrideFeynmanFix *uint64 `toml:",omitempty"`
BlobExtraReserve uint64
}
var enc Config
enc.Genesis = c.Genesis
@@ -95,6 +95,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.StateHistory = c.StateHistory
enc.StateScheme = c.StateScheme
enc.PathSyncFlush = c.PathSyncFlush
enc.JournalFileEnabled = c.JournalFileEnabled
enc.RequiredBlocks = c.RequiredBlocks
enc.LightServ = c.LightServ
enc.LightIngress = c.LightIngress
@@ -129,8 +130,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.RPCTxFeeCap = c.RPCTxFeeCap
enc.OverrideCancun = c.OverrideCancun
enc.OverrideVerkle = c.OverrideVerkle
enc.OverrideFeynman = c.OverrideFeynman
enc.OverrideFeynmanFix = c.OverrideFeynmanFix
enc.BlobExtraReserve = c.BlobExtraReserve
return &enc, nil
}
@@ -152,11 +152,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
EnableTrustProtocol *bool
PipeCommit *bool
RangeLimit *bool
TxLookupLimit *uint64 `toml:",omitempty"`
TransactionHistory *uint64 `toml:",omitempty"`
StateHistory *uint64 `toml:",omitempty"`
StateScheme *string `toml:",omitempty"`
PathSyncFlush *bool `toml:",omitempty"`
TxLookupLimit *uint64 `toml:",omitempty"`
TransactionHistory *uint64 `toml:",omitempty"`
StateHistory *uint64 `toml:",omitempty"`
StateScheme *string `toml:",omitempty"`
PathSyncFlush *bool `toml:",omitempty"`
JournalFileEnabled *bool
RequiredBlocks map[uint64]common.Hash `toml:"-"`
LightServ *int `toml:",omitempty"`
LightIngress *int `toml:",omitempty"`
@@ -191,8 +192,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
RPCTxFeeCap *float64
OverrideCancun *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"`
OverrideFeynman *uint64 `toml:",omitempty"`
OverrideFeynmanFix *uint64 `toml:",omitempty"`
BlobExtraReserve *uint64
}
var dec Config
if err := unmarshal(&dec); err != nil {
@@ -258,6 +258,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.PathSyncFlush != nil {
c.PathSyncFlush = *dec.PathSyncFlush
}
if dec.JournalFileEnabled != nil {
c.JournalFileEnabled = *dec.JournalFileEnabled
}
if dec.RequiredBlocks != nil {
c.RequiredBlocks = dec.RequiredBlocks
}
@@ -360,11 +363,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.OverrideVerkle != nil {
c.OverrideVerkle = dec.OverrideVerkle
}
if dec.OverrideFeynman != nil {
c.OverrideFeynman = dec.OverrideFeynman
}
if dec.OverrideFeynmanFix != nil {
c.OverrideFeynmanFix = dec.OverrideFeynmanFix
if dec.BlobExtraReserve != nil {
c.BlobExtraReserve = *dec.BlobExtraReserve
}
return nil
}

View File

@@ -22,6 +22,7 @@ import (
"math/rand"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus"
@@ -66,6 +67,10 @@ var (
headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/out", nil)
bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/in", nil)
bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil)
blockInsertFailRecords = mapset.NewSet[common.Hash]()
blockInsertFailRecordslimit = 1000
blockInsertFailGauge = metrics.NewRegisteredGauge("chain/insert/failed", nil)
)
var errTerminated = errors.New("terminated")
@@ -934,6 +939,10 @@ func (f *BlockFetcher) importBlocks(op *blockOrHeaderInject) {
}
// Run the actual import and log any issues
if _, err := f.insertChain(types.Blocks{block}); err != nil {
if blockInsertFailRecords.Cardinality() < blockInsertFailRecordslimit {
blockInsertFailRecords.Add(block.Hash())
blockInsertFailGauge.Update(int64(blockInsertFailRecords.Cardinality()))
}
log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
return
}

View File

@@ -44,6 +44,9 @@ var (
// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0
const maxTopics = 4
// The maximum number of allowed topics within a topic criteria
const maxSubTopics = 1000
// filter is a helper struct that holds meta information over the filter type
// and associated subscription in the event system.
type filter struct {
@@ -671,6 +674,9 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
return errors.New("invalid addresses in query")
}
}
if len(raw.Topics) > maxTopics {
return errExceedMaxTopics
}
// topics is an array consisting of strings and/or arrays of strings.
// JSON null values are converted to common.Hash{} and ignored by the filter manager.
@@ -691,6 +697,9 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
case []interface{}:
// or case e.g. [null, "topic0", "topic1"]
if len(topic) > maxSubTopics {
return errExceedMaxTopics
}
for _, rawTopic := range topic {
if rawTopic == nil {
// null component, match all

View File

@@ -318,7 +318,30 @@ func newHandler(config *handlerConfig) (*handler, error) {
}
return h.chain.InsertChain(blocks)
}
h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock,
broadcastBlockWithCheck := func(block *types.Block, propagate bool) {
if propagate {
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)
}
h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, broadcastBlockWithCheck,
heighter, finalizeHeighter, nil, inserter, h.removePeer)
fetchTx := func(peer string, hashes []common.Hash) error {

View File

@@ -131,9 +131,9 @@ func (ec *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumb
}
// BlobSidecars return the Sidecars of a given block number or hash.
func (ec *Client) BlobSidecars(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.BlobTxSidecar, error) {
func (ec *Client) BlobSidecars(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, fullBlob bool) ([]*types.BlobTxSidecar, error) {
var r []*types.BlobTxSidecar
err := ec.c.CallContext(ctx, &r, "eth_getBlobSidecars", blockNrOrHash.String())
err := ec.c.CallContext(ctx, &r, "eth_getBlobSidecars", blockNrOrHash.String(), fullBlob)
if err == nil && r == nil {
return nil, ethereum.NotFound
}
@@ -141,9 +141,9 @@ func (ec *Client) BlobSidecars(ctx context.Context, blockNrOrHash rpc.BlockNumbe
}
// BlobSidecarByTxHash return a sidecar of a given blob transaction
func (ec *Client) BlobSidecarByTxHash(ctx context.Context, hash common.Hash) (*types.BlobTxSidecar, error) {
func (ec *Client) BlobSidecarByTxHash(ctx context.Context, hash common.Hash, fullBlob bool) (*types.BlobTxSidecar, error) {
var r *types.BlobTxSidecar
err := ec.c.CallContext(ctx, &r, "eth_getBlockSidecarByTxHash", hash)
err := ec.c.CallContext(ctx, &r, "eth_getBlockSidecarByTxHash", hash, fullBlob)
if err == nil && r == nil {
return nil, ethereum.NotFound
}

View File

@@ -48,6 +48,9 @@ const (
// numLevels is the level number of pebble sst files
numLevels = 7
// degradationWarnInterval specifies how often warning should be printed if the
// leveldb database cannot keep up with requested writes.
degradationWarnInterval = time.Minute
)
// Database is a persistent key-value store based on the pebble storage engine.
@@ -79,14 +82,16 @@ type Database struct {
log log.Logger // Contextual logger tracking the database path
activeComp int // Current number of active compactions
compStartTime time.Time // The start time of the earliest currently-active compaction
compTime atomic.Int64 // Total time spent in compaction in ns
level0Comp atomic.Uint32 // Total number of level-zero compactions
nonLevel0Comp atomic.Uint32 // Total number of non level-zero compactions
writeDelayStartTime time.Time // The start time of the latest write stall
writeDelayCount atomic.Int64 // Total number of write stall counts
writeDelayTime atomic.Int64 // Total time spent in write stalls
activeComp int // Current number of active compactions
compStartTime time.Time // The start time of the earliest currently-active compaction
compTime atomic.Int64 // Total time spent in compaction in ns
level0Comp atomic.Uint32 // Total number of level-zero compactions
nonLevel0Comp atomic.Uint32 // Total number of non level-zero compactions
writeStalled atomic.Bool // Flag whether the write is stalled
writeDelayStartTime time.Time // The start time of the latest write stall
writeDelayCount atomic.Int64 // Total number of write stall counts
writeDelayTime atomic.Int64 // Total time spent in write stalls
writeOptions *pebble.WriteOptions
}
@@ -115,10 +120,13 @@ func (d *Database) onCompactionEnd(info pebble.CompactionInfo) {
func (d *Database) onWriteStallBegin(b pebble.WriteStallBeginInfo) {
d.writeDelayStartTime = time.Now()
d.writeDelayCount.Add(1)
d.writeStalled.Store(true)
}
func (d *Database) onWriteStallEnd() {
d.writeDelayTime.Add(int64(time.Since(d.writeDelayStartTime)))
d.writeStalled.Store(false)
}
// panicLogger is just a noop logger to disable Pebble's internal logger.
@@ -461,13 +469,15 @@ func (d *Database) meter(refresh time.Duration, namespace string) {
// Create storage and warning log tracer for write delay.
var (
compTimes [2]int64
writeDelayTimes [2]int64
writeDelayCounts [2]int64
compWrites [2]int64
compReads [2]int64
compTimes [2]int64
compWrites [2]int64
compReads [2]int64
nWrites [2]int64
writeDelayTimes [2]int64
writeDelayCounts [2]int64
lastWriteStallReport time.Time
)
// Iterate ad infinitum and collect the stats
@@ -507,6 +517,13 @@ func (d *Database) meter(refresh time.Duration, namespace string) {
if d.writeDelayMeter != nil {
d.writeDelayMeter.Mark(writeDelayTimes[i%2] - writeDelayTimes[(i-1)%2])
}
// Print a warning log if writing has been stalled for a while. The log will
// be printed per minute to avoid overwhelming users.
if d.writeStalled.Load() && writeDelayCounts[i%2] == writeDelayCounts[(i-1)%2] &&
time.Now().After(lastWriteStallReport.Add(degradationWarnInterval)) {
d.log.Warn("Database compacting, degraded performance")
lastWriteStallReport = time.Now()
}
if d.compTimeMeter != nil {
d.compTimeMeter.Mark(compTimes[i%2] - compTimes[(i-1)%2])
}
@@ -600,8 +617,8 @@ func (b *batch) Reset() {
func (b *batch) Replay(w ethdb.KeyValueWriter) error {
reader := b.b.Reader()
for {
kind, k, v, ok := reader.Next()
if !ok {
kind, k, v, ok, err := reader.Next()
if !ok || err != nil {
break
}
// The (k,v) slices might be overwritten if the batch is reset/reused,

10
go.mod
View File

@@ -17,8 +17,8 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/cespare/cp v1.1.1
github.com/cloudflare/cloudflare-go v0.79.0
github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593
github.com/cometbft/cometbft v0.37.0
github.com/cockroachdb/pebble v1.1.0
github.com/consensys/gnark-crypto v0.12.1
github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233
github.com/crate-crypto/go-kzg-4844 v0.7.0
@@ -81,10 +81,10 @@ require (
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3
github.com/willf/bitset v1.1.3
go.uber.org/automaxprocs v1.5.2
golang.org/x/crypto v0.19.0
golang.org/x/crypto v0.21.0
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
golang.org/x/sync v0.6.0
golang.org/x/sys v0.17.0
golang.org/x/sys v0.18.0
golang.org/x/text v0.14.0
golang.org/x/time v0.5.0
golang.org/x/tools v0.18.0
@@ -274,9 +274,9 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/term v0.18.0 // indirect
google.golang.org/api v0.44.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect

20
go.sum
View File

@@ -275,8 +275,8 @@ github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZ
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A=
github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo=
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
@@ -1743,8 +1743,8 @@ golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1857,8 +1857,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -2007,13 +2007,13 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -1010,7 +1010,7 @@ func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.
return result, nil
}
func (s *BlockChainAPI) GetBlobSidecars(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
func (s *BlockChainAPI) GetBlobSidecars(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, fullBlob bool) ([]map[string]interface{}, error) {
header, err := s.b.HeaderByNumberOrHash(ctx, blockNrOrHash)
if header == nil || err != nil {
// When the block doesn't exist, the RPC method should return JSON null
@@ -1023,12 +1023,12 @@ func (s *BlockChainAPI) GetBlobSidecars(ctx context.Context, blockNrOrHash rpc.B
}
result := make([]map[string]interface{}, len(blobSidecars))
for i, sidecar := range blobSidecars {
result[i] = marshalBlobSidecar(sidecar)
result[i] = marshalBlobSidecar(sidecar, fullBlob)
}
return result, nil
}
func (s *BlockChainAPI) GetBlobSidecarByTxHash(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
func (s *BlockChainAPI) GetBlobSidecarByTxHash(ctx context.Context, hash common.Hash, fullBlob bool) (map[string]interface{}, error) {
txTarget, blockHash, _, Index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
if txTarget == nil {
return nil, nil
@@ -1045,7 +1045,7 @@ func (s *BlockChainAPI) GetBlobSidecarByTxHash(ctx context.Context, hash common.
}
for _, sidecar := range blobSidecars {
if sidecar.TxIndex == Index {
return marshalBlobSidecar(sidecar), nil
return marshalBlobSidecar(sidecar, fullBlob), nil
}
}
@@ -2142,13 +2142,31 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u
return fields
}
func marshalBlobSidecar(sidecar *types.BlobSidecar) map[string]interface{} {
func marshalBlobSidecar(sidecar *types.BlobSidecar, fullBlob bool) map[string]interface{} {
fields := map[string]interface{}{
"blockHash": sidecar.BlockHash,
"blockNumber": hexutil.EncodeUint64(sidecar.BlockNumber.Uint64()),
"txHash": sidecar.TxHash,
"txIndex": hexutil.EncodeUint64(sidecar.TxIndex),
"blobSidecar": sidecar.BlobTxSidecar,
}
fields["blobSidecar"] = marshalBlob(sidecar.BlobTxSidecar, fullBlob)
return fields
}
func marshalBlob(blobTxSidecar types.BlobTxSidecar, fullBlob bool) map[string]interface{} {
fields := map[string]interface{}{
"blobs": blobTxSidecar.Blobs,
"commitments": blobTxSidecar.Commitments,
"proofs": blobTxSidecar.Proofs,
}
if !fullBlob {
var blobs []common.Hash
for _, blob := range blobTxSidecar.Blobs {
var value common.Hash
copy(value[:], blob[:32])
blobs = append(blobs, value)
}
fields["blobs"] = blobs
}
return fields
}

View File

@@ -2139,48 +2139,63 @@ func TestRPCGetBlobSidecars(t *testing.T) {
}
var testSuite = []struct {
test rpc.BlockNumberOrHash
file string
test rpc.BlockNumberOrHash
fullBlob bool
file string
}{
// 1. block without any txs(number)
{
test: rpc.BlockNumberOrHashWithNumber(0),
file: "number-1",
test: rpc.BlockNumberOrHashWithNumber(0),
fullBlob: true,
file: "number-1",
},
// 2. earliest tag
{
test: rpc.BlockNumberOrHashWithNumber(rpc.EarliestBlockNumber),
file: "tag-earliest",
test: rpc.BlockNumberOrHashWithNumber(rpc.EarliestBlockNumber),
fullBlob: true,
file: "tag-earliest",
},
// 3. latest tag
{
test: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber),
file: "tag-latest",
test: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber),
fullBlob: true,
file: "tag-latest",
},
// 4. block is empty
{
test: rpc.BlockNumberOrHashWithHash(common.Hash{}, false),
file: "hash-empty",
test: rpc.BlockNumberOrHashWithHash(common.Hash{}, false),
fullBlob: true,
file: "hash-empty",
},
// 5. block is not found
{
test: rpc.BlockNumberOrHashWithHash(common.HexToHash("deadbeef"), false),
file: "hash-notfound",
test: rpc.BlockNumberOrHashWithHash(common.HexToHash("deadbeef"), false),
fullBlob: true,
file: "hash-notfound",
},
// 6. block is not found
{
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(genBlocks + 1)),
file: "block-notfound",
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(genBlocks + 1)),
fullBlob: true,
file: "block-notfound",
},
// 7. block with blob tx
{
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(6)),
file: "block-with-blob-tx",
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(6)),
fullBlob: true,
file: "block-with-blob-tx",
},
// 8. block with sidecar
{
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(7)),
file: "block-with-blobSidecars",
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(7)),
fullBlob: true,
file: "block-with-blobSidecars",
},
// 9. block with sidecar but show little
{
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(7)),
fullBlob: false,
file: "block-with-blobSidecars-show-little",
},
}
@@ -2189,7 +2204,7 @@ func TestRPCGetBlobSidecars(t *testing.T) {
result interface{}
err error
)
result, err = api.GetBlobSidecars(context.Background(), tt.test)
result, err = api.GetBlobSidecars(context.Background(), tt.test, tt.fullBlob)
if err != nil {
t.Errorf("test %d: want no error, have %v", i, err)
continue
@@ -2205,33 +2220,45 @@ func TestGetBlobSidecarByTxHash(t *testing.T) {
api = NewBlockChainAPI(backend)
)
var testSuite = []struct {
test common.Hash
file string
test common.Hash
fullBlob bool
file string
}{
// 0. txHash is empty
{
test: common.Hash{},
file: "hash-empty",
test: common.Hash{},
fullBlob: true,
file: "hash-empty",
},
// 1. txHash is not found
{
test: common.HexToHash("deadbeef"),
file: "hash-notfound",
test: common.HexToHash("deadbeef"),
fullBlob: true,
file: "hash-notfound",
},
// 2. txHash is not blob tx
{
test: common.HexToHash("deadbeef"),
file: "not-blob-tx",
test: common.HexToHash("deadbeef"),
fullBlob: true,
file: "not-blob-tx",
},
// 3. block with blob tx without sidecar
{
test: txHashs[5],
file: "block-with-blob-tx",
test: txHashs[5],
fullBlob: true,
file: "block-with-blob-tx",
},
// 4. block with sidecar
{
test: txHashs[6],
file: "block-with-blobSidecars",
test: txHashs[6],
fullBlob: true,
file: "block-with-blobSidecars",
},
// 4. block show part blobs
{
test: txHashs[6],
fullBlob: false,
file: "block-with-blobSidecars-show-little",
},
}
@@ -2240,7 +2267,7 @@ func TestGetBlobSidecarByTxHash(t *testing.T) {
result interface{}
err error
)
result, err = api.GetBlobSidecarByTxHash(context.Background(), tt.test)
result, err = api.GetBlobSidecarByTxHash(context.Background(), tt.test, tt.fullBlob)
if err != nil {
t.Errorf("test %d: want no error, have %v", i, err)
continue

View File

@@ -0,0 +1,17 @@
{
"blobSidecar": {
"blobs": [
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
"commitments": [
"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"proofs": [
"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
]
},
"blockHash": "0x6fe5e7eea22c44f700c95da066d6e0740894b6c1e993825cc63f634ad4f74250",
"blockNumber": "0x7",
"txHash": "0xc520427e696154779f6b21ab03a0735769e1c029035a484f5876f60383a0a7ce",
"txIndex": "0x0"
}

View File

@@ -0,0 +1,19 @@
[
{
"blobSidecar": {
"blobs": [
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
"commitments": [
"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"proofs": [
"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
]
},
"blockHash": "0x6fe5e7eea22c44f700c95da066d6e0740894b6c1e993825cc63f634ad4f74250",
"blockNumber": "0x7",
"txHash": "0xc520427e696154779f6b21ab03a0735769e1c029035a484f5876f60383a0a7ce",
"txIndex": "0x0"
}
]

View File

@@ -617,12 +617,12 @@ web3._extend({
new web3._extend.Method({
name: 'getBlobSidecars',
call: 'eth_getBlobSidecars',
params: 1,
params: 2,
}),
new web3._extend.Method({
name: 'getBlobSidecarByTxHash',
call: 'eth_getBlobSidecarByTxHash',
params: 1,
params: 2,
}),
],
properties: [

View File

@@ -295,3 +295,7 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) {
return miner.worker.buildPayload(args)
}
func (miner *Miner) GasCeil() uint64 {
return miner.worker.getGasCeil()
}

View File

@@ -329,6 +329,12 @@ func (w *worker) setGasCeil(ceil uint64) {
w.config.GasCeil = ceil
}
func (w *worker) getGasCeil() uint64 {
w.mu.Lock()
defer w.mu.Unlock()
return w.config.GasCeil
}
// setExtra sets the content used to initialize the block extra field.
func (w *worker) setExtra(extra []byte) {
w.mu.Lock()

View File

@@ -147,13 +147,11 @@ var (
LondonBlock: big.NewInt(31302048),
HertzBlock: big.NewInt(31302048),
HertzfixBlock: big.NewInt(34140700),
// UnixTime: 1705996800 is January 23, 2024 8:00:00 AM UTC
ShanghaiTime: newUint64(1705996800),
KeplerTime: newUint64(1705996800),
FeynmanTime: newUint64(1713419340),
FeynmanFixTime: newUint64(1713419340),
// TODO(GalaIO): enable cancun fork time later
//CancunTime: newUint64(),
ShanghaiTime: newUint64(1705996800), // 2024-01-23 08:00:00 AM UTC
KeplerTime: newUint64(1705996800), // 2024-01-23 08:00:00 AM UTC
FeynmanTime: newUint64(1713419340), // 2024-04-18 05:49:00 AM UTC
FeynmanFixTime: newUint64(1713419340), // 2024-04-18 05:49:00 AM UTC
CancunTime: newUint64(1718863500), // 2024-06-20 06:05:00 AM UTC
Parlia: &ParliaConfig{
Period: 3,

View File

@@ -189,6 +189,7 @@ const (
var (
MinBlocksForBlobRequests uint64 = 524288 // it keeps blob data available for ~18.2 days in local, ref: https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP-336.md#51-parameters.
DefaultExtraReserveForBlobRequests uint64 = 1 * (24 * 3600) / 3 // it adds more time for expired blobs for some request cases, like expiry blob when remote peer is syncing, default 1 day.
BreatheBlockInterval uint64 = 86400 // Controls the interval for updateValidatorSetV2
)
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations

View File

@@ -23,7 +23,7 @@ import (
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 4 // Minor version component of the current release
VersionPatch = 6 // Patch version component of the current release
VersionPatch = 7 // Patch version component of the current release
VersionMeta = "" // Version metadata to append to the version string
)

View File

@@ -181,7 +181,7 @@ func (h2p *Hbss2Pbss) ConcurrentTraversal(theTrie *Trie, theNode node, path []by
}
}
// copy from trie/Commiter (*committer).commit
// copy from trie/Committer (*committer).commit
func (h2p *Hbss2Pbss) commitChildren(path []byte, n *fullNode) [17]node {
var children [17]node
for i := 0; i < 16; i++ {

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

@@ -195,7 +195,8 @@ func newJournalReader(file string, db ethdb.Database, journalType JournalType) (
// loadJournal tries to parse the layer journal from the disk.
func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
start := time.Now()
reader, err := newJournalReader(db.config.JournalFilePath, db.diskdb, db.DetermineJournalTypeForReader())
journalTypeForReader := db.DetermineJournalTypeForReader()
reader, err := newJournalReader(db.config.JournalFilePath, db.diskdb, journalTypeForReader)
if err != nil {
return nil, err
@@ -226,12 +227,12 @@ func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
return nil, fmt.Errorf("%w want %x got %x", errUnmatchedJournal, root, diskRoot)
}
// Load the disk layer from the journal
base, err := db.loadDiskLayer(r)
base, err := db.loadDiskLayer(r, journalTypeForReader)
if err != nil {
return nil, err
}
// Load all the diff layers from the journal
head, err := db.loadDiffLayer(base, r)
head, err := db.loadDiffLayer(base, r, journalTypeForReader)
if err != nil {
return nil, err
}
@@ -262,14 +263,14 @@ func (db *Database) loadLayers() layer {
// loadDiskLayer reads the binary blob from the layer journal, reconstructing
// a new disk layer on it.
func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
func (db *Database) loadDiskLayer(r *rlp.Stream, journalTypeForReader JournalType) (layer, error) {
// Resolve disk layer root
var (
root common.Hash
journalBuf *rlp.Stream
journalEncodedBuff []byte
)
if db.DetermineJournalTypeForReader() == JournalFileType {
if journalTypeForReader == JournalFileType {
if err := r.Decode(&journalEncodedBuff); err != nil {
return nil, fmt.Errorf("load disk journal: %v", err)
}
@@ -310,7 +311,7 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
nodes[entry.Owner] = subset
}
if db.DetermineJournalTypeForReader() == JournalFileType {
if journalTypeForReader == JournalFileType {
var shaSum [32]byte
if err := r.Decode(&shaSum); err != nil {
return nil, fmt.Errorf("load shasum: %v", err)
@@ -329,14 +330,14 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
// loadDiffLayer reads the next sections of a layer journal, reconstructing a new
// diff and verifying that it can be linked to the requested parent.
func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) {
func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream, journalTypeForReader JournalType) (layer, error) {
// Read the next diff journal entry
var (
root common.Hash
journalBuf *rlp.Stream
journalEncodedBuff []byte
)
if db.DetermineJournalTypeForReader() == JournalFileType {
if journalTypeForReader == JournalFileType {
if err := r.Decode(&journalEncodedBuff); err != nil {
// The first read may fail with EOF, marking the end of the journal
if err == io.EOF {
@@ -409,7 +410,7 @@ func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) {
storages[entry.Account] = set
}
if db.DetermineJournalTypeForReader() == JournalFileType {
if journalTypeForReader == JournalFileType {
var shaSum [32]byte
if err := r.Decode(&shaSum); err != nil {
return nil, fmt.Errorf("load shasum: %v", err)
@@ -423,7 +424,7 @@ func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) {
log.Debug("Loaded diff layer journal", "root", root, "parent", parent.rootHash(), "id", parent.stateID()+1, "block", block)
return db.loadDiffLayer(newDiffLayer(parent, root, parent.stateID()+1, block, nodes, triestate.New(accounts, storages, incomplete)), r)
return db.loadDiffLayer(newDiffLayer(parent, root, parent.stateID()+1, block, nodes, triestate.New(accounts, storages, incomplete)), r, journalTypeForReader)
}
// journal implements the layer interface, marshaling the un-flushed trie nodes