mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
Merge branch 'master' into engine-otel-tracing
This commit is contained in:
commit
adb8e8fb23
204 changed files with 15283 additions and 2184 deletions
|
|
@ -158,10 +158,10 @@ func testLinkCase(tcInput linkTestCaseInput) error {
|
||||||
overrideAddrs = make(map[rune]common.Address)
|
overrideAddrs = make(map[rune]common.Address)
|
||||||
)
|
)
|
||||||
// generate deterministic addresses for the override set.
|
// generate deterministic addresses for the override set.
|
||||||
rand.Seed(42)
|
rng := rand.New(rand.NewSource(42))
|
||||||
for contract := range tcInput.overrides {
|
for contract := range tcInput.overrides {
|
||||||
var addr common.Address
|
var addr common.Address
|
||||||
rand.Read(addr[:])
|
rng.Read(addr[:])
|
||||||
overrideAddrs[contract] = addr
|
overrideAddrs[contract] = addr
|
||||||
overridesAddrs[addr] = struct{}{}
|
overridesAddrs[addr] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account represents an Ethereum account located at a specific location defined
|
// Account represents an Ethereum account located at a specific location defined
|
||||||
|
|
@ -196,7 +196,7 @@ func TextHash(data []byte) []byte {
|
||||||
// This gives context to the signed message and prevents signing of transactions.
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
func TextAndHash(data []byte) ([]byte, string) {
|
func TextAndHash(data []byte) ([]byte, string) {
|
||||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
hasher.Write([]byte(msg))
|
hasher.Write([]byte(msg))
|
||||||
return hasher.Sum(nil), msg
|
return hasher.Sum(nil), msg
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,10 @@ func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 || len(data)%aes.BlockSize != 0 {
|
||||||
|
return nil, fmt.Errorf("invalid ciphertext length: %d", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
ret := make([]byte, len(data))
|
ret := make([]byte, len(data))
|
||||||
|
|
||||||
crypter := cipher.NewCBCDecrypter(a, s.iv)
|
crypter := cipher.NewCBCDecrypter(a, s.iv)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ const refreshCycle = time.Second
|
||||||
// trashing.
|
// trashing.
|
||||||
const refreshThrottling = 500 * time.Millisecond
|
const refreshThrottling = 500 * time.Millisecond
|
||||||
|
|
||||||
|
const (
|
||||||
|
// deviceUsagePage identifies Ledger devices by HID usage page (0xffa0) on Windows and macOS.
|
||||||
|
// See: https://github.com/LedgerHQ/ledger-live/blob/05a2980e838955a11a1418da638ef8ac3df4fb74/libs/ledgerjs/packages/hw-transport-node-hid-noevents/src/TransportNodeHid.ts
|
||||||
|
deviceUsagePage = 0xffa0
|
||||||
|
// deviceInterface identifies Ledger devices by USB interface number (0) on Linux.
|
||||||
|
deviceInterface = 0
|
||||||
|
)
|
||||||
|
|
||||||
// Hub is a accounts.Backend that can find and handle generic USB hardware wallets.
|
// Hub is a accounts.Backend that can find and handle generic USB hardware wallets.
|
||||||
type Hub struct {
|
type Hub struct {
|
||||||
scheme string // Protocol scheme prefixing account and wallet URLs.
|
scheme string // Protocol scheme prefixing account and wallet URLs.
|
||||||
|
|
@ -82,6 +90,7 @@ func NewLedgerHub() (*Hub, error) {
|
||||||
0x0005, /* Ledger Nano S Plus */
|
0x0005, /* Ledger Nano S Plus */
|
||||||
0x0006, /* Ledger Nano FTS */
|
0x0006, /* Ledger Nano FTS */
|
||||||
0x0007, /* Ledger Flex */
|
0x0007, /* Ledger Flex */
|
||||||
|
0x0008, /* Ledger Nano Gen5 */
|
||||||
|
|
||||||
0x0000, /* WebUSB Ledger Blue */
|
0x0000, /* WebUSB Ledger Blue */
|
||||||
0x1000, /* WebUSB Ledger Nano S */
|
0x1000, /* WebUSB Ledger Nano S */
|
||||||
|
|
@ -89,7 +98,8 @@ func NewLedgerHub() (*Hub, error) {
|
||||||
0x5000, /* WebUSB Ledger Nano S Plus */
|
0x5000, /* WebUSB Ledger Nano S Plus */
|
||||||
0x6000, /* WebUSB Ledger Nano FTS */
|
0x6000, /* WebUSB Ledger Nano FTS */
|
||||||
0x7000, /* WebUSB Ledger Flex */
|
0x7000, /* WebUSB Ledger Flex */
|
||||||
}, 0xffa0, 0, newLedgerDriver)
|
0x8000, /* WebUSB Ledger Nano Gen5 */
|
||||||
|
}, deviceUsagePage, deviceInterface, newLedgerDriver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices.
|
// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices.
|
||||||
|
|
|
||||||
32
circle.yml
32
circle.yml
|
|
@ -1,32 +0,0 @@
|
||||||
machine:
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
cache_directories:
|
|
||||||
- "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds
|
|
||||||
- "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds
|
|
||||||
override:
|
|
||||||
# Restore all previously cached docker images
|
|
||||||
- mkdir -p ~/.docker
|
|
||||||
- for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done
|
|
||||||
|
|
||||||
# Pull in and hive, restore cached ethash DAGs and do a dry run
|
|
||||||
- go get -u github.com/karalabe/hive
|
|
||||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash)
|
|
||||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/)
|
|
||||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6)
|
|
||||||
|
|
||||||
# Cache all the docker images and the ethash DAGs
|
|
||||||
- for img in `docker images | grep -v "^<none>" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done
|
|
||||||
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash
|
|
||||||
|
|
||||||
test:
|
|
||||||
override:
|
|
||||||
# Build Geth and move into a known folder
|
|
||||||
- make geth
|
|
||||||
- cp ./build/bin/geth $HOME/geth
|
|
||||||
|
|
||||||
# Run hive and move all generated logs into the public artifacts folder
|
|
||||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.)
|
|
||||||
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS
|
|
||||||
128
cmd/era/main.go
128
cmd/era/main.go
|
|
@ -30,6 +30,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/execdb"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/onedb"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -53,7 +55,7 @@ var (
|
||||||
eraSizeFlag = &cli.IntFlag{
|
eraSizeFlag = &cli.IntFlag{
|
||||||
Name: "size",
|
Name: "size",
|
||||||
Usage: "number of blocks per era",
|
Usage: "number of blocks per era",
|
||||||
Value: era.MaxEra1Size,
|
Value: era.MaxSize,
|
||||||
}
|
}
|
||||||
txsFlag = &cli.BoolFlag{
|
txsFlag = &cli.BoolFlag{
|
||||||
Name: "txs",
|
Name: "txs",
|
||||||
|
|
@ -131,7 +133,7 @@ func block(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// info prints some high-level information about the era1 file.
|
// info prints some high-level information about the era file.
|
||||||
func info(ctx *cli.Context) error {
|
func info(ctx *cli.Context) error {
|
||||||
epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64)
|
epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -142,33 +144,34 @@ func info(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer e.Close()
|
defer e.Close()
|
||||||
acc, err := e.Accumulator()
|
var (
|
||||||
if err != nil {
|
accHex string
|
||||||
return fmt.Errorf("error reading accumulator: %w", err)
|
tdStr string
|
||||||
|
)
|
||||||
|
if acc, err := e.Accumulator(); err == nil {
|
||||||
|
accHex = acc.Hex()
|
||||||
}
|
}
|
||||||
td, err := e.InitialTD()
|
if td, err := e.InitialTD(); err == nil {
|
||||||
if err != nil {
|
tdStr = td.String()
|
||||||
return fmt.Errorf("error reading total difficulty: %w", err)
|
|
||||||
}
|
}
|
||||||
info := struct {
|
info := struct {
|
||||||
Accumulator common.Hash `json:"accumulator"`
|
Accumulator string `json:"accumulator,omitempty"`
|
||||||
TotalDifficulty *big.Int `json:"totalDifficulty"`
|
TotalDifficulty string `json:"totalDifficulty,omitempty"`
|
||||||
StartBlock uint64 `json:"startBlock"`
|
StartBlock uint64 `json:"startBlock"`
|
||||||
Count uint64 `json:"count"`
|
Count uint64 `json:"count"`
|
||||||
}{
|
}{
|
||||||
acc, td, e.Start(), e.Count(),
|
accHex, tdStr, e.Start(), e.Count(),
|
||||||
}
|
}
|
||||||
b, _ := json.MarshalIndent(info, "", " ")
|
b, _ := json.MarshalIndent(info, "", " ")
|
||||||
fmt.Println(string(b))
|
fmt.Println(string(b))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// open opens an era1 file at a certain epoch.
|
// open opens an era file at a certain epoch.
|
||||||
func open(ctx *cli.Context, epoch uint64) (*era.Era, error) {
|
func open(ctx *cli.Context, epoch uint64) (era.Era, error) {
|
||||||
var (
|
dir := ctx.String(dirFlag.Name)
|
||||||
dir = ctx.String(dirFlag.Name)
|
network := ctx.String(networkFlag.Name)
|
||||||
network = ctx.String(networkFlag.Name)
|
|
||||||
)
|
|
||||||
entries, err := era.ReadDir(dir, network)
|
entries, err := era.ReadDir(dir, network)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading era dir: %w", err)
|
return nil, fmt.Errorf("error reading era dir: %w", err)
|
||||||
|
|
@ -176,7 +179,28 @@ func open(ctx *cli.Context, epoch uint64) (*era.Era, error) {
|
||||||
if epoch >= uint64(len(entries)) {
|
if epoch >= uint64(len(entries)) {
|
||||||
return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch)
|
return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch)
|
||||||
}
|
}
|
||||||
return era.Open(filepath.Join(dir, entries[epoch]))
|
path := filepath.Join(dir, entries[epoch])
|
||||||
|
return openByPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// openByPath tries to open a single file as either eraE or era1 based on extension,
|
||||||
|
// falling back to the other reader if needed.
|
||||||
|
func openByPath(path string) (era.Era, error) {
|
||||||
|
switch strings.ToLower(filepath.Ext(path)) {
|
||||||
|
case ".erae":
|
||||||
|
if e, err := execdb.Open(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
case ".era1":
|
||||||
|
if e, err := onedb.Open(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unsupported or unreadable era file: %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify checks each era1 file in a directory to ensure it is well-formed and
|
// verify checks each era1 file in a directory to ensure it is well-formed and
|
||||||
|
|
@ -203,18 +227,58 @@ func verify(ctx *cli.Context) error {
|
||||||
return fmt.Errorf("error reading %s: %w", dir, err)
|
return fmt.Errorf("error reading %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) != len(roots) {
|
// Build the verification list respecting the rule:
|
||||||
return errors.New("number of era1 files should match the number of accumulator hashes")
|
// era1: must have accumulator, always verify
|
||||||
|
// erae: verify only if accumulator exists (pre-merge)
|
||||||
|
|
||||||
|
// Build list of files to verify.
|
||||||
|
verify := make([]string, 0, len(entries))
|
||||||
|
|
||||||
|
for _, name := range entries {
|
||||||
|
path := filepath.Join(dir, name)
|
||||||
|
ext := strings.ToLower(filepath.Ext(name))
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case ".era1":
|
||||||
|
e, err := onedb.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening era1 file %s: %w", name, err)
|
||||||
|
}
|
||||||
|
_, accErr := e.Accumulator()
|
||||||
|
e.Close()
|
||||||
|
if accErr != nil {
|
||||||
|
return fmt.Errorf("era1 file %s missing accumulator: %w", name, accErr)
|
||||||
|
}
|
||||||
|
verify = append(verify, path)
|
||||||
|
|
||||||
|
case ".erae":
|
||||||
|
e, err := execdb.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening erae file %s: %w", name, err)
|
||||||
|
}
|
||||||
|
_, accErr := e.Accumulator()
|
||||||
|
e.Close()
|
||||||
|
if accErr == nil {
|
||||||
|
verify = append(verify, path) // pre-merge only
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported era file: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(verify) != len(roots) {
|
||||||
|
return fmt.Errorf("mismatch between eras to verify (%d) and provided roots (%d)", len(verify), len(roots))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify each epoch matches the expected root.
|
// Verify each epoch matches the expected root.
|
||||||
for i, want := range roots {
|
for i, want := range roots {
|
||||||
// Wrap in function so defers don't stack.
|
// Wrap in function so defers don't stack.
|
||||||
err := func() error {
|
err := func() error {
|
||||||
name := entries[i]
|
path := verify[i]
|
||||||
e, err := era.Open(filepath.Join(dir, name))
|
name := filepath.Base(path)
|
||||||
|
e, err := openByPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error opening era1 file %s: %w", name, err)
|
return fmt.Errorf("error opening era file %s: %w", name, err)
|
||||||
}
|
}
|
||||||
defer e.Close()
|
defer e.Close()
|
||||||
// Read accumulator and check against expected.
|
// Read accumulator and check against expected.
|
||||||
|
|
@ -243,7 +307,7 @@ func verify(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAccumulator verifies the accumulator matches the data in the Era.
|
// checkAccumulator verifies the accumulator matches the data in the Era.
|
||||||
func checkAccumulator(e *era.Era) error {
|
func checkAccumulator(e era.Era) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
want common.Hash
|
want common.Hash
|
||||||
|
|
@ -257,7 +321,7 @@ func checkAccumulator(e *era.Era) error {
|
||||||
if td, err = e.InitialTD(); err != nil {
|
if td, err = e.InitialTD(); err != nil {
|
||||||
return fmt.Errorf("error reading total difficulty: %w", err)
|
return fmt.Errorf("error reading total difficulty: %w", err)
|
||||||
}
|
}
|
||||||
it, err := era.NewIterator(e)
|
it, err := e.Iterator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error making era iterator: %w", err)
|
return fmt.Errorf("error making era iterator: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -290,9 +354,13 @@ func checkAccumulator(e *era.Era) error {
|
||||||
if rr != block.ReceiptHash() {
|
if rr != block.ReceiptHash() {
|
||||||
return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr)
|
return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr)
|
||||||
}
|
}
|
||||||
hashes = append(hashes, block.Hash())
|
// Only include pre-merge blocks in accumulator calculation.
|
||||||
td.Add(td, block.Difficulty())
|
// Post-merge blocks have difficulty == 0.
|
||||||
tds = append(tds, new(big.Int).Set(td))
|
if block.Difficulty().Sign() > 0 {
|
||||||
|
hashes = append(hashes, block.Hash())
|
||||||
|
td.Add(td, block.Difficulty())
|
||||||
|
tds = append(tds, new(big.Int).Set(td))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if it.Error() != nil {
|
if it.Error() != nil {
|
||||||
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
|
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -40,7 +41,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Prestate struct {
|
type Prestate struct {
|
||||||
|
|
@ -415,7 +415,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func rlpHash(x any) (h common.Hash) {
|
func rlpHash(x any) (h common.Hash) {
|
||||||
hw := sha3.NewLegacyKeccak256()
|
hw := keccak.NewLegacyKeccak256()
|
||||||
rlp.Encode(hw, x)
|
rlp.Encode(hw, x)
|
||||||
hw.Sum(h[:0])
|
hw.Sum(h[:0])
|
||||||
return h
|
return h
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,6 @@ func Transaction(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
var results []result
|
var results []result
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
if err := it.Err(); err != nil {
|
|
||||||
return NewError(ErrorIO, err)
|
|
||||||
}
|
|
||||||
var tx types.Transaction
|
var tx types.Transaction
|
||||||
err := rlp.DecodeBytes(it.Value(), &tx)
|
err := rlp.DecodeBytes(it.Value(), &tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -188,6 +185,10 @@ func Transaction(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
}
|
}
|
||||||
|
if err := it.Err(); err != nil {
|
||||||
|
return NewError(ErrorIO, err)
|
||||||
|
}
|
||||||
|
|
||||||
out, err := json.MarshalIndent(results, "", " ")
|
out, err := json.MarshalIndent(results, "", " ")
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -43,6 +44,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
"github.com/ethereum/go-ethereum/internal/era/eradl"
|
"github.com/ethereum/go-ethereum/internal/era/eradl"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/execdb"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/onedb"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
|
@ -120,6 +123,8 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
utils.LogNoHistoryFlag,
|
utils.LogNoHistoryFlag,
|
||||||
utils.LogExportCheckpointsFlag,
|
utils.LogExportCheckpointsFlag,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
|
utils.TrienodeHistoryFlag,
|
||||||
|
utils.TrienodeHistoryFullValueCheckpointFlag,
|
||||||
}, utils.DatabaseFlags, debug.Flags),
|
}, utils.DatabaseFlags, debug.Flags),
|
||||||
Before: func(ctx *cli.Context) error {
|
Before: func(ctx *cli.Context) error {
|
||||||
flags.MigrateGlobalFlags(ctx)
|
flags.MigrateGlobalFlags(ctx)
|
||||||
|
|
@ -151,7 +156,7 @@ be gzipped.`,
|
||||||
Name: "import-history",
|
Name: "import-history",
|
||||||
Usage: "Import an Era archive",
|
Usage: "Import an Era archive",
|
||||||
ArgsUsage: "<dir>",
|
ArgsUsage: "<dir>",
|
||||||
Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag}, utils.DatabaseFlags, utils.NetworkFlags),
|
Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag, utils.EraFormatFlag}, utils.DatabaseFlags, utils.NetworkFlags),
|
||||||
Description: `
|
Description: `
|
||||||
The import-history command will import blocks and their corresponding receipts
|
The import-history command will import blocks and their corresponding receipts
|
||||||
from Era archives.
|
from Era archives.
|
||||||
|
|
@ -162,7 +167,7 @@ from Era archives.
|
||||||
Name: "export-history",
|
Name: "export-history",
|
||||||
Usage: "Export blockchain history to Era archives",
|
Usage: "Export blockchain history to Era archives",
|
||||||
ArgsUsage: "<dir> <first> <last>",
|
ArgsUsage: "<dir> <first> <last>",
|
||||||
Flags: utils.DatabaseFlags,
|
Flags: slices.Concat([]cli.Flag{utils.EraFormatFlag}, utils.DatabaseFlags),
|
||||||
Description: `
|
Description: `
|
||||||
The export-history command will export blocks and their corresponding receipts
|
The export-history command will export blocks and their corresponding receipts
|
||||||
into Era archives. Eras are typically packaged in steps of 8192 blocks.
|
into Era archives. Eras are typically packaged in steps of 8192 blocks.
|
||||||
|
|
@ -514,15 +519,27 @@ func importHistory(ctx *cli.Context) error {
|
||||||
network = networks[0]
|
network = networks[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.ImportHistory(chain, dir, network); err != nil {
|
var (
|
||||||
|
format = ctx.String(utils.EraFormatFlag.Name)
|
||||||
|
from func(era.ReadAtSeekCloser) (era.Era, error)
|
||||||
|
)
|
||||||
|
switch format {
|
||||||
|
case "era1", "era":
|
||||||
|
from = onedb.From
|
||||||
|
case "erae":
|
||||||
|
from = execdb.From
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown --era.format %q (expected 'era1' or 'erae')", format)
|
||||||
|
}
|
||||||
|
if err := utils.ImportHistory(chain, dir, network, from); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Import done in %v\n", time.Since(start))
|
fmt.Printf("Import done in %v\n", time.Since(start))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// exportHistory exports chain history in Era archives at a specified
|
// exportHistory exports chain history in Era archives at a specified directory.
|
||||||
// directory.
|
|
||||||
func exportHistory(ctx *cli.Context) error {
|
func exportHistory(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() != 3 {
|
if ctx.Args().Len() != 3 {
|
||||||
utils.Fatalf("usage: %s", ctx.Command.ArgsUsage)
|
utils.Fatalf("usage: %s", ctx.Command.ArgsUsage)
|
||||||
|
|
@ -548,10 +565,26 @@ func exportHistory(ctx *cli.Context) error {
|
||||||
if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() {
|
if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() {
|
||||||
utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64())
|
utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64())
|
||||||
}
|
}
|
||||||
err := utils.ExportHistory(chain, dir, uint64(first), uint64(last), uint64(era.MaxEra1Size))
|
|
||||||
if err != nil {
|
var (
|
||||||
|
format = ctx.String(utils.EraFormatFlag.Name)
|
||||||
|
filename func(network string, epoch int, root common.Hash) string
|
||||||
|
newBuilder func(w io.Writer) era.Builder
|
||||||
|
)
|
||||||
|
switch format {
|
||||||
|
case "era1", "era":
|
||||||
|
newBuilder = func(w io.Writer) era.Builder { return onedb.NewBuilder(w) }
|
||||||
|
filename = func(network string, epoch int, root common.Hash) string { return onedb.Filename(network, epoch, root) }
|
||||||
|
case "erae":
|
||||||
|
newBuilder = func(w io.Writer) era.Builder { return execdb.NewBuilder(w) }
|
||||||
|
filename = func(network string, epoch int, root common.Hash) string { return execdb.Filename(network, epoch, root) }
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown archive format %q (use 'era1' or 'erae')", format)
|
||||||
|
}
|
||||||
|
if err := utils.ExportHistory(chain, dir, uint64(first), uint64(last), newBuilder, filename); err != nil {
|
||||||
utils.Fatalf("Export error: %v\n", err)
|
utils.Fatalf("Export error: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Export done in %v\n", time.Since(start))
|
fmt.Printf("Export done in %v\n", time.Since(start))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
|
||||||
"github.com/ethereum/go-ethereum/internal/version"
|
"github.com/ethereum/go-ethereum/internal/version"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
@ -239,9 +240,15 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
cfg.Eth.OverrideVerkle = &v
|
cfg.Eth.OverrideVerkle = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start metrics export if enabled
|
// Start metrics export if enabled.
|
||||||
utils.SetupMetrics(&cfg.Metrics)
|
utils.SetupMetrics(&cfg.Metrics)
|
||||||
|
|
||||||
|
// Setup OpenTelemetry reporting if enabled.
|
||||||
|
if err := tracesetup.SetupTelemetry(cfg.Node.OpenTelemetry, stack); err != nil {
|
||||||
|
utils.Fatalf("failed to setup OpenTelemetry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Ethereum service.
|
||||||
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
|
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
|
||||||
|
|
||||||
// Create gauge with geth system and build information
|
// Create gauge with geth system and build information
|
||||||
|
|
@ -398,9 +405,9 @@ func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) {
|
||||||
ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name)
|
ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name)
|
||||||
|
|
||||||
if enableExport && v2FlagIsSet {
|
if enableExport && v2FlagIsSet {
|
||||||
utils.Fatalf("Flags --influxdb.metrics.organization, --influxdb.metrics.token, --influxdb.metrics.bucket are only available for influxdb-v2")
|
utils.Fatalf("Flags --%s, --%s, --%s are only available for influxdb-v2", utils.MetricsInfluxDBOrganizationFlag.Name, utils.MetricsInfluxDBTokenFlag.Name, utils.MetricsInfluxDBBucketFlag.Name)
|
||||||
} else if enableExportV2 && v1FlagIsSet {
|
} else if enableExportV2 && v1FlagIsSet {
|
||||||
utils.Fatalf("Flags --influxdb.metrics.username, --influxdb.metrics.password are only available for influxdb-v1")
|
utils.Fatalf("Flags --%s, --%s are only available for influxdb-v1", utils.MetricsInfluxDBUsernameFlag.Name, utils.MetricsInfluxDBPasswordFlag.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,8 @@ var (
|
||||||
utils.LogNoHistoryFlag,
|
utils.LogNoHistoryFlag,
|
||||||
utils.LogExportCheckpointsFlag,
|
utils.LogExportCheckpointsFlag,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
|
utils.TrienodeHistoryFlag,
|
||||||
|
utils.TrienodeHistoryFullValueCheckpointFlag,
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
utils.EthRequiredBlocksFlag,
|
utils.EthRequiredBlocksFlag,
|
||||||
utils.LegacyWhitelistFlag, // deprecated
|
utils.LegacyWhitelistFlag, // deprecated
|
||||||
|
|
@ -193,6 +195,14 @@ var (
|
||||||
utils.BatchResponseMaxSize,
|
utils.BatchResponseMaxSize,
|
||||||
utils.RPCTxSyncDefaultTimeoutFlag,
|
utils.RPCTxSyncDefaultTimeoutFlag,
|
||||||
utils.RPCTxSyncMaxTimeoutFlag,
|
utils.RPCTxSyncMaxTimeoutFlag,
|
||||||
|
utils.RPCGlobalRangeLimitFlag,
|
||||||
|
utils.RPCTelemetryFlag,
|
||||||
|
utils.RPCTelemetryEndpointFlag,
|
||||||
|
utils.RPCTelemetryUserFlag,
|
||||||
|
utils.RPCTelemetryPasswordFlag,
|
||||||
|
utils.RPCTelemetryInstanceIDFlag,
|
||||||
|
utils.RPCTelemetryTagsFlag,
|
||||||
|
utils.RPCTelemetrySampleRatioFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsFlags = []cli.Flag{
|
metricsFlags = []cli.Flag{
|
||||||
|
|
|
||||||
36
cmd/keeper/getpayload_wasm.go
Normal file
36
cmd/keeper/getpayload_wasm.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2026 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build wasm
|
||||||
|
// +build wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:wasmimport geth_io len
|
||||||
|
func hintLen() uint32
|
||||||
|
|
||||||
|
//go:wasmimport geth_io read
|
||||||
|
func hintRead(data unsafe.Pointer)
|
||||||
|
|
||||||
|
func getInput() []byte {
|
||||||
|
data := make([]byte, hintLen())
|
||||||
|
hintRead(unsafe.Pointer(&data[0]))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
@ -38,8 +38,8 @@ require (
|
||||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.44.0 // indirect
|
||||||
golang.org/x/sync v0.12.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ
|
||||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||||
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
|
@ -123,22 +123,22 @@ go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build !example && !ziren
|
//go:build !example && !ziren && !wasm
|
||||||
|
// +build !example,!ziren,!wasm
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
158
cmd/utils/cmd.go
158
cmd/utils/cmd.go
|
|
@ -57,6 +57,8 @@ const (
|
||||||
importBatchSize = 2500
|
importBatchSize = 2500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EraFileFormat int
|
||||||
|
|
||||||
// ErrImportInterrupted is returned when the user interrupts the import process.
|
// ErrImportInterrupted is returned when the user interrupts the import process.
|
||||||
var ErrImportInterrupted = errors.New("interrupted")
|
var ErrImportInterrupted = errors.New("interrupted")
|
||||||
|
|
||||||
|
|
@ -250,7 +252,7 @@ func readList(filename string) ([]string, error) {
|
||||||
// ImportHistory imports Era1 files containing historical block information,
|
// ImportHistory imports Era1 files containing historical block information,
|
||||||
// starting from genesis. The assumption is held that the provided chain
|
// starting from genesis. The assumption is held that the provided chain
|
||||||
// segment in Era1 file should all be canonical and verified.
|
// segment in Era1 file should all be canonical and verified.
|
||||||
func ImportHistory(chain *core.BlockChain, dir string, network string) error {
|
func ImportHistory(chain *core.BlockChain, dir string, network string, from func(f era.ReadAtSeekCloser) (era.Era, error)) error {
|
||||||
if chain.CurrentSnapBlock().Number.BitLen() != 0 {
|
if chain.CurrentSnapBlock().Number.BitLen() != 0 {
|
||||||
return errors.New("history import only supported when starting from genesis")
|
return errors.New("history import only supported when starting from genesis")
|
||||||
}
|
}
|
||||||
|
|
@ -263,42 +265,49 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
|
||||||
return fmt.Errorf("unable to read checksums.txt: %w", err)
|
return fmt.Errorf("unable to read checksums.txt: %w", err)
|
||||||
}
|
}
|
||||||
if len(checksums) != len(entries) {
|
if len(checksums) != len(entries) {
|
||||||
return fmt.Errorf("expected equal number of checksums and entries, have: %d checksums, %d entries", len(checksums), len(entries))
|
return fmt.Errorf("expected equal number of checksums and entries, have: %d checksums, %d entries",
|
||||||
|
len(checksums), len(entries))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
reported = time.Now()
|
reported = time.Now()
|
||||||
imported = 0
|
imported = 0
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
buf = bytes.NewBuffer(nil)
|
scratch = bytes.NewBuffer(nil)
|
||||||
)
|
)
|
||||||
for i, filename := range entries {
|
|
||||||
|
for i, file := range entries {
|
||||||
err := func() error {
|
err := func() error {
|
||||||
f, err := os.Open(filepath.Join(dir, filename))
|
path := filepath.Join(dir, file)
|
||||||
|
|
||||||
|
// validate against checksum file in directory
|
||||||
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to open era: %w", err)
|
return fmt.Errorf("open %s: %w", path, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Validate checksum.
|
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
return fmt.Errorf("unable to recalculate checksum: %w", err)
|
return fmt.Errorf("checksum %s: %w", path, err)
|
||||||
}
|
|
||||||
if have, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; have != want {
|
|
||||||
return fmt.Errorf("checksum mismatch: have %s, want %s", have, want)
|
|
||||||
}
|
}
|
||||||
|
got := common.BytesToHash(h.Sum(scratch.Bytes()[:])).Hex()
|
||||||
|
want := checksums[i]
|
||||||
h.Reset()
|
h.Reset()
|
||||||
buf.Reset()
|
scratch.Reset()
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want)
|
||||||
|
}
|
||||||
// Import all block data from Era1.
|
// Import all block data from Era1.
|
||||||
e, err := era.From(f)
|
e, err := from(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error opening era: %w", err)
|
return fmt.Errorf("error opening era: %w", err)
|
||||||
}
|
}
|
||||||
it, err := era.NewIterator(e)
|
it, err := e.Iterator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error making era reader: %w", err)
|
return fmt.Errorf("error creating iterator: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
block, err := it.Block()
|
block, err := it.Block()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -311,26 +320,28 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
||||||
}
|
}
|
||||||
encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
|
enc := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
|
||||||
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, math.MaxUint64); err != nil {
|
if _, err := chain.InsertReceiptChain([]*types.Block{block}, enc, math.MaxUint64); err != nil {
|
||||||
return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
|
return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
|
||||||
}
|
}
|
||||||
imported += 1
|
imported++
|
||||||
|
|
||||||
// Give the user some feedback that something is happening.
|
|
||||||
if time.Since(reported) >= 8*time.Second {
|
if time.Since(reported) >= 8*time.Second {
|
||||||
log.Info("Importing Era files", "head", it.Number(), "imported", imported, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Info("Importing Era files", "head", it.Number(), "imported", imported,
|
||||||
|
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
imported = 0
|
imported = 0
|
||||||
reported = time.Now()
|
reported = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,7 +400,6 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
|
|
||||||
var writer io.Writer = fh
|
var writer io.Writer = fh
|
||||||
if strings.HasSuffix(fn, ".gz") {
|
if strings.HasSuffix(fn, ".gz") {
|
||||||
writer = gzip.NewWriter(writer)
|
writer = gzip.NewWriter(writer)
|
||||||
|
|
@ -405,7 +415,7 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
|
||||||
|
|
||||||
// ExportHistory exports blockchain history into the specified directory,
|
// ExportHistory exports blockchain history into the specified directory,
|
||||||
// following the Era format.
|
// following the Era format.
|
||||||
func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) error {
|
func ExportHistory(bc *core.BlockChain, dir string, first, last uint64, newBuilder func(io.Writer) era.Builder, filename func(network string, epoch int, lastBlockHash common.Hash) string) error {
|
||||||
log.Info("Exporting blockchain history", "dir", dir)
|
log.Info("Exporting blockchain history", "dir", dir)
|
||||||
if head := bc.CurrentBlock().Number.Uint64(); head < last {
|
if head := bc.CurrentBlock().Number.Uint64(); head < last {
|
||||||
log.Warn("Last block beyond head, setting last = head", "head", head, "last", last)
|
log.Warn("Last block beyond head, setting last = head", "head", head, "last", last)
|
||||||
|
|
@ -418,76 +428,100 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) er
|
||||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
return fmt.Errorf("error creating output directory: %w", err)
|
return fmt.Errorf("error creating output directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
reported = time.Now()
|
reported = time.Now()
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
buf = bytes.NewBuffer(nil)
|
buf = bytes.NewBuffer(nil)
|
||||||
|
td = new(big.Int)
|
||||||
checksums []string
|
checksums []string
|
||||||
)
|
)
|
||||||
td := new(big.Int)
|
|
||||||
for i := uint64(0); i < first; i++ {
|
// Compute initial TD by accumulating difficulty from genesis to first-1.
|
||||||
td.Add(td, bc.GetHeaderByNumber(i).Difficulty)
|
// This is necessary because TD is no longer stored in the database. Only
|
||||||
|
// compute if a segment of the export is pre-merge.
|
||||||
|
b := bc.GetBlockByNumber(first)
|
||||||
|
if b == nil {
|
||||||
|
return fmt.Errorf("block #%d not found", first)
|
||||||
}
|
}
|
||||||
for i := first; i <= last; i += step {
|
if first > 0 && b.Difficulty().Sign() != 0 {
|
||||||
err := func() error {
|
log.Info("Computing initial total difficulty", "from", 0, "to", first-1)
|
||||||
filename := filepath.Join(dir, era.Filename(network, int(i/step), common.Hash{}))
|
for i := uint64(0); i < first; i++ {
|
||||||
f, err := os.Create(filename)
|
b := bc.GetBlockByNumber(i)
|
||||||
|
if b == nil {
|
||||||
|
return fmt.Errorf("block #%d not found while computing initial TD", i)
|
||||||
|
}
|
||||||
|
td.Add(td, b.Difficulty())
|
||||||
|
}
|
||||||
|
log.Info("Initial total difficulty computed", "td", td)
|
||||||
|
}
|
||||||
|
|
||||||
|
for batch := first; batch <= last; batch += uint64(era.MaxSize) {
|
||||||
|
idx := int(batch / uint64(era.MaxSize))
|
||||||
|
tmpPath := filepath.Join(dir, filename(network, idx, common.Hash{}))
|
||||||
|
|
||||||
|
if err := func() error {
|
||||||
|
f, err := os.Create(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create era file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
w := era.NewBuilder(f)
|
builder := newBuilder(f)
|
||||||
for j := uint64(0); j < step && j <= last-i; j++ {
|
|
||||||
var (
|
for j := uint64(0); j < uint64(era.MaxSize) && batch+j <= last; j++ {
|
||||||
n = i + j
|
n := batch + j
|
||||||
block = bc.GetBlockByNumber(n)
|
block := bc.GetBlockByNumber(n)
|
||||||
)
|
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return fmt.Errorf("export failed on #%d: not found", n)
|
return fmt.Errorf("block #%d not found", n)
|
||||||
}
|
}
|
||||||
receipts := bc.GetReceiptsByHash(block.Hash())
|
receipt := bc.GetReceiptsByHash(block.Hash())
|
||||||
if receipts == nil {
|
if receipt == nil {
|
||||||
return fmt.Errorf("export failed on #%d: receipts not found", n)
|
return fmt.Errorf("receipts for #%d missing", n)
|
||||||
}
|
}
|
||||||
td.Add(td, block.Difficulty())
|
|
||||||
if err := w.Add(block, receipts, new(big.Int).Set(td)); err != nil {
|
// For pre-merge blocks, pass accumulated TD.
|
||||||
|
// For post-merge blocks (difficulty == 0), pass nil TD.
|
||||||
|
var blockTD *big.Int
|
||||||
|
if block.Difficulty().Sign() != 0 {
|
||||||
|
td.Add(td, block.Difficulty())
|
||||||
|
blockTD = new(big.Int).Set(td)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := builder.Add(block, receipt, blockTD); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root, err := w.Finalize()
|
id, err := builder.Finalize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("export failed to finalize %d: %w", step/i, err)
|
return err
|
||||||
}
|
}
|
||||||
// Set correct filename with root.
|
|
||||||
os.Rename(filename, filepath.Join(dir, era.Filename(network, int(i/step), root)))
|
|
||||||
|
|
||||||
// Compute checksum of entire Era1.
|
|
||||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
|
||||||
return fmt.Errorf("unable to calculate checksum: %w", err)
|
|
||||||
}
|
|
||||||
checksums = append(checksums, common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex())
|
|
||||||
h.Reset()
|
h.Reset()
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
return nil
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
}()
|
return err
|
||||||
if err != nil {
|
}
|
||||||
|
checksums = append(checksums, common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex())
|
||||||
|
|
||||||
|
// Close before rename. It's required on Windows.
|
||||||
|
f.Close()
|
||||||
|
final := filepath.Join(dir, filename(network, idx, id))
|
||||||
|
return os.Rename(tmpPath, final)
|
||||||
|
}(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Since(reported) >= 8*time.Second {
|
if time.Since(reported) >= 8*time.Second {
|
||||||
log.Info("Exporting blocks", "exported", i, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Info("export progress", "exported", batch, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
reported = time.Now()
|
reported = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm)
|
_ = os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm)
|
||||||
|
|
||||||
log.Info("Exported blockchain to", "dir", dir)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,18 @@ var (
|
||||||
Value: ethconfig.Defaults.StateHistory,
|
Value: ethconfig.Defaults.StateHistory,
|
||||||
Category: flags.StateCategory,
|
Category: flags.StateCategory,
|
||||||
}
|
}
|
||||||
|
TrienodeHistoryFlag = &cli.Int64Flag{
|
||||||
|
Name: "history.trienode",
|
||||||
|
Usage: "Number of recent blocks to retain trienode history for, only relevant in state.scheme=path (default/negative = disabled, 0 = entire chain)",
|
||||||
|
Value: ethconfig.Defaults.TrienodeHistory,
|
||||||
|
Category: flags.StateCategory,
|
||||||
|
}
|
||||||
|
TrienodeHistoryFullValueCheckpointFlag = &cli.UintFlag{
|
||||||
|
Name: "history.trienode.full-value-checkpoint",
|
||||||
|
Usage: "The frequency of full-value encoding. Every n-th node is stored in full-value format; all other nodes are stored as diffs relative to their predecessor",
|
||||||
|
Value: uint(ethconfig.Defaults.NodeFullValueCheckpoint),
|
||||||
|
Category: flags.StateCategory,
|
||||||
|
}
|
||||||
TransactionHistoryFlag = &cli.Uint64Flag{
|
TransactionHistoryFlag = &cli.Uint64Flag{
|
||||||
Name: "history.transactions",
|
Name: "history.transactions",
|
||||||
Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)",
|
Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)",
|
||||||
|
|
@ -636,6 +648,12 @@ var (
|
||||||
Value: ethconfig.Defaults.TxSyncMaxTimeout,
|
Value: ethconfig.Defaults.TxSyncMaxTimeout,
|
||||||
Category: flags.APICategory,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
|
RPCGlobalRangeLimitFlag = &cli.Uint64Flag{
|
||||||
|
Name: "rpc.rangelimit",
|
||||||
|
Usage: "Maximum block range (end - begin) allowed for range queries (0 = unlimited)",
|
||||||
|
Value: ethconfig.Defaults.RangeLimit,
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
// Authenticated RPC HTTP settings
|
// Authenticated RPC HTTP settings
|
||||||
AuthListenFlag = &cli.StringFlag{
|
AuthListenFlag = &cli.StringFlag{
|
||||||
Name: "authrpc.addr",
|
Name: "authrpc.addr",
|
||||||
|
|
@ -674,7 +692,7 @@ var (
|
||||||
}
|
}
|
||||||
LogSlowBlockFlag = &cli.DurationFlag{
|
LogSlowBlockFlag = &cli.DurationFlag{
|
||||||
Name: "debug.logslowblock",
|
Name: "debug.logslowblock",
|
||||||
Usage: "Block execution time threshold beyond which detailed statistics will be logged (0 means disable)",
|
Usage: "Block execution time threshold beyond which detailed statistics will be logged (0 logs all blocks, negative means disable)",
|
||||||
Value: ethconfig.Defaults.SlowBlockThreshold,
|
Value: ethconfig.Defaults.SlowBlockThreshold,
|
||||||
Category: flags.LoggingCategory,
|
Category: flags.LoggingCategory,
|
||||||
}
|
}
|
||||||
|
|
@ -1024,6 +1042,55 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
||||||
Value: metrics.DefaultConfig.InfluxDBOrganization,
|
Value: metrics.DefaultConfig.InfluxDBOrganization,
|
||||||
Category: flags.MetricsCategory,
|
Category: flags.MetricsCategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RPC Telemetry
|
||||||
|
RPCTelemetryFlag = &cli.BoolFlag{
|
||||||
|
Name: "rpc.telemetry",
|
||||||
|
Usage: "Enable RPC telemetry",
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTelemetryEndpointFlag = &cli.StringFlag{
|
||||||
|
Name: "rpc.telemetry.endpoint",
|
||||||
|
Usage: "Defines where RPC telemetry is sent (e.g., http://localhost:4318)",
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTelemetryUserFlag = &cli.StringFlag{
|
||||||
|
Name: "rpc.telemetry.username",
|
||||||
|
Usage: "HTTP Basic Auth username for OpenTelemetry",
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTelemetryPasswordFlag = &cli.StringFlag{
|
||||||
|
Name: "rpc.telemetry.password",
|
||||||
|
Usage: "HTTP Basic Auth password for OpenTelemetry",
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTelemetryInstanceIDFlag = &cli.StringFlag{
|
||||||
|
Name: "rpc.telemetry.instance-id",
|
||||||
|
Usage: "OpenTelemetry instance ID",
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTelemetryTagsFlag = &cli.StringFlag{
|
||||||
|
Name: "rpc.telemetry.tags",
|
||||||
|
Usage: "Comma-separated tags (key/values) added as attributes to the OpenTelemetry resource struct",
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
|
||||||
|
Name: "rpc.telemetry.sample-ratio",
|
||||||
|
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
|
||||||
|
Value: 1.0,
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
// Era flags are a group of flags related to the era archive format.
|
||||||
|
EraFormatFlag = &cli.StringFlag{
|
||||||
|
Name: "era.format",
|
||||||
|
Usage: "Archive format: 'era1' or 'erae'",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -1414,6 +1481,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||||
setNodeUserIdent(ctx, cfg)
|
setNodeUserIdent(ctx, cfg)
|
||||||
SetDataDir(ctx, cfg)
|
SetDataDir(ctx, cfg)
|
||||||
setSmartCard(ctx, cfg)
|
setSmartCard(ctx, cfg)
|
||||||
|
setOpenTelemetry(ctx, cfg)
|
||||||
|
|
||||||
if ctx.IsSet(JWTSecretFlag.Name) {
|
if ctx.IsSet(JWTSecretFlag.Name) {
|
||||||
cfg.JWTSecret = ctx.String(JWTSecretFlag.Name)
|
cfg.JWTSecret = ctx.String(JWTSecretFlag.Name)
|
||||||
|
|
@ -1481,6 +1549,33 @@ func setSmartCard(ctx *cli.Context, cfg *node.Config) {
|
||||||
cfg.SmartCardDaemonPath = path
|
cfg.SmartCardDaemonPath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setOpenTelemetry(ctx *cli.Context, cfg *node.Config) {
|
||||||
|
tcfg := &cfg.OpenTelemetry
|
||||||
|
if ctx.IsSet(RPCTelemetryFlag.Name) {
|
||||||
|
tcfg.Enabled = ctx.Bool(RPCTelemetryFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(RPCTelemetryEndpointFlag.Name) {
|
||||||
|
tcfg.Endpoint = ctx.String(RPCTelemetryEndpointFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(RPCTelemetryUserFlag.Name) {
|
||||||
|
tcfg.AuthUser = ctx.String(RPCTelemetryUserFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(RPCTelemetryPasswordFlag.Name) {
|
||||||
|
tcfg.AuthPassword = ctx.String(RPCTelemetryPasswordFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(RPCTelemetryInstanceIDFlag.Name) {
|
||||||
|
tcfg.InstanceID = ctx.String(RPCTelemetryInstanceIDFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(RPCTelemetryTagsFlag.Name) {
|
||||||
|
tcfg.Tags = ctx.String(RPCTelemetryTagsFlag.Name)
|
||||||
|
}
|
||||||
|
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
|
||||||
|
|
||||||
|
if tcfg.Endpoint != "" && !tcfg.Enabled {
|
||||||
|
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SetDataDir(ctx *cli.Context, cfg *node.Config) {
|
func SetDataDir(ctx *cli.Context, cfg *node.Config) {
|
||||||
switch {
|
switch {
|
||||||
case ctx.IsSet(DataDirFlag.Name):
|
case ctx.IsSet(DataDirFlag.Name):
|
||||||
|
|
@ -1699,6 +1794,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(StateHistoryFlag.Name) {
|
if ctx.IsSet(StateHistoryFlag.Name) {
|
||||||
cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name)
|
cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name)
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(TrienodeHistoryFlag.Name) {
|
||||||
|
cfg.TrienodeHistory = ctx.Int64(TrienodeHistoryFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
|
||||||
|
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name))
|
||||||
|
}
|
||||||
if ctx.IsSet(StateSchemeFlag.Name) {
|
if ctx.IsSet(StateSchemeFlag.Name) {
|
||||||
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
|
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
@ -1753,6 +1854,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
|
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
|
||||||
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
|
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(RPCGlobalRangeLimitFlag.Name) {
|
||||||
|
cfg.RangeLimit = ctx.Uint64(RPCGlobalRangeLimitFlag.Name)
|
||||||
|
}
|
||||||
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
|
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
|
||||||
// If snap-sync is requested, this flag is also required
|
// If snap-sync is requested, this flag is also required
|
||||||
if cfg.SyncMode == ethconfig.SnapSync {
|
if cfg.SyncMode == ethconfig.SnapSync {
|
||||||
|
|
@ -2097,6 +2201,7 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
|
||||||
filterSystem := filters.NewFilterSystem(backend, filters.Config{
|
filterSystem := filters.NewFilterSystem(backend, filters.Config{
|
||||||
LogCacheSize: ethcfg.FilterLogCacheSize,
|
LogCacheSize: ethcfg.FilterLogCacheSize,
|
||||||
LogQueryLimit: ethcfg.LogQueryLimit,
|
LogQueryLimit: ethcfg.LogQueryLimit,
|
||||||
|
RangeLimit: ethcfg.RangeLimit,
|
||||||
})
|
})
|
||||||
stack.RegisterAPIs([]rpc.API{{
|
stack.RegisterAPIs([]rpc.API{{
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
|
|
@ -2299,15 +2404,17 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
Fatalf("%v", err)
|
Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
options := &core.BlockChainConfig{
|
options := &core.BlockChainConfig{
|
||||||
TrieCleanLimit: ethconfig.Defaults.TrieCleanCache,
|
TrieCleanLimit: ethconfig.Defaults.TrieCleanCache,
|
||||||
NoPrefetch: ctx.Bool(CacheNoPrefetchFlag.Name),
|
NoPrefetch: ctx.Bool(CacheNoPrefetchFlag.Name),
|
||||||
TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache,
|
TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache,
|
||||||
ArchiveMode: ctx.String(GCModeFlag.Name) == "archive",
|
ArchiveMode: ctx.String(GCModeFlag.Name) == "archive",
|
||||||
TrieTimeLimit: ethconfig.Defaults.TrieTimeout,
|
TrieTimeLimit: ethconfig.Defaults.TrieTimeout,
|
||||||
SnapshotLimit: ethconfig.Defaults.SnapshotCache,
|
SnapshotLimit: ethconfig.Defaults.SnapshotCache,
|
||||||
Preimages: ctx.Bool(CachePreimagesFlag.Name),
|
Preimages: ctx.Bool(CachePreimagesFlag.Name),
|
||||||
StateScheme: scheme,
|
StateScheme: scheme,
|
||||||
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
|
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
|
||||||
|
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
|
||||||
|
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
|
||||||
|
|
||||||
// Disable transaction indexing/unindexing.
|
// Disable transaction indexing/unindexing.
|
||||||
TxLookupLimit: -1,
|
TxLookupLimit: -1,
|
||||||
|
|
@ -2321,8 +2428,12 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
// Enable state size tracking if enabled
|
// Enable state size tracking if enabled
|
||||||
StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name),
|
StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name),
|
||||||
|
|
||||||
// Configure the slow block statistic logger
|
// Configure the slow block statistic logger (disabled by default)
|
||||||
SlowBlockThreshold: ctx.Duration(LogSlowBlockFlag.Name),
|
SlowBlockThreshold: ethconfig.Defaults.SlowBlockThreshold,
|
||||||
|
}
|
||||||
|
// Only enable slow block logging if the flag was explicitly set
|
||||||
|
if ctx.IsSet(LogSlowBlockFlag.Name) {
|
||||||
|
options.SlowBlockThreshold = ctx.Duration(LogSlowBlockFlag.Name)
|
||||||
}
|
}
|
||||||
if options.ArchiveMode && !options.Preimages {
|
if options.ArchiveMode && !options.Preimages {
|
||||||
options.Preimages = true
|
options.Preimages = true
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/execdb"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/onedb"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
|
@ -44,136 +46,148 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHistoryImportAndExport(t *testing.T) {
|
func TestHistoryImportAndExport(t *testing.T) {
|
||||||
var (
|
for _, tt := range []struct {
|
||||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
name string
|
||||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
builder func(io.Writer) era.Builder
|
||||||
genesis = &core.Genesis{
|
filename func(network string, epoch int, root common.Hash) string
|
||||||
Config: params.TestChainConfig,
|
from func(f era.ReadAtSeekCloser) (era.Era, error)
|
||||||
Alloc: types.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}},
|
}{
|
||||||
}
|
{"era1", onedb.NewBuilder, onedb.Filename, onedb.From},
|
||||||
signer = types.LatestSigner(genesis.Config)
|
{"erae", execdb.NewBuilder, execdb.Filename, execdb.From},
|
||||||
)
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Generate chain.
|
|
||||||
db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), int(count), func(i int, g *core.BlockGen) {
|
|
||||||
if i == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{
|
|
||||||
ChainID: genesis.Config.ChainID,
|
|
||||||
Nonce: uint64(i - 1),
|
|
||||||
GasTipCap: common.Big0,
|
|
||||||
GasFeeCap: g.PrevBlock(0).BaseFee(),
|
|
||||||
Gas: 50000,
|
|
||||||
To: &common.Address{0xaa},
|
|
||||||
Value: big.NewInt(int64(i)),
|
|
||||||
Data: nil,
|
|
||||||
AccessList: nil,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating tx: %v", err)
|
|
||||||
}
|
|
||||||
g.AddTx(tx)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Initialize BlockChain.
|
|
||||||
chain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to initialize chain: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := chain.InsertChain(blocks); err != nil {
|
|
||||||
t.Fatalf("error inserting chain: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make temp directory for era files.
|
|
||||||
dir := t.TempDir()
|
|
||||||
|
|
||||||
// Export history to temp directory.
|
|
||||||
if err := ExportHistory(chain, dir, 0, count, step); err != nil {
|
|
||||||
t.Fatalf("error exporting history: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read checksums.
|
|
||||||
b, err := os.ReadFile(filepath.Join(dir, "checksums.txt"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read checksums: %v", err)
|
|
||||||
}
|
|
||||||
checksums := strings.Split(string(b), "\n")
|
|
||||||
|
|
||||||
// Verify each Era.
|
|
||||||
entries, _ := era.ReadDir(dir, "mainnet")
|
|
||||||
for i, filename := range entries {
|
|
||||||
func() {
|
|
||||||
f, err := os.Open(filepath.Join(dir, filename))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error opening era file: %v", err)
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
h = sha256.New()
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
buf = bytes.NewBuffer(nil)
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
genesis = &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}},
|
||||||
|
}
|
||||||
|
signer = types.LatestSigner(genesis.Config)
|
||||||
)
|
)
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
|
||||||
t.Fatalf("unable to recalculate checksum: %v", err)
|
// Generate chain.
|
||||||
}
|
db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), int(count), func(i int, g *core.BlockGen) {
|
||||||
if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want {
|
if i == 0 {
|
||||||
t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want)
|
return
|
||||||
}
|
|
||||||
e, err := era.From(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error opening era: %v", err)
|
|
||||||
}
|
|
||||||
defer e.Close()
|
|
||||||
it, err := era.NewIterator(e)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error making era reader: %v", err)
|
|
||||||
}
|
|
||||||
for j := 0; it.Next(); j++ {
|
|
||||||
n := i*int(step) + j
|
|
||||||
if it.Error() != nil {
|
|
||||||
t.Fatalf("error reading block entry %d: %v", n, it.Error())
|
|
||||||
}
|
}
|
||||||
block, receipts, err := it.BlockAndReceipts()
|
tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{
|
||||||
|
ChainID: genesis.Config.ChainID,
|
||||||
|
Nonce: uint64(i - 1),
|
||||||
|
GasTipCap: common.Big0,
|
||||||
|
GasFeeCap: g.PrevBlock(0).BaseFee(),
|
||||||
|
Gas: 50000,
|
||||||
|
To: &common.Address{0xaa},
|
||||||
|
Value: big.NewInt(int64(i)),
|
||||||
|
Data: nil,
|
||||||
|
AccessList: nil,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error reading block entry %d: %v", n, err)
|
t.Fatalf("error creating tx: %v", err)
|
||||||
}
|
|
||||||
want := chain.GetBlockByNumber(uint64(n))
|
|
||||||
if want, got := uint64(n), block.NumberU64(); want != got {
|
|
||||||
t.Fatalf("blocks out of order: want %d, got %d", want, got)
|
|
||||||
}
|
|
||||||
if want.Hash() != block.Hash() {
|
|
||||||
t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex())
|
|
||||||
}
|
|
||||||
if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() {
|
|
||||||
t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got)
|
|
||||||
}
|
|
||||||
if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() {
|
|
||||||
t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got)
|
|
||||||
}
|
|
||||||
if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() {
|
|
||||||
t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got)
|
|
||||||
}
|
}
|
||||||
|
g.AddTx(tx)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize BlockChain.
|
||||||
|
chain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to initialize chain: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := chain.InsertChain(blocks); err != nil {
|
||||||
|
t.Fatalf("error inserting chain: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now import Era.
|
// Make temp directory for era files.
|
||||||
db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
dir := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() {
|
|
||||||
db2.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
genesis.MustCommit(db2, triedb.NewDatabase(db2, triedb.HashDefaults))
|
// Export history to temp directory.
|
||||||
imported, err := core.NewBlockChain(db2, genesis, ethash.NewFaker(), nil)
|
if err := ExportHistory(chain, dir, 0, count, tt.builder, tt.filename); err != nil {
|
||||||
if err != nil {
|
t.Fatalf("error exporting history: %v", err)
|
||||||
t.Fatalf("unable to initialize chain: %v", err)
|
}
|
||||||
}
|
|
||||||
if err := ImportHistory(imported, dir, "mainnet"); err != nil {
|
// Read checksums.
|
||||||
t.Fatalf("failed to import chain: %v", err)
|
b, err := os.ReadFile(filepath.Join(dir, "checksums.txt"))
|
||||||
}
|
if err != nil {
|
||||||
if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() {
|
t.Fatalf("failed to read checksums: %v", err)
|
||||||
t.Fatalf("imported chain does not match expected, have (%d, %s) want (%d, %s)", have.Number, have.Hash(), want.Number, want.Hash())
|
}
|
||||||
|
checksums := strings.Split(string(b), "\n")
|
||||||
|
|
||||||
|
// Verify each Era.
|
||||||
|
entries, _ := era.ReadDir(dir, "mainnet")
|
||||||
|
for i, filename := range entries {
|
||||||
|
func() {
|
||||||
|
f, err := os.Open(filepath.Join(dir, filename))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error opening era file: %v", err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
h = sha256.New()
|
||||||
|
buf = bytes.NewBuffer(nil)
|
||||||
|
)
|
||||||
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
|
t.Fatalf("unable to recalculate checksum: %v", err)
|
||||||
|
}
|
||||||
|
if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want {
|
||||||
|
t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want)
|
||||||
|
}
|
||||||
|
e, err := tt.from(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error opening era: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
it, err := e.Iterator()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error making era reader: %v", err)
|
||||||
|
}
|
||||||
|
for j := 0; it.Next(); j++ {
|
||||||
|
n := i*int(step) + j
|
||||||
|
if it.Error() != nil {
|
||||||
|
t.Fatalf("error reading block entry %d: %v", n, it.Error())
|
||||||
|
}
|
||||||
|
block, receipts, err := it.BlockAndReceipts()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error reading block entry %d: %v", n, err)
|
||||||
|
}
|
||||||
|
want := chain.GetBlockByNumber(uint64(n))
|
||||||
|
if want, got := uint64(n), block.NumberU64(); want != got {
|
||||||
|
t.Fatalf("blocks out of order: want %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
if want.Hash() != block.Hash() {
|
||||||
|
t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex())
|
||||||
|
}
|
||||||
|
if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() {
|
||||||
|
t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got)
|
||||||
|
}
|
||||||
|
if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() {
|
||||||
|
t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got)
|
||||||
|
}
|
||||||
|
if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() {
|
||||||
|
t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now import Era.
|
||||||
|
db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
db2.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
genesis.MustCommit(db2, triedb.NewDatabase(db2, triedb.HashDefaults))
|
||||||
|
imported, err := core.NewBlockChain(db2, genesis, ethash.NewFaker(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to initialize chain: %v", err)
|
||||||
|
}
|
||||||
|
if err := ImportHistory(imported, dir, "mainnet", tt.from); err != nil {
|
||||||
|
t.Fatalf("failed to import chain: %v", err)
|
||||||
|
}
|
||||||
|
if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() {
|
||||||
|
t.Fatalf("imported chain does not match expected, have (%d, %s) want (%d, %s)", have.Number, have.Hash(), want.Number, want.Hash())
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"golang.org/x/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lengths of hashes and addresses in bytes.
|
// Lengths of hashes and addresses in bytes.
|
||||||
|
|
@ -271,7 +271,7 @@ func (a *Address) checksumHex() []byte {
|
||||||
buf := a.hex()
|
buf := a.hex()
|
||||||
|
|
||||||
// compute checksum
|
// compute checksum
|
||||||
sha := sha3.NewLegacyKeccak256()
|
sha := keccak.NewLegacyKeccak256()
|
||||||
sha.Write(buf[2:])
|
sha.Write(buf[2:])
|
||||||
hash := sha.Sum(nil)
|
hash := sha.Sum(nil)
|
||||||
for i := 2; i < len(buf); i++ {
|
for i := 2; i < len(buf); i++ {
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -642,7 +642,7 @@ func (c *Clique) Close() error {
|
||||||
|
|
||||||
// SealHash returns the hash of a block prior to it being sealed.
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
func SealHash(header *types.Header) (hash common.Hash) {
|
func SealHash(header *types.Header) (hash common.Hash) {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
encodeSigHeader(hasher, header)
|
encodeSigHeader(hasher, header)
|
||||||
hasher.(crypto.KeccakState).Read(hash[:])
|
hasher.(crypto.KeccakState).Read(hash[:])
|
||||||
return hash
|
return hash
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ethash proof-of-work protocol constants.
|
// Ethash proof-of-work protocol constants.
|
||||||
|
|
@ -527,7 +527,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
||||||
|
|
||||||
// SealHash returns the hash of a block prior to it being sealed.
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
|
|
||||||
enc := []interface{}{
|
enc := []interface{}{
|
||||||
header.ParentHash,
|
header.ParentHash,
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ func (bc *BlobConfig) blobPrice(excessBlobGas uint64) *big.Int {
|
||||||
return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob))
|
return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob))
|
||||||
}
|
}
|
||||||
|
|
||||||
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
|
func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error) {
|
||||||
if cfg.BlobScheduleConfig == nil {
|
if cfg.BlobScheduleConfig == nil {
|
||||||
return nil
|
return BlobConfig{}, errors.New("no blob config")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
london = cfg.LondonBlock
|
london = cfg.LondonBlock
|
||||||
|
|
@ -80,14 +80,14 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
|
||||||
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
||||||
bc = s.Cancun
|
bc = s.Cancun
|
||||||
default:
|
default:
|
||||||
return nil
|
return BlobConfig{}, errors.New("no blob config")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BlobConfig{
|
return BlobConfig{
|
||||||
Target: bc.Target,
|
Target: bc.Target,
|
||||||
Max: bc.Max,
|
Max: bc.Max,
|
||||||
UpdateFraction: bc.UpdateFraction,
|
UpdateFraction: bc.UpdateFraction,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
||||||
|
|
@ -98,8 +98,8 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
|
||||||
panic("bad header pair")
|
panic("bad header pair")
|
||||||
}
|
}
|
||||||
|
|
||||||
bcfg := latestBlobConfig(config, header.Time)
|
bcfg, err := latestBlobConfig(config, header.Time)
|
||||||
if bcfg == nil {
|
if err != nil {
|
||||||
panic("called before EIP-4844 is active")
|
panic("called before EIP-4844 is active")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,11 +130,14 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
|
||||||
// blobs on top of the excess blob gas.
|
// blobs on top of the excess blob gas.
|
||||||
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
|
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
|
||||||
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
|
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
|
||||||
bcfg := latestBlobConfig(config, headTimestamp)
|
bcfg, err := latestBlobConfig(config, headTimestamp)
|
||||||
|
if err != nil {
|
||||||
|
panic("calculating excess blob gas on nil blob config")
|
||||||
|
}
|
||||||
return calcExcessBlobGas(isOsaka, bcfg, parent)
|
return calcExcessBlobGas(isOsaka, bcfg, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uint64 {
|
func calcExcessBlobGas(isOsaka bool, bcfg BlobConfig, parent *types.Header) uint64 {
|
||||||
var parentExcessBlobGas, parentBlobGasUsed uint64
|
var parentExcessBlobGas, parentBlobGasUsed uint64
|
||||||
if parent.ExcessBlobGas != nil {
|
if parent.ExcessBlobGas != nil {
|
||||||
parentExcessBlobGas = *parent.ExcessBlobGas
|
parentExcessBlobGas = *parent.ExcessBlobGas
|
||||||
|
|
@ -169,8 +172,8 @@ func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uin
|
||||||
|
|
||||||
// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
|
// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
|
||||||
func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
|
func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
|
||||||
blobConfig := latestBlobConfig(config, header.Time)
|
blobConfig, err := latestBlobConfig(config, header.Time)
|
||||||
if blobConfig == nil {
|
if err != nil {
|
||||||
panic("calculating blob fee on unsupported fork")
|
panic("calculating blob fee on unsupported fork")
|
||||||
}
|
}
|
||||||
return blobConfig.blobBaseFee(*header.ExcessBlobGas)
|
return blobConfig.blobBaseFee(*header.ExcessBlobGas)
|
||||||
|
|
@ -178,8 +181,8 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
|
||||||
|
|
||||||
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
|
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
|
||||||
func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
||||||
blobConfig := latestBlobConfig(cfg, time)
|
blobConfig, err := latestBlobConfig(cfg, time)
|
||||||
if blobConfig == nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return blobConfig.Max
|
return blobConfig.Max
|
||||||
|
|
@ -193,8 +196,8 @@ func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
|
||||||
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
|
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
|
||||||
// configuration, regardless of the currently active fork.
|
// configuration, regardless of the currently active fork.
|
||||||
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
||||||
bcfg := latestBlobConfig(cfg, math.MaxUint64)
|
bcfg, err := latestBlobConfig(cfg, math.MaxUint64)
|
||||||
if bcfg == nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return bcfg.Max
|
return bcfg.Max
|
||||||
|
|
@ -202,8 +205,8 @@ func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
||||||
|
|
||||||
// TargetBlobsPerBlock returns the target blobs per block for a block at the given timestamp.
|
// TargetBlobsPerBlock returns the target blobs per block for a block at the given timestamp.
|
||||||
func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
||||||
blobConfig := latestBlobConfig(cfg, time)
|
blobConfig, err := latestBlobConfig(cfg, time)
|
||||||
if blobConfig == nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return blobConfig.Target
|
return blobConfig.Target
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ var (
|
||||||
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
||||||
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
|
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
|
||||||
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
|
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
|
||||||
|
codeReadBytesTimer = metrics.NewRegisteredResettingTimer("chain/code/readbytes", nil)
|
||||||
|
|
||||||
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
|
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
|
||||||
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
|
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
|
||||||
|
|
@ -179,6 +180,17 @@ type BlockChainConfig struct {
|
||||||
// If set to 0, all state histories across the entire chain will be retained;
|
// If set to 0, all state histories across the entire chain will be retained;
|
||||||
StateHistory uint64
|
StateHistory uint64
|
||||||
|
|
||||||
|
// Number of blocks from the chain head for which trienode histories are retained.
|
||||||
|
// If set to 0, all trienode histories across the entire chain will be retained;
|
||||||
|
// If set to -1, no trienode history will be retained;
|
||||||
|
TrienodeHistory int64
|
||||||
|
|
||||||
|
// The frequency of full-value encoding. For example, a value of 16 means
|
||||||
|
// that, on average, for a given trie node across its 16 consecutive historical
|
||||||
|
// versions, only one version is stored in full format, while the others
|
||||||
|
// are stored in diff mode for storage compression.
|
||||||
|
NodeFullValueCheckpoint uint32
|
||||||
|
|
||||||
// State snapshot related options
|
// State snapshot related options
|
||||||
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
|
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
|
||||||
SnapshotNoBuild bool // Whether the background generation is allowed
|
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||||
|
|
@ -204,7 +216,8 @@ type BlockChainConfig struct {
|
||||||
StateSizeTracking bool
|
StateSizeTracking bool
|
||||||
|
|
||||||
// SlowBlockThreshold is the block execution time threshold beyond which
|
// SlowBlockThreshold is the block execution time threshold beyond which
|
||||||
// detailed statistics will be logged.
|
// detailed statistics will be logged. Negative value means disabled (default),
|
||||||
|
// zero logs all blocks, positive value filters blocks by execution time.
|
||||||
SlowBlockThreshold time.Duration
|
SlowBlockThreshold time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,17 +269,22 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
|
||||||
}
|
}
|
||||||
if cfg.StateScheme == rawdb.PathScheme {
|
if cfg.StateScheme == rawdb.PathScheme {
|
||||||
config.PathDB = &pathdb.Config{
|
config.PathDB = &pathdb.Config{
|
||||||
StateHistory: cfg.StateHistory,
|
TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
|
||||||
EnableStateIndexing: cfg.ArchiveMode,
|
StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
|
||||||
TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
|
|
||||||
StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
|
|
||||||
JournalDirectory: cfg.TrieJournalDirectory,
|
|
||||||
|
|
||||||
// TODO(rjl493456442): The write buffer represents the memory limit used
|
// TODO(rjl493456442): The write buffer represents the memory limit used
|
||||||
// for flushing both trie data and state data to disk. The config name
|
// for flushing both trie data and state data to disk. The config name
|
||||||
// should be updated to eliminate the confusion.
|
// should be updated to eliminate the confusion.
|
||||||
WriteBufferSize: cfg.TrieDirtyLimit * 1024 * 1024,
|
WriteBufferSize: cfg.TrieDirtyLimit * 1024 * 1024,
|
||||||
NoAsyncFlush: cfg.TrieNoAsyncFlush,
|
JournalDirectory: cfg.TrieJournalDirectory,
|
||||||
|
|
||||||
|
// Historical state configurations
|
||||||
|
StateHistory: cfg.StateHistory,
|
||||||
|
TrienodeHistory: cfg.TrienodeHistory,
|
||||||
|
EnableStateIndexing: cfg.ArchiveMode,
|
||||||
|
FullValueCheckpoint: cfg.NodeFullValueCheckpoint,
|
||||||
|
|
||||||
|
// Testing configurations
|
||||||
|
NoAsyncFlush: cfg.TrieNoAsyncFlush,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
|
|
@ -2091,24 +2109,11 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
}
|
}
|
||||||
// Upload the statistics of reader at the end
|
// Upload the statistics of reader at the end
|
||||||
defer func() {
|
defer func() {
|
||||||
pStat := prefetch.GetStats()
|
|
||||||
accountCacheHitPrefetchMeter.Mark(pStat.AccountCacheHit)
|
|
||||||
accountCacheMissPrefetchMeter.Mark(pStat.AccountCacheMiss)
|
|
||||||
storageCacheHitPrefetchMeter.Mark(pStat.StorageCacheHit)
|
|
||||||
storageCacheMissPrefetchMeter.Mark(pStat.StorageCacheMiss)
|
|
||||||
|
|
||||||
rStat := process.GetStats()
|
|
||||||
accountCacheHitMeter.Mark(rStat.AccountCacheHit)
|
|
||||||
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
|
|
||||||
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
|
|
||||||
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
|
|
||||||
|
|
||||||
if result != nil {
|
if result != nil {
|
||||||
result.stats.StatePrefetchCacheStats = pStat
|
result.stats.StatePrefetchCacheStats = prefetch.GetStats()
|
||||||
result.stats.StateReadCacheStats = rStat
|
result.stats.StateReadCacheStats = process.GetStats()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
|
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
|
||||||
// Disable tracing for prefetcher executions.
|
// Disable tracing for prefetcher executions.
|
||||||
vmCfg := bc.cfg.VmConfig
|
vmCfg := bc.cfg.VmConfig
|
||||||
|
|
@ -2228,7 +2233,11 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
stats.StorageLoaded = statedb.StorageLoaded
|
stats.StorageLoaded = statedb.StorageLoaded
|
||||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||||
|
|
||||||
stats.CodeLoaded = statedb.CodeLoaded
|
stats.CodeLoaded = statedb.CodeLoaded
|
||||||
|
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
||||||
|
stats.CodeUpdated = statedb.CodeUpdated
|
||||||
|
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
||||||
|
|
||||||
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
||||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -39,13 +38,16 @@ type ExecuteStats struct {
|
||||||
StorageCommits time.Duration // Time spent on the storage trie commit
|
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||||
CodeReads time.Duration // Time spent on the contract code read
|
CodeReads time.Duration // Time spent on the contract code read
|
||||||
|
|
||||||
AccountLoaded int // Number of accounts loaded
|
AccountLoaded int // Number of accounts loaded
|
||||||
AccountUpdated int // Number of accounts updated
|
AccountUpdated int // Number of accounts updated
|
||||||
AccountDeleted int // Number of accounts deleted
|
AccountDeleted int // Number of accounts deleted
|
||||||
StorageLoaded int // Number of storage slots loaded
|
StorageLoaded int // Number of storage slots loaded
|
||||||
StorageUpdated int // Number of storage slots updated
|
StorageUpdated int // Number of storage slots updated
|
||||||
StorageDeleted int // Number of storage slots deleted
|
StorageDeleted int // Number of storage slots deleted
|
||||||
CodeLoaded int // Number of contract code loaded
|
CodeLoaded int // Number of contract code loaded
|
||||||
|
CodeLoadBytes int // Number of bytes read from contract code
|
||||||
|
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||||
|
CodeUpdateBytes int // Total bytes of code written
|
||||||
|
|
||||||
Execution time.Duration // Time spent on the EVM execution
|
Execution time.Duration // Time spent on the EVM execution
|
||||||
Validation time.Duration // Time spent on the block validation
|
Validation time.Duration // Time spent on the block validation
|
||||||
|
|
@ -74,6 +76,7 @@ func (s *ExecuteStats) reportMetrics() {
|
||||||
if s.CodeLoaded != 0 {
|
if s.CodeLoaded != 0 {
|
||||||
codeReadTimer.Update(s.CodeReads)
|
codeReadTimer.Update(s.CodeReads)
|
||||||
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
|
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
|
||||||
|
codeReadBytesTimer.Update(time.Duration(s.CodeLoadBytes))
|
||||||
}
|
}
|
||||||
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
||||||
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
|
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
|
||||||
|
|
@ -102,64 +105,161 @@ func (s *ExecuteStats) reportMetrics() {
|
||||||
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
|
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logSlow prints the detailed execution statistics if the block is regarded as slow.
|
// slowBlockLog represents the JSON structure for slow block logging.
|
||||||
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
|
// This format is designed for cross-client compatibility with other
|
||||||
if slowBlockThreshold == 0 {
|
// Ethereum execution clients (reth, Besu, Nethermind).
|
||||||
return
|
type slowBlockLog struct {
|
||||||
}
|
Level string `json:"level"`
|
||||||
if s.TotalTime < slowBlockThreshold {
|
Msg string `json:"msg"`
|
||||||
return
|
Block slowBlockInfo `json:"block"`
|
||||||
}
|
Timing slowBlockTime `json:"timing"`
|
||||||
msg := fmt.Sprintf(`
|
Throughput slowBlockThru `json:"throughput"`
|
||||||
########## SLOW BLOCK #########
|
StateReads slowBlockReads `json:"state_reads"`
|
||||||
Block: %v (%#x) txs: %d, mgasps: %.2f, elapsed: %v
|
StateWrites slowBlockWrites `json:"state_writes"`
|
||||||
|
Cache slowBlockCache `json:"cache"`
|
||||||
EVM execution: %v
|
}
|
||||||
|
|
||||||
Validation: %v
|
type slowBlockInfo struct {
|
||||||
Account hash: %v
|
Number uint64 `json:"number"`
|
||||||
Storage hash: %v
|
Hash common.Hash `json:"hash"`
|
||||||
|
GasUsed uint64 `json:"gas_used"`
|
||||||
State read: %v
|
TxCount int `json:"tx_count"`
|
||||||
Account read: %v(%d)
|
}
|
||||||
Storage read: %v(%d)
|
|
||||||
Code read: %v(%d)
|
type slowBlockTime struct {
|
||||||
|
ExecutionMs float64 `json:"execution_ms"`
|
||||||
State write: %v
|
StateReadMs float64 `json:"state_read_ms"`
|
||||||
Trie commit: %v
|
StateHashMs float64 `json:"state_hash_ms"`
|
||||||
State write: %v
|
CommitMs float64 `json:"commit_ms"`
|
||||||
Block write: %v
|
TotalMs float64 `json:"total_ms"`
|
||||||
|
}
|
||||||
%s
|
|
||||||
##############################
|
type slowBlockThru struct {
|
||||||
`, block.Number(), block.Hash(), len(block.Transactions()), s.MgasPerSecond, common.PrettyDuration(s.TotalTime),
|
MgasPerSec float64 `json:"mgas_per_sec"`
|
||||||
// EVM execution
|
}
|
||||||
common.PrettyDuration(s.Execution),
|
|
||||||
|
type slowBlockReads struct {
|
||||||
// Block validation
|
Accounts int `json:"accounts"`
|
||||||
common.PrettyDuration(s.Validation+s.CrossValidation+s.AccountHashes+s.AccountUpdates+s.StorageUpdates),
|
StorageSlots int `json:"storage_slots"`
|
||||||
common.PrettyDuration(s.AccountHashes+s.AccountUpdates),
|
Code int `json:"code"`
|
||||||
common.PrettyDuration(s.StorageUpdates),
|
CodeBytes int `json:"code_bytes"`
|
||||||
|
}
|
||||||
// State read
|
|
||||||
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
|
type slowBlockWrites struct {
|
||||||
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
|
Accounts int `json:"accounts"`
|
||||||
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
|
AccountsDeleted int `json:"accounts_deleted"`
|
||||||
common.PrettyDuration(s.CodeReads), s.CodeLoaded,
|
StorageSlots int `json:"storage_slots"`
|
||||||
|
StorageSlotsDeleted int `json:"storage_slots_deleted"`
|
||||||
// State write
|
Code int `json:"code"`
|
||||||
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)+s.TrieDBCommit+s.SnapshotCommit+s.BlockWrite),
|
CodeBytes int `json:"code_bytes"`
|
||||||
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)),
|
}
|
||||||
common.PrettyDuration(s.TrieDBCommit+s.SnapshotCommit),
|
|
||||||
common.PrettyDuration(s.BlockWrite),
|
// slowBlockCache represents cache hit/miss statistics for cross-client analysis.
|
||||||
|
type slowBlockCache struct {
|
||||||
// cache statistics
|
Account slowBlockCacheEntry `json:"account"`
|
||||||
s.StateReadCacheStats,
|
Storage slowBlockCacheEntry `json:"storage"`
|
||||||
)
|
Code slowBlockCodeCacheEntry `json:"code"`
|
||||||
for _, line := range strings.Split(msg, "\n") {
|
}
|
||||||
if line == "" {
|
|
||||||
continue
|
// slowBlockCacheEntry represents cache statistics for account/storage caches.
|
||||||
}
|
type slowBlockCacheEntry struct {
|
||||||
log.Info(line)
|
Hits int64 `json:"hits"`
|
||||||
}
|
Misses int64 `json:"misses"`
|
||||||
|
HitRate float64 `json:"hit_rate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// slowBlockCodeCacheEntry represents cache statistics for code cache with byte-level granularity.
|
||||||
|
type slowBlockCodeCacheEntry struct {
|
||||||
|
Hits int64 `json:"hits"`
|
||||||
|
Misses int64 `json:"misses"`
|
||||||
|
HitRate float64 `json:"hit_rate"`
|
||||||
|
HitBytes int64 `json:"hit_bytes"`
|
||||||
|
MissBytes int64 `json:"miss_bytes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateHitRate computes the cache hit rate as a percentage (0-100).
|
||||||
|
func calculateHitRate(hits, misses int64) float64 {
|
||||||
|
if total := hits + misses; total > 0 {
|
||||||
|
return float64(hits) / float64(total) * 100.0
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationToMs converts a time.Duration to milliseconds as a float64
|
||||||
|
// with sub-millisecond precision for accurate cross-client metrics.
|
||||||
|
func durationToMs(d time.Duration) float64 {
|
||||||
|
return float64(d.Nanoseconds()) / 1e6
|
||||||
|
}
|
||||||
|
|
||||||
|
// logSlow prints the detailed execution statistics in JSON format if the block
|
||||||
|
// is regarded as slow. The JSON format is designed for cross-client compatibility
|
||||||
|
// with other Ethereum execution clients.
|
||||||
|
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
|
||||||
|
// Negative threshold means disabled (default when flag not set)
|
||||||
|
if slowBlockThreshold < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Threshold of 0 logs all blocks; positive threshold filters
|
||||||
|
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logEntry := slowBlockLog{
|
||||||
|
Level: "warn",
|
||||||
|
Msg: "Slow block",
|
||||||
|
Block: slowBlockInfo{
|
||||||
|
Number: block.NumberU64(),
|
||||||
|
Hash: block.Hash(),
|
||||||
|
GasUsed: block.GasUsed(),
|
||||||
|
TxCount: len(block.Transactions()),
|
||||||
|
},
|
||||||
|
Timing: slowBlockTime{
|
||||||
|
ExecutionMs: durationToMs(s.Execution),
|
||||||
|
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
|
||||||
|
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
|
||||||
|
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.TrieDBCommit + s.SnapshotCommit + s.BlockWrite),
|
||||||
|
TotalMs: durationToMs(s.TotalTime),
|
||||||
|
},
|
||||||
|
Throughput: slowBlockThru{
|
||||||
|
MgasPerSec: s.MgasPerSecond,
|
||||||
|
},
|
||||||
|
StateReads: slowBlockReads{
|
||||||
|
Accounts: s.AccountLoaded,
|
||||||
|
StorageSlots: s.StorageLoaded,
|
||||||
|
Code: s.CodeLoaded,
|
||||||
|
CodeBytes: s.CodeLoadBytes,
|
||||||
|
},
|
||||||
|
StateWrites: slowBlockWrites{
|
||||||
|
Accounts: s.AccountUpdated,
|
||||||
|
AccountsDeleted: s.AccountDeleted,
|
||||||
|
StorageSlots: s.StorageUpdated,
|
||||||
|
StorageSlotsDeleted: s.StorageDeleted,
|
||||||
|
Code: s.CodeUpdated,
|
||||||
|
CodeBytes: s.CodeUpdateBytes,
|
||||||
|
},
|
||||||
|
Cache: slowBlockCache{
|
||||||
|
Account: slowBlockCacheEntry{
|
||||||
|
Hits: s.StateReadCacheStats.AccountCacheHit,
|
||||||
|
Misses: s.StateReadCacheStats.AccountCacheMiss,
|
||||||
|
HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss),
|
||||||
|
},
|
||||||
|
Storage: slowBlockCacheEntry{
|
||||||
|
Hits: s.StateReadCacheStats.StorageCacheHit,
|
||||||
|
Misses: s.StateReadCacheStats.StorageCacheMiss,
|
||||||
|
HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss),
|
||||||
|
},
|
||||||
|
Code: slowBlockCodeCacheEntry{
|
||||||
|
Hits: s.StateReadCacheStats.CodeStats.CacheHit,
|
||||||
|
Misses: s.StateReadCacheStats.CodeStats.CacheMiss,
|
||||||
|
HitRate: calculateHitRate(s.StateReadCacheStats.CodeStats.CacheHit, s.StateReadCacheStats.CodeStats.CacheMiss),
|
||||||
|
HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes,
|
||||||
|
MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jsonBytes, err := json.Marshal(logEntry)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to marshal slow block log", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Warn(string(jsonBytes))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||||
func NewEVMTxContext(msg *Message) vm.TxContext {
|
func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||||
ctx := vm.TxContext{
|
ctx := vm.TxContext{
|
||||||
Origin: msg.From,
|
Origin: msg.From,
|
||||||
GasPrice: new(big.Int).Set(msg.GasPrice),
|
GasPrice: uint256.MustFromBig(msg.GasPrice),
|
||||||
BlobHashes: msg.BlobHashes,
|
BlobHashes: msg.BlobHashes,
|
||||||
}
|
}
|
||||||
if msg.BlobGasFeeCap != nil {
|
|
||||||
ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap)
|
|
||||||
}
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -308,7 +308,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := common.FromHex("19056b480530799a4fdaa9fd9407043b965a3a5c37b4d2a1a9a4f3395a327561")
|
expected := common.FromHex("b94812c1674dcf4f2bc98f4503d15f4cc674265135bcf3be6e4417b60881042a")
|
||||||
got := genesis.ToBlock().Root().Bytes()
|
got := genesis.ToBlock().Root().Bytes()
|
||||||
if !bytes.Equal(got, expected) {
|
if !bytes.Equal(got, expected) {
|
||||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
||||||
|
|
|
||||||
|
|
@ -424,7 +424,13 @@ func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp
|
||||||
// HasBody verifies the existence of a block body corresponding to the hash.
|
// HasBody verifies the existence of a block body corresponding to the hash.
|
||||||
func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
||||||
if isCanon(db, number, hash) {
|
if isCanon(db, number, hash) {
|
||||||
return true
|
// Block is in ancient store, but bodies can be pruned.
|
||||||
|
// Check if the block number is above the pruning tail.
|
||||||
|
tail, _ := db.Tail()
|
||||||
|
if number >= tail {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil {
|
if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil {
|
||||||
return false
|
return false
|
||||||
|
|
@ -466,7 +472,13 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||||
// to a block.
|
// to a block.
|
||||||
func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
||||||
if isCanon(db, number, hash) {
|
if isCanon(db, number, hash) {
|
||||||
return true
|
// Block is in ancient store, but receipts can be pruned.
|
||||||
|
// Check if the block number is above the pruning tail.
|
||||||
|
tail, _ := db.Tail()
|
||||||
|
if number >= tail {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil {
|
if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests block header storage and retrieval operations.
|
// Tests block header storage and retrieval operations.
|
||||||
|
|
@ -52,10 +52,7 @@ func TestHeaderStorage(t *testing.T) {
|
||||||
if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil {
|
if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil {
|
||||||
t.Fatalf("Stored header RLP not found")
|
t.Fatalf("Stored header RLP not found")
|
||||||
} else {
|
} else {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
if hash := crypto.Keccak256Hash(entry); hash != header.Hash() {
|
||||||
hasher.Write(entry)
|
|
||||||
|
|
||||||
if hash := common.BytesToHash(hasher.Sum(nil)); hash != header.Hash() {
|
|
||||||
t.Fatalf("Retrieved RLP header mismatch: have %v, want %v", entry, header)
|
t.Fatalf("Retrieved RLP header mismatch: have %v, want %v", entry, header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,8 +69,7 @@ func TestBodyStorage(t *testing.T) {
|
||||||
|
|
||||||
// Create a test body to move around the database and make sure it's really new
|
// Create a test body to move around the database and make sure it's really new
|
||||||
body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}}
|
body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}}
|
||||||
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
|
||||||
rlp.Encode(hasher, body)
|
rlp.Encode(hasher, body)
|
||||||
hash := common.BytesToHash(hasher.Sum(nil))
|
hash := common.BytesToHash(hasher.Sum(nil))
|
||||||
|
|
||||||
|
|
@ -90,10 +86,7 @@ func TestBodyStorage(t *testing.T) {
|
||||||
if entry := ReadBodyRLP(db, hash, 0); entry == nil {
|
if entry := ReadBodyRLP(db, hash, 0); entry == nil {
|
||||||
t.Fatalf("Stored body RLP not found")
|
t.Fatalf("Stored body RLP not found")
|
||||||
} else {
|
} else {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
if calc := crypto.Keccak256Hash(entry); calc != hash {
|
||||||
hasher.Write(entry)
|
|
||||||
|
|
||||||
if calc := common.BytesToHash(hasher.Sum(nil)); calc != hash {
|
|
||||||
t.Fatalf("Retrieved RLP body mismatch: have %v, want %v", entry, body)
|
t.Fatalf("Retrieved RLP body mismatch: have %v, want %v", entry, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,9 +147,6 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans
|
||||||
}
|
}
|
||||||
txIndex := uint64(0)
|
txIndex := uint64(0)
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
if iter.Err() != nil {
|
|
||||||
return nil, 0, iter.Err()
|
|
||||||
}
|
|
||||||
// The preimage for the hash calculation of legacy transactions
|
// The preimage for the hash calculation of legacy transactions
|
||||||
// is just their RLP encoding. For typed (EIP-2718) transactions,
|
// is just their RLP encoding. For typed (EIP-2718) transactions,
|
||||||
// which are encoded as byte arrays, the preimage is the content of
|
// which are encoded as byte arrays, the preimage is the content of
|
||||||
|
|
@ -171,6 +168,9 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans
|
||||||
}
|
}
|
||||||
txIndex++
|
txIndex++
|
||||||
}
|
}
|
||||||
|
if iter.Err() != nil {
|
||||||
|
return nil, 0, iter.Err()
|
||||||
|
}
|
||||||
return nil, 0, errors.New("transaction not found")
|
return nil, 0, errors.New("transaction not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer table.Close()
|
||||||
table.dumpIndexStdout(start, end)
|
table.dumpIndexStdout(start, end)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,9 +153,9 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
|
||||||
err: err,
|
err: err,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var hashes []common.Hash
|
hashes := make([]common.Hash, len(body.Transactions))
|
||||||
for _, tx := range body.Transactions {
|
for i, tx := range body.Transactions {
|
||||||
hashes = append(hashes, tx.Hash())
|
hashes[i] = tx.Hash()
|
||||||
}
|
}
|
||||||
result = &blockTxHashes{
|
result = &blockTxHashes{
|
||||||
hashes: hashes,
|
hashes: hashes,
|
||||||
|
|
@ -357,9 +357,9 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-interrupt:
|
case <-interrupt:
|
||||||
logger("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
logger("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", nextNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
default:
|
default:
|
||||||
logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", nextNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -429,7 +429,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
filterMapBlockLV stat
|
filterMapBlockLV stat
|
||||||
|
|
||||||
// Path-mode archive data
|
// Path-mode archive data
|
||||||
stateIndex stat
|
stateIndex stat
|
||||||
|
trienodeIndex stat
|
||||||
|
|
||||||
// Verkle statistics
|
// Verkle statistics
|
||||||
verkleTries stat
|
verkleTries stat
|
||||||
|
|
@ -524,8 +525,19 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
bloomBits.add(size)
|
bloomBits.add(size)
|
||||||
|
|
||||||
// Path-based historic state indexes
|
// Path-based historic state indexes
|
||||||
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength:
|
case bytes.HasPrefix(key, StateHistoryAccountMetadataPrefix) && len(key) == len(StateHistoryAccountMetadataPrefix)+common.HashLength:
|
||||||
stateIndex.add(size)
|
stateIndex.add(size)
|
||||||
|
case bytes.HasPrefix(key, StateHistoryStorageMetadataPrefix) && len(key) == len(StateHistoryStorageMetadataPrefix)+2*common.HashLength:
|
||||||
|
stateIndex.add(size)
|
||||||
|
case bytes.HasPrefix(key, StateHistoryAccountBlockPrefix) && len(key) == len(StateHistoryAccountBlockPrefix)+common.HashLength+4:
|
||||||
|
stateIndex.add(size)
|
||||||
|
case bytes.HasPrefix(key, StateHistoryStorageBlockPrefix) && len(key) == len(StateHistoryStorageBlockPrefix)+2*common.HashLength+4:
|
||||||
|
stateIndex.add(size)
|
||||||
|
|
||||||
|
case bytes.HasPrefix(key, TrienodeHistoryMetadataPrefix) && len(key) >= len(TrienodeHistoryMetadataPrefix)+common.HashLength:
|
||||||
|
trienodeIndex.add(size)
|
||||||
|
case bytes.HasPrefix(key, TrienodeHistoryBlockPrefix) && len(key) >= len(TrienodeHistoryBlockPrefix)+common.HashLength+4:
|
||||||
|
trienodeIndex.add(size)
|
||||||
|
|
||||||
// Verkle trie data is detected, determine the sub-category
|
// Verkle trie data is detected, determine the sub-category
|
||||||
case bytes.HasPrefix(key, VerklePrefix):
|
case bytes.HasPrefix(key, VerklePrefix):
|
||||||
|
|
@ -622,12 +634,13 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
{"Key-Value store", "Path trie state lookups", stateLookups.sizeString(), stateLookups.countString()},
|
{"Key-Value store", "Path trie state lookups", stateLookups.sizeString(), stateLookups.countString()},
|
||||||
{"Key-Value store", "Path trie account nodes", accountTries.sizeString(), accountTries.countString()},
|
{"Key-Value store", "Path trie account nodes", accountTries.sizeString(), accountTries.countString()},
|
||||||
{"Key-Value store", "Path trie storage nodes", storageTries.sizeString(), storageTries.countString()},
|
{"Key-Value store", "Path trie storage nodes", storageTries.sizeString(), storageTries.countString()},
|
||||||
{"Key-Value store", "Path state history indexes", stateIndex.sizeString(), stateIndex.countString()},
|
|
||||||
{"Key-Value store", "Verkle trie nodes", verkleTries.sizeString(), verkleTries.countString()},
|
{"Key-Value store", "Verkle trie nodes", verkleTries.sizeString(), verkleTries.countString()},
|
||||||
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.sizeString(), verkleStateLookups.countString()},
|
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.sizeString(), verkleStateLookups.countString()},
|
||||||
{"Key-Value store", "Trie preimages", preimages.sizeString(), preimages.countString()},
|
{"Key-Value store", "Trie preimages", preimages.sizeString(), preimages.countString()},
|
||||||
{"Key-Value store", "Account snapshot", accountSnaps.sizeString(), accountSnaps.countString()},
|
{"Key-Value store", "Account snapshot", accountSnaps.sizeString(), accountSnaps.countString()},
|
||||||
{"Key-Value store", "Storage snapshot", storageSnaps.sizeString(), storageSnaps.countString()},
|
{"Key-Value store", "Storage snapshot", storageSnaps.sizeString(), storageSnaps.countString()},
|
||||||
|
{"Key-Value store", "Historical state index", stateIndex.sizeString(), stateIndex.countString()},
|
||||||
|
{"Key-Value store", "Historical trie index", trienodeIndex.sizeString(), trienodeIndex.countString()},
|
||||||
{"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()},
|
{"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()},
|
||||||
{"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()},
|
{"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()},
|
||||||
{"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()},
|
{"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()},
|
||||||
|
|
@ -672,7 +685,7 @@ var knownMetadataKeys = [][]byte{
|
||||||
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
||||||
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
|
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
|
||||||
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
|
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
|
||||||
filterMapsRangeKey, headStateHistoryIndexKey, VerkleTransitionStatePrefix,
|
filterMapsRangeKey, headStateHistoryIndexKey, headTrienodeHistoryIndexKey, VerkleTransitionStatePrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
// printChainMetadata prints out chain metadata to stderr.
|
// printChainMetadata prints out chain metadata to stderr.
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era/onedb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
@ -51,7 +52,7 @@ type Store struct {
|
||||||
type fileCacheEntry struct {
|
type fileCacheEntry struct {
|
||||||
refcount int // reference count. This is protected by Store.mu!
|
refcount int // reference count. This is protected by Store.mu!
|
||||||
opened chan struct{} // signals opening of file has completed
|
opened chan struct{} // signals opening of file has completed
|
||||||
file *era.Era // the file
|
file *onedb.Era // the file
|
||||||
err error // error from opening the file
|
err error // error from opening the file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +103,7 @@ func (db *Store) Close() {
|
||||||
|
|
||||||
// GetRawBody returns the raw body for a given block number.
|
// GetRawBody returns the raw body for a given block number.
|
||||||
func (db *Store) GetRawBody(number uint64) ([]byte, error) {
|
func (db *Store) GetRawBody(number uint64) ([]byte, error) {
|
||||||
epoch := number / uint64(era.MaxEra1Size)
|
epoch := number / uint64(era.MaxSize)
|
||||||
entry := db.getEraByEpoch(epoch)
|
entry := db.getEraByEpoch(epoch)
|
||||||
if entry.err != nil {
|
if entry.err != nil {
|
||||||
if errors.Is(entry.err, fs.ErrNotExist) {
|
if errors.Is(entry.err, fs.ErrNotExist) {
|
||||||
|
|
@ -117,7 +118,7 @@ func (db *Store) GetRawBody(number uint64) ([]byte, error) {
|
||||||
|
|
||||||
// GetRawReceipts returns the raw receipts for a given block number.
|
// GetRawReceipts returns the raw receipts for a given block number.
|
||||||
func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
|
func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
|
||||||
epoch := number / uint64(era.MaxEra1Size)
|
epoch := number / uint64(era.MaxSize)
|
||||||
entry := db.getEraByEpoch(epoch)
|
entry := db.getEraByEpoch(epoch)
|
||||||
if entry.err != nil {
|
if entry.err != nil {
|
||||||
if errors.Is(entry.err, fs.ErrNotExist) {
|
if errors.Is(entry.err, fs.ErrNotExist) {
|
||||||
|
|
@ -249,7 +250,7 @@ func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileC
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileOpened is called after an era file has been successfully opened.
|
// fileOpened is called after an era file has been successfully opened.
|
||||||
func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *era.Era) {
|
func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *onedb.Era) {
|
||||||
db.mu.Lock()
|
db.mu.Lock()
|
||||||
defer db.mu.Unlock()
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -282,7 +283,7 @@ func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error
|
||||||
entry.err = err
|
entry.err = err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Store) openEraFile(epoch uint64) (*era.Era, error) {
|
func (db *Store) openEraFile(epoch uint64) (*onedb.Era, error) {
|
||||||
// File name scheme is <network>-<epoch>-<root>.
|
// File name scheme is <network>-<epoch>-<root>.
|
||||||
glob := fmt.Sprintf("*-%05d-*.era1", epoch)
|
glob := fmt.Sprintf("*-%05d-*.era1", epoch)
|
||||||
matches, err := filepath.Glob(filepath.Join(db.datadir, glob))
|
matches, err := filepath.Glob(filepath.Join(db.datadir, glob))
|
||||||
|
|
@ -297,17 +298,17 @@ func (db *Store) openEraFile(epoch uint64) (*era.Era, error) {
|
||||||
}
|
}
|
||||||
filename := matches[0]
|
filename := matches[0]
|
||||||
|
|
||||||
e, err := era.Open(filename)
|
e, err := onedb.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Sanity-check start block.
|
// Sanity-check start block.
|
||||||
if e.Start()%uint64(era.MaxEra1Size) != 0 {
|
if e.Start()%uint64(era.MaxSize) != 0 {
|
||||||
e.Close()
|
e.Close()
|
||||||
return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size)
|
return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxSize)
|
||||||
}
|
}
|
||||||
log.Debug("Opened era1 file", "epoch", epoch)
|
log.Debug("Opened era1 file", "epoch", epoch)
|
||||||
return e, nil
|
return e.(*onedb.Era), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// doneWithFile signals that the caller has finished using a file.
|
// doneWithFile signals that the caller has finished using a file.
|
||||||
|
|
|
||||||
|
|
@ -221,13 +221,12 @@ func cleanup(path string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer dir.Close()
|
||||||
|
|
||||||
names, err := dir.Readdirnames(0)
|
names, err := dir.Readdirnames(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if cerr := dir.Close(); cerr != nil {
|
|
||||||
return cerr
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if name == filepath.Base(path)+tmpSuffix {
|
if name == filepath.Base(path)+tmpSuffix {
|
||||||
log.Info("Removed leftover freezer directory", "name", name)
|
log.Info("Removed leftover freezer directory", "name", name)
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
||||||
|
|
@ -147,7 +147,7 @@ func BenchmarkHashing(b *testing.B) {
|
||||||
blockRlp, _ = rlp.EncodeToBytes(block)
|
blockRlp, _ = rlp.EncodeToBytes(block)
|
||||||
}
|
}
|
||||||
var got common.Hash
|
var got common.Hash
|
||||||
var hasher = sha3.NewLegacyKeccak256()
|
var hasher = keccak.NewLegacyKeccak256()
|
||||||
b.Run("iteratorhashing", func(b *testing.B) {
|
b.Run("iteratorhashing", func(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
var hash common.Hash
|
var hash common.Hash
|
||||||
|
|
|
||||||
|
|
@ -17,40 +17,39 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb/database"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// historicReader wraps a historical state reader defined in path database,
|
// historicStateReader implements StateReader, wrapping a historical state reader
|
||||||
// providing historic state serving over the path scheme.
|
// defined in path database and provide historic state serving over the path scheme.
|
||||||
//
|
type historicStateReader struct {
|
||||||
// TODO(rjl493456442): historicReader is not thread-safe and does not fully
|
|
||||||
// comply with the StateReader interface requirements, needs to be fixed.
|
|
||||||
// Currently, it is only used in a non-concurrent context, so it is safe for now.
|
|
||||||
type historicReader struct {
|
|
||||||
reader *pathdb.HistoricalStateReader
|
reader *pathdb.HistoricalStateReader
|
||||||
|
lock sync.Mutex // Lock for protecting concurrent read
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHistoricReader constructs a reader for historic state serving.
|
// newHistoricStateReader constructs a reader for historical state serving.
|
||||||
func newHistoricReader(r *pathdb.HistoricalStateReader) *historicReader {
|
func newHistoricStateReader(r *pathdb.HistoricalStateReader) *historicStateReader {
|
||||||
return &historicReader{reader: r}
|
return &historicStateReader{reader: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account implements StateReader, retrieving the account specified by the address.
|
// Account implements StateReader, retrieving the account specified by the address.
|
||||||
//
|
func (r *historicStateReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||||
// An error will be returned if the associated snapshot is already stale or
|
r.lock.Lock()
|
||||||
// the requested account is not yet covered by the snapshot.
|
defer r.lock.Unlock()
|
||||||
//
|
|
||||||
// The returned account might be nil if it's not existent.
|
|
||||||
func (r *historicReader) Account(addr common.Address) (*types.StateAccount, error) {
|
|
||||||
account, err := r.reader.Account(addr)
|
account, err := r.reader.Account(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -80,7 +79,10 @@ func (r *historicReader) Account(addr common.Address) (*types.StateAccount, erro
|
||||||
// the requested storage slot is not yet covered by the snapshot.
|
// the requested storage slot is not yet covered by the snapshot.
|
||||||
//
|
//
|
||||||
// The returned storage slot might be empty if it's not existent.
|
// The returned storage slot might be empty if it's not existent.
|
||||||
func (r *historicReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
func (r *historicStateReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
blob, err := r.reader.Storage(addr, key)
|
blob, err := r.reader.Storage(addr, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
|
|
@ -97,6 +99,125 @@ func (r *historicReader) Storage(addr common.Address, key common.Hash) (common.H
|
||||||
return slot, nil
|
return slot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// historicTrieOpener is a wrapper of pathdb.HistoricalNodeReader, implementing
|
||||||
|
// the database.NodeDatabase by adding NodeReader function.
|
||||||
|
type historicTrieOpener struct {
|
||||||
|
root common.Hash
|
||||||
|
reader *pathdb.HistoricalNodeReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHistoricTrieOpener constructs the historical trie opener.
|
||||||
|
func newHistoricTrieOpener(root common.Hash, reader *pathdb.HistoricalNodeReader) *historicTrieOpener {
|
||||||
|
return &historicTrieOpener{
|
||||||
|
root: root,
|
||||||
|
reader: reader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeReader implements database.NodeDatabase, returning a node reader of a
|
||||||
|
// specified state.
|
||||||
|
func (o *historicTrieOpener) NodeReader(root common.Hash) (database.NodeReader, error) {
|
||||||
|
if root != o.root {
|
||||||
|
return nil, fmt.Errorf("state %x is not available", root)
|
||||||
|
}
|
||||||
|
return o.reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// historicalTrieReader wraps a historical node reader defined in path database,
|
||||||
|
// providing historical node serving over the path scheme.
|
||||||
|
type historicalTrieReader struct {
|
||||||
|
root common.Hash
|
||||||
|
opener *historicTrieOpener
|
||||||
|
tr Trie
|
||||||
|
|
||||||
|
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
|
||||||
|
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
|
||||||
|
lock sync.Mutex // Lock for protecting concurrent read
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHistoricalTrieReader constructs a reader for historical trie node serving.
|
||||||
|
func newHistoricalTrieReader(root common.Hash, r *pathdb.HistoricalNodeReader) (*historicalTrieReader, error) {
|
||||||
|
opener := newHistoricTrieOpener(root, r)
|
||||||
|
tr, err := trie.NewStateTrie(trie.StateTrieID(root), opener)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &historicalTrieReader{
|
||||||
|
root: root,
|
||||||
|
opener: opener,
|
||||||
|
tr: tr,
|
||||||
|
subRoots: make(map[common.Address]common.Hash),
|
||||||
|
subTries: make(map[common.Address]Trie),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// account is the inner version of Account and assumes the r.lock is already held.
|
||||||
|
func (r *historicalTrieReader) account(addr common.Address) (*types.StateAccount, error) {
|
||||||
|
account, err := r.tr.GetAccount(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if account == nil {
|
||||||
|
r.subRoots[addr] = types.EmptyRootHash
|
||||||
|
} else {
|
||||||
|
r.subRoots[addr] = account.Root
|
||||||
|
}
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account implements StateReader, retrieving the account specified by the address.
|
||||||
|
//
|
||||||
|
// An error will be returned if the associated snapshot is already stale or
|
||||||
|
// the requested account is not yet covered by the snapshot.
|
||||||
|
//
|
||||||
|
// The returned account might be nil if it's not existent.
|
||||||
|
func (r *historicalTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
return r.account(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage implements StateReader, retrieving the storage slot specified by the
|
||||||
|
// address and slot key.
|
||||||
|
//
|
||||||
|
// An error will be returned if the associated snapshot is already stale or
|
||||||
|
// the requested storage slot is not yet covered by the snapshot.
|
||||||
|
//
|
||||||
|
// The returned storage slot might be empty if it's not existent.
|
||||||
|
func (r *historicalTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
tr, found := r.subTries[addr]
|
||||||
|
if !found {
|
||||||
|
root, ok := r.subRoots[addr]
|
||||||
|
|
||||||
|
// The storage slot is accessed without account caching. It's unexpected
|
||||||
|
// behavior but try to resolve the account first anyway.
|
||||||
|
if !ok {
|
||||||
|
_, err := r.account(addr)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
root = r.subRoots[addr]
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.opener)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
r.subTries[addr] = tr
|
||||||
|
}
|
||||||
|
ret, err := tr.GetStorage(addr, key.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
var value common.Hash
|
||||||
|
value.SetBytes(ret)
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HistoricDB is the implementation of Database interface, with the ability to
|
// HistoricDB is the implementation of Database interface, with the ability to
|
||||||
// access historical state.
|
// access historical state.
|
||||||
type HistoricDB struct {
|
type HistoricDB struct {
|
||||||
|
|
@ -118,22 +239,54 @@ func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *His
|
||||||
|
|
||||||
// Reader implements Database interface, returning a reader of the specific state.
|
// Reader implements Database interface, returning a reader of the specific state.
|
||||||
func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
|
func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
|
||||||
hr, err := db.triedb.HistoricReader(stateRoot)
|
var readers []StateReader
|
||||||
|
sr, err := db.triedb.HistoricStateReader(stateRoot)
|
||||||
|
if err == nil {
|
||||||
|
readers = append(readers, newHistoricStateReader(sr))
|
||||||
|
}
|
||||||
|
nr, err := db.triedb.HistoricNodeReader(stateRoot)
|
||||||
|
if err == nil {
|
||||||
|
tr, err := newHistoricalTrieReader(stateRoot, nr)
|
||||||
|
if err == nil {
|
||||||
|
readers = append(readers, tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(readers) == 0 {
|
||||||
|
return nil, fmt.Errorf("historical state %x is not available", stateRoot)
|
||||||
|
}
|
||||||
|
combined, err := newMultiStateReader(readers...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newHistoricReader(hr)), nil
|
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenTrie opens the main account trie. It's not supported by historic database.
|
// OpenTrie opens the main account trie. It's not supported by historic database.
|
||||||
func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) {
|
func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||||
return nil, errors.New("not implemented")
|
nr, err := db.triedb.HistoricNodeReader(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tr, err := trie.NewStateTrie(trie.StateTrieID(root), newHistoricTrieOpener(root, nr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenStorageTrie opens the storage trie of an account. It's not supported by
|
// OpenStorageTrie opens the storage trie of an account. It's not supported by
|
||||||
// historic database.
|
// historic database.
|
||||||
func (db *HistoricDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) {
|
func (db *HistoricDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, _ Trie) (Trie, error) {
|
||||||
return nil, errors.New("not implemented")
|
nr, err := db.triedb.HistoricNodeReader(stateRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id := trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root)
|
||||||
|
tr, err := trie.NewStateTrie(id, newHistoricTrieOpener(stateRoot, nr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ type journalEntry interface {
|
||||||
revert(*StateDB)
|
revert(*StateDB)
|
||||||
|
|
||||||
// dirtied returns the Ethereum address modified by this journal entry.
|
// dirtied returns the Ethereum address modified by this journal entry.
|
||||||
dirtied() *common.Address
|
// indicates false if no address was changed.
|
||||||
|
dirtied() (common.Address, bool)
|
||||||
|
|
||||||
// copy returns a deep-copied journal entry.
|
// copy returns a deep-copied journal entry.
|
||||||
copy() journalEntry
|
copy() journalEntry
|
||||||
|
|
@ -100,8 +101,8 @@ func (j *journal) revertToSnapshot(revid int, s *StateDB) {
|
||||||
// append inserts a new modification entry to the end of the change journal.
|
// append inserts a new modification entry to the end of the change journal.
|
||||||
func (j *journal) append(entry journalEntry) {
|
func (j *journal) append(entry journalEntry) {
|
||||||
j.entries = append(j.entries, entry)
|
j.entries = append(j.entries, entry)
|
||||||
if addr := entry.dirtied(); addr != nil {
|
if addr, dirty := entry.dirtied(); dirty {
|
||||||
j.dirties[*addr]++
|
j.dirties[addr]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,9 +114,9 @@ func (j *journal) revert(statedb *StateDB, snapshot int) {
|
||||||
j.entries[i].revert(statedb)
|
j.entries[i].revert(statedb)
|
||||||
|
|
||||||
// Drop any dirty tracking induced by the change
|
// Drop any dirty tracking induced by the change
|
||||||
if addr := j.entries[i].dirtied(); addr != nil {
|
if addr, dirty := j.entries[i].dirtied(); dirty {
|
||||||
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
|
if j.dirties[addr]--; j.dirties[addr] == 0 {
|
||||||
delete(j.dirties, *addr)
|
delete(j.dirties, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -294,8 +295,8 @@ func (ch createObjectChange) revert(s *StateDB) {
|
||||||
delete(s.stateObjects, ch.account)
|
delete(s.stateObjects, ch.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) dirtied() *common.Address {
|
func (ch createObjectChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) copy() journalEntry {
|
func (ch createObjectChange) copy() journalEntry {
|
||||||
|
|
@ -308,8 +309,8 @@ func (ch createContractChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).newContract = false
|
s.getStateObject(ch.account).newContract = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createContractChange) dirtied() *common.Address {
|
func (ch createContractChange) dirtied() (common.Address, bool) {
|
||||||
return nil
|
return common.Address{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createContractChange) copy() journalEntry {
|
func (ch createContractChange) copy() journalEntry {
|
||||||
|
|
@ -325,8 +326,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) dirtied() *common.Address {
|
func (ch selfDestructChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) copy() journalEntry {
|
func (ch selfDestructChange) copy() journalEntry {
|
||||||
|
|
@ -340,8 +341,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||||
func (ch touchChange) revert(s *StateDB) {
|
func (ch touchChange) revert(s *StateDB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch touchChange) dirtied() *common.Address {
|
func (ch touchChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch touchChange) copy() journalEntry {
|
func (ch touchChange) copy() journalEntry {
|
||||||
|
|
@ -354,8 +355,8 @@ func (ch balanceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setBalance(ch.prev)
|
s.getStateObject(ch.account).setBalance(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch balanceChange) dirtied() *common.Address {
|
func (ch balanceChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch balanceChange) copy() journalEntry {
|
func (ch balanceChange) copy() journalEntry {
|
||||||
|
|
@ -369,8 +370,8 @@ func (ch nonceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setNonce(ch.prev)
|
s.getStateObject(ch.account).setNonce(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch nonceChange) dirtied() *common.Address {
|
func (ch nonceChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch nonceChange) copy() journalEntry {
|
func (ch nonceChange) copy() journalEntry {
|
||||||
|
|
@ -384,8 +385,8 @@ func (ch codeChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) dirtied() *common.Address {
|
func (ch codeChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) copy() journalEntry {
|
func (ch codeChange) copy() journalEntry {
|
||||||
|
|
@ -399,8 +400,8 @@ func (ch storageChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) dirtied() *common.Address {
|
func (ch storageChange) dirtied() (common.Address, bool) {
|
||||||
return &ch.account
|
return ch.account, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) copy() journalEntry {
|
func (ch storageChange) copy() journalEntry {
|
||||||
|
|
@ -416,8 +417,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
|
||||||
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) dirtied() *common.Address {
|
func (ch transientStorageChange) dirtied() (common.Address, bool) {
|
||||||
return nil
|
return common.Address{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) copy() journalEntry {
|
func (ch transientStorageChange) copy() journalEntry {
|
||||||
|
|
@ -432,8 +433,8 @@ func (ch refundChange) revert(s *StateDB) {
|
||||||
s.refund = ch.prev
|
s.refund = ch.prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch refundChange) dirtied() *common.Address {
|
func (ch refundChange) dirtied() (common.Address, bool) {
|
||||||
return nil
|
return common.Address{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch refundChange) copy() journalEntry {
|
func (ch refundChange) copy() journalEntry {
|
||||||
|
|
@ -452,8 +453,8 @@ func (ch addLogChange) revert(s *StateDB) {
|
||||||
s.logSize--
|
s.logSize--
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch addLogChange) dirtied() *common.Address {
|
func (ch addLogChange) dirtied() (common.Address, bool) {
|
||||||
return nil
|
return common.Address{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch addLogChange) copy() journalEntry {
|
func (ch addLogChange) copy() journalEntry {
|
||||||
|
|
@ -475,8 +476,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteAddress(ch.address)
|
s.accessList.DeleteAddress(ch.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) dirtied() *common.Address {
|
func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
|
||||||
return nil
|
return common.Address{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) copy() journalEntry {
|
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||||
|
|
@ -489,8 +490,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteSlot(ch.address, ch.slot)
|
s.accessList.DeleteSlot(ch.address, ch.slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
|
||||||
return nil
|
return common.Address{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) copy() journalEntry {
|
func (ch accessListAddSlotChange) copy() journalEntry {
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,28 @@ type ContractCodeReader interface {
|
||||||
CodeSize(addr common.Address, codeHash common.Hash) (int, error)
|
CodeSize(addr common.Address, codeHash common.Hash) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContractCodeReaderStats aggregates statistics for the contract code reader.
|
||||||
|
type ContractCodeReaderStats struct {
|
||||||
|
CacheHit int64 // Number of cache hits
|
||||||
|
CacheMiss int64 // Number of cache misses
|
||||||
|
CacheHitBytes int64 // Total bytes served from cache
|
||||||
|
CacheMissBytes int64 // Total bytes read on cache misses
|
||||||
|
}
|
||||||
|
|
||||||
|
// HitRate returns the cache hit rate.
|
||||||
|
func (s ContractCodeReaderStats) HitRate() float64 {
|
||||||
|
if s.CacheHit == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
|
// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
|
||||||
// expose statistics of code reader.
|
// expose statistics of code reader.
|
||||||
type ContractCodeReaderWithStats interface {
|
type ContractCodeReaderWithStats interface {
|
||||||
ContractCodeReader
|
ContractCodeReader
|
||||||
GetStats() (int64, int64)
|
|
||||||
|
GetStats() ContractCodeReaderStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateReader defines the interface for accessing accounts and storage slots
|
// StateReader defines the interface for accessing accounts and storage slots
|
||||||
|
|
@ -99,13 +116,11 @@ type Reader interface {
|
||||||
|
|
||||||
// ReaderStats wraps the statistics of reader.
|
// ReaderStats wraps the statistics of reader.
|
||||||
type ReaderStats struct {
|
type ReaderStats struct {
|
||||||
// Cache stats
|
|
||||||
AccountCacheHit int64
|
AccountCacheHit int64
|
||||||
AccountCacheMiss int64
|
AccountCacheMiss int64
|
||||||
StorageCacheHit int64
|
StorageCacheHit int64
|
||||||
StorageCacheMiss int64
|
StorageCacheMiss int64
|
||||||
ContractCodeHit int64
|
CodeStats ContractCodeReaderStats
|
||||||
ContractCodeMiss int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer, returning string format statistics.
|
// String implements fmt.Stringer, returning string format statistics.
|
||||||
|
|
@ -113,7 +128,6 @@ func (s ReaderStats) String() string {
|
||||||
var (
|
var (
|
||||||
accountCacheHitRate float64
|
accountCacheHitRate float64
|
||||||
storageCacheHitRate float64
|
storageCacheHitRate float64
|
||||||
contractCodeHitRate float64
|
|
||||||
)
|
)
|
||||||
if s.AccountCacheHit > 0 {
|
if s.AccountCacheHit > 0 {
|
||||||
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
|
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
|
||||||
|
|
@ -121,13 +135,10 @@ func (s ReaderStats) String() string {
|
||||||
if s.StorageCacheHit > 0 {
|
if s.StorageCacheHit > 0 {
|
||||||
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
|
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
|
||||||
}
|
}
|
||||||
if s.ContractCodeHit > 0 {
|
|
||||||
contractCodeHitRate = float64(s.ContractCodeHit) / float64(s.ContractCodeHit+s.ContractCodeMiss) * 100
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf("Reader statistics\n")
|
msg := fmt.Sprintf("Reader statistics\n")
|
||||||
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
|
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
|
||||||
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
|
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
|
||||||
msg += fmt.Sprintf("code: hit: %d, miss: %d, rate: %.2f\n", s.ContractCodeHit, s.ContractCodeMiss, contractCodeHitRate)
|
msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate())
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,8 +161,10 @@ type cachingCodeReader struct {
|
||||||
codeSizeCache *lru.Cache[common.Hash, int]
|
codeSizeCache *lru.Cache[common.Hash, int]
|
||||||
|
|
||||||
// Cache statistics
|
// Cache statistics
|
||||||
hit atomic.Int64 // Number of code lookups found in the cache.
|
hit atomic.Int64 // Number of code lookups found in the cache
|
||||||
miss atomic.Int64 // Number of code lookups not found in the cache.
|
miss atomic.Int64 // Number of code lookups not found in the cache
|
||||||
|
hitBytes atomic.Int64 // Total number of bytes read from cache
|
||||||
|
missBytes atomic.Int64 // Total number of bytes read from database
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCachingCodeReader constructs the code reader.
|
// newCachingCodeReader constructs the code reader.
|
||||||
|
|
@ -169,6 +182,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b
|
||||||
code, _ := r.codeCache.Get(codeHash)
|
code, _ := r.codeCache.Get(codeHash)
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
r.hit.Add(1)
|
r.hit.Add(1)
|
||||||
|
r.hitBytes.Add(int64(len(code)))
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
r.miss.Add(1)
|
r.miss.Add(1)
|
||||||
|
|
@ -177,6 +191,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
r.codeCache.Add(codeHash, code)
|
r.codeCache.Add(codeHash, code)
|
||||||
r.codeSizeCache.Add(codeHash, len(code))
|
r.codeSizeCache.Add(codeHash, len(code))
|
||||||
|
r.missBytes.Add(int64(len(code)))
|
||||||
}
|
}
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
@ -202,9 +217,14 @@ func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool
|
||||||
return len(code) > 0
|
return len(code) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStats returns the cache statistics fo the code reader.
|
// GetStats returns the statistics of the code reader.
|
||||||
func (r *cachingCodeReader) GetStats() (int64, int64) {
|
func (r *cachingCodeReader) GetStats() ContractCodeReaderStats {
|
||||||
return r.hit.Load(), r.miss.Load()
|
return ContractCodeReaderStats{
|
||||||
|
CacheHit: r.hit.Load(),
|
||||||
|
CacheMiss: r.miss.Load(),
|
||||||
|
CacheHitBytes: r.hitBytes.Load(),
|
||||||
|
CacheMissBytes: r.missBytes.Load(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flatReader wraps a database state reader and is safe for concurrent access.
|
// flatReader wraps a database state reader and is safe for concurrent access.
|
||||||
|
|
@ -654,13 +674,11 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
|
||||||
|
|
||||||
// GetStats implements ReaderWithStats, returning the statistics of state reader.
|
// GetStats implements ReaderWithStats, returning the statistics of state reader.
|
||||||
func (r *readerWithStats) GetStats() ReaderStats {
|
func (r *readerWithStats) GetStats() ReaderStats {
|
||||||
codeHit, codeMiss := r.ContractCodeReaderWithStats.GetStats()
|
|
||||||
return ReaderStats{
|
return ReaderStats{
|
||||||
AccountCacheHit: r.accountCacheHit.Load(),
|
AccountCacheHit: r.accountCacheHit.Load(),
|
||||||
AccountCacheMiss: r.accountCacheMiss.Load(),
|
AccountCacheMiss: r.accountCacheMiss.Load(),
|
||||||
StorageCacheHit: r.storageCacheHit.Load(),
|
StorageCacheHit: r.storageCacheHit.Load(),
|
||||||
StorageCacheMiss: r.storageCacheMiss.Load(),
|
StorageCacheMiss: r.storageCacheMiss.Load(),
|
||||||
ContractCodeHit: codeHit,
|
CodeStats: r.ContractCodeReaderWithStats.GetStats(),
|
||||||
ContractCodeMiss: codeMiss,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
@ -34,16 +35,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func hashData(input []byte) common.Hash {
|
func hashData(input []byte) common.Hash {
|
||||||
var hasher = sha3.NewLegacyKeccak256()
|
return crypto.Keccak256Hash(input)
|
||||||
var hash common.Hash
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write(input)
|
|
||||||
hasher.Sum(hash[:0])
|
|
||||||
return hash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that snapshot generation from an empty database.
|
// Tests that snapshot generation from an empty database.
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,11 @@ func (s Storage) Copy() Storage {
|
||||||
// - Account values as well as storages can be accessed and modified through the object.
|
// - Account values as well as storages can be accessed and modified through the object.
|
||||||
// - Finally, call commit to return the changes of storage trie and update account data.
|
// - Finally, call commit to return the changes of storage trie and update account data.
|
||||||
type stateObject struct {
|
type stateObject struct {
|
||||||
db *StateDB
|
db *StateDB
|
||||||
address common.Address // address of ethereum account
|
address common.Address // address of ethereum account
|
||||||
addrHash common.Hash // hash of ethereum address of the account
|
addressHash *common.Hash // hash of ethereum address of the account
|
||||||
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
|
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
|
data types.StateAccount // Account data with all mutations applied in the scope of block
|
||||||
|
|
||||||
// Write caches.
|
// Write caches.
|
||||||
trie Trie // storage trie, which becomes non-nil on first access
|
trie Trie // storage trie, which becomes non-nil on first access
|
||||||
|
|
@ -102,7 +102,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
||||||
return &stateObject{
|
return &stateObject{
|
||||||
db: db,
|
db: db,
|
||||||
address: address,
|
address: address,
|
||||||
addrHash: crypto.Keccak256Hash(address[:]),
|
|
||||||
origin: origin,
|
origin: origin,
|
||||||
data: *acct,
|
data: *acct,
|
||||||
originStorage: make(Storage),
|
originStorage: make(Storage),
|
||||||
|
|
@ -112,6 +111,14 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *stateObject) addrHash() common.Hash {
|
||||||
|
if s.addressHash == nil {
|
||||||
|
h := crypto.Keccak256Hash(s.address[:])
|
||||||
|
s.addressHash = &h
|
||||||
|
}
|
||||||
|
return *s.addressHash
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stateObject) markSelfdestructed() {
|
func (s *stateObject) markSelfdestructed() {
|
||||||
s.selfDestructed = true
|
s.selfDestructed = true
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +158,7 @@ func (s *stateObject) getPrefetchedTrie() Trie {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Attempt to retrieve the trie from the prefetcher
|
// Attempt to retrieve the trie from the prefetcher
|
||||||
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
return s.db.prefetcher.trie(s.addrHash(), s.data.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState retrieves a value associated with the given storage key.
|
// GetState retrieves a value associated with the given storage key.
|
||||||
|
|
@ -203,7 +210,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||||
|
|
||||||
// Schedule the resolved storage slots for prefetching if it's enabled.
|
// Schedule the resolved storage slots for prefetching if it's enabled.
|
||||||
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
|
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
|
||||||
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil {
|
if err = s.db.prefetcher.prefetch(s.addrHash(), s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil {
|
||||||
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
|
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +271,7 @@ func (s *stateObject) finalise() {
|
||||||
s.pendingStorage[key] = value
|
s.pendingStorage[key] = value
|
||||||
}
|
}
|
||||||
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
||||||
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
|
if err := s.db.prefetcher.prefetch(s.addrHash(), s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
|
||||||
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
|
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +366,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
s.db.StorageDeleted.Add(1)
|
s.db.StorageDeleted.Add(1)
|
||||||
}
|
}
|
||||||
if s.db.prefetcher != nil {
|
if s.db.prefetcher != nil {
|
||||||
s.db.prefetcher.used(s.addrHash, s.data.Root, nil, used)
|
s.db.prefetcher.used(s.addrHash(), s.data.Root, nil, used)
|
||||||
}
|
}
|
||||||
s.uncommittedStorage = make(Storage) // empties the commit markers
|
s.uncommittedStorage = make(Storage) // empties the commit markers
|
||||||
return tr, nil
|
return tr, nil
|
||||||
|
|
@ -491,7 +498,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
obj := &stateObject{
|
obj := &stateObject{
|
||||||
db: db,
|
db: db,
|
||||||
address: s.address,
|
address: s.address,
|
||||||
addrHash: s.addrHash,
|
addressHash: nil,
|
||||||
origin: s.origin,
|
origin: s.origin,
|
||||||
data: s.data,
|
data: s.data,
|
||||||
code: s.code,
|
code: s.code,
|
||||||
|
|
@ -541,6 +548,7 @@ func (s *stateObject) Code() []byte {
|
||||||
defer func(start time.Time) {
|
defer func(start time.Time) {
|
||||||
s.db.CodeLoaded += 1
|
s.db.CodeLoaded += 1
|
||||||
s.db.CodeReads += time.Since(start)
|
s.db.CodeReads += time.Since(start)
|
||||||
|
s.db.CodeLoadBytes += len(s.code)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,7 @@ func (t *SizeTracker) run() {
|
||||||
|
|
||||||
// Evict the stale statistics
|
// Evict the stale statistics
|
||||||
heap.Push(&h, stats[u.root])
|
heap.Push(&h, stats[u.root])
|
||||||
for u.blockNumber-h[0].BlockNumber > statEvictThreshold {
|
for len(h) > 0 && u.blockNumber-h[0].BlockNumber > statEvictThreshold {
|
||||||
delete(stats, h[0].StateRoot)
|
delete(stats, h[0].StateRoot)
|
||||||
heap.Pop(&h)
|
heap.Pop(&h)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,15 @@ type StateDB struct {
|
||||||
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
||||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||||
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
||||||
CodeLoaded int // Number of contract code loaded during the state transition
|
|
||||||
|
// CodeLoadBytes is the total number of bytes read from contract code.
|
||||||
|
// This value may be smaller than the actual number of bytes read, since
|
||||||
|
// some APIs (e.g. CodeSize) may load the entire code from either the
|
||||||
|
// cache or the database when the size is not available in the cache.
|
||||||
|
CodeLoaded int // Number of contract code loaded during the state transition
|
||||||
|
CodeLoadBytes int // Total bytes of resolved code
|
||||||
|
CodeUpdated int // Number of contracts with code changes that persisted
|
||||||
|
CodeUpdateBytes int // Total bytes of persisted code written
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// New creates a new state from a given trie.
|
||||||
|
|
@ -509,21 +517,13 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelfDestruct marks the given account as selfdestructed.
|
// SelfDestruct marks the given account as selfdestructed.
|
||||||
// This clears the account balance.
|
|
||||||
//
|
//
|
||||||
// The account's state object is still available until the state is committed,
|
// The account's state object is still available until the state is committed,
|
||||||
// getStateObject will return a non-nil account after SelfDestruct.
|
// getStateObject will return a non-nil account after SelfDestruct.
|
||||||
func (s *StateDB) SelfDestruct(addr common.Address) uint256.Int {
|
func (s *StateDB) SelfDestruct(addr common.Address) {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
var prevBalance uint256.Int
|
|
||||||
if stateObject == nil {
|
if stateObject == nil {
|
||||||
return prevBalance
|
return
|
||||||
}
|
|
||||||
prevBalance = *(stateObject.Balance())
|
|
||||||
// Regardless of whether it is already destructed or not, we do have to
|
|
||||||
// journal the balance-change, if we set it to zero here.
|
|
||||||
if !stateObject.Balance().IsZero() {
|
|
||||||
stateObject.SetBalance(new(uint256.Int))
|
|
||||||
}
|
}
|
||||||
// If it is already marked as self-destructed, we do not need to add it
|
// If it is already marked as self-destructed, we do not need to add it
|
||||||
// for journalling a second time.
|
// for journalling a second time.
|
||||||
|
|
@ -531,18 +531,6 @@ func (s *StateDB) SelfDestruct(addr common.Address) uint256.Int {
|
||||||
s.journal.destruct(addr)
|
s.journal.destruct(addr)
|
||||||
stateObject.markSelfdestructed()
|
stateObject.markSelfdestructed()
|
||||||
}
|
}
|
||||||
return prevBalance
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StateDB) SelfDestruct6780(addr common.Address) (uint256.Int, bool) {
|
|
||||||
stateObject := s.getStateObject(addr)
|
|
||||||
if stateObject == nil {
|
|
||||||
return uint256.Int{}, false
|
|
||||||
}
|
|
||||||
if stateObject.newContract {
|
|
||||||
return s.SelfDestruct(addr), true
|
|
||||||
}
|
|
||||||
return *(stateObject.Balance()), false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTransientState sets transient storage for a given account. It
|
// SetTransientState sets transient storage for a given account. It
|
||||||
|
|
@ -670,6 +658,16 @@ func (s *StateDB) CreateContract(addr common.Address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNewContract reports whether the contract at the given address was deployed
|
||||||
|
// during the current transaction.
|
||||||
|
func (s *StateDB) IsNewContract(addr common.Address) bool {
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
if obj == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return obj.newContract
|
||||||
|
}
|
||||||
|
|
||||||
// Copy creates a deep, independent copy of the state.
|
// Copy creates a deep, independent copy of the state.
|
||||||
// Snapshots of the copied state cannot be applied to the copy.
|
// Snapshots of the copied state cannot be applied to the copy.
|
||||||
func (s *StateDB) Copy() *StateDB {
|
func (s *StateDB) Copy() *StateDB {
|
||||||
|
|
@ -869,13 +867,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
witness := trie.Witness()
|
witness := trie.Witness()
|
||||||
s.witness.AddState(witness)
|
s.witness.AddState(witness)
|
||||||
if s.witnessStats != nil {
|
if s.witnessStats != nil {
|
||||||
s.witnessStats.Add(witness, obj.addrHash)
|
s.witnessStats.Add(witness, obj.addrHash())
|
||||||
}
|
}
|
||||||
} else if obj.trie != nil {
|
} else if obj.trie != nil {
|
||||||
witness := obj.trie.Witness()
|
witness := obj.trie.Witness()
|
||||||
s.witness.AddState(witness)
|
s.witness.AddState(witness)
|
||||||
if s.witnessStats != nil {
|
if s.witnessStats != nil {
|
||||||
s.witnessStats.Add(witness, obj.addrHash)
|
s.witnessStats.Add(witness, obj.addrHash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -893,13 +891,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
witness := trie.Witness()
|
witness := trie.Witness()
|
||||||
s.witness.AddState(witness)
|
s.witness.AddState(witness)
|
||||||
if s.witnessStats != nil {
|
if s.witnessStats != nil {
|
||||||
s.witnessStats.Add(witness, obj.addrHash)
|
s.witnessStats.Add(witness, obj.addrHash())
|
||||||
}
|
}
|
||||||
} else if obj.trie != nil {
|
} else if obj.trie != nil {
|
||||||
witness := obj.trie.Witness()
|
witness := obj.trie.Witness()
|
||||||
s.witness.AddState(witness)
|
s.witness.AddState(witness)
|
||||||
if s.witnessStats != nil {
|
if s.witnessStats != nil {
|
||||||
s.witnessStats.Add(witness, obj.addrHash)
|
s.witnessStats.Add(witness, obj.addrHash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -945,8 +943,15 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
if op.isDelete() {
|
if op.isDelete() {
|
||||||
deletedAddrs = append(deletedAddrs, addr)
|
deletedAddrs = append(deletedAddrs, addr)
|
||||||
} else {
|
} else {
|
||||||
s.updateStateObject(s.stateObjects[addr])
|
obj := s.stateObjects[addr]
|
||||||
|
s.updateStateObject(obj)
|
||||||
s.AccountUpdated += 1
|
s.AccountUpdated += 1
|
||||||
|
|
||||||
|
// Count code writes post-Finalise so reverted CREATEs are excluded.
|
||||||
|
if obj.dirtyCode {
|
||||||
|
s.CodeUpdated += 1
|
||||||
|
s.CodeUpdateBytes += len(obj.code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
usedAddrs = append(usedAddrs, addr) // Copy needed for closure
|
usedAddrs = append(usedAddrs, addr) // Copy needed for closure
|
||||||
}
|
}
|
||||||
|
|
@ -1279,7 +1284,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
updates[obj.addrHash] = update
|
updates[obj.addrHash()] = update
|
||||||
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
|
|
@ -52,6 +54,10 @@ func (s *hookedStateDB) CreateContract(addr common.Address) {
|
||||||
s.inner.CreateContract(addr)
|
s.inner.CreateContract(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) IsNewContract(addr common.Address) bool {
|
||||||
|
return s.inner.IsNewContract(addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
|
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
|
||||||
return s.inner.GetBalance(addr)
|
return s.inner.GetBalance(addr)
|
||||||
}
|
}
|
||||||
|
|
@ -211,56 +217,8 @@ func (s *hookedStateDB) SetState(address common.Address, key common.Hash, value
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int {
|
func (s *hookedStateDB) SelfDestruct(address common.Address) {
|
||||||
var prevCode []byte
|
s.inner.SelfDestruct(address)
|
||||||
var prevCodeHash common.Hash
|
|
||||||
|
|
||||||
if s.hooks.OnCodeChange != nil || s.hooks.OnCodeChangeV2 != nil {
|
|
||||||
prevCode = s.inner.GetCode(address)
|
|
||||||
prevCodeHash = s.inner.GetCodeHash(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev := s.inner.SelfDestruct(address)
|
|
||||||
|
|
||||||
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
|
|
||||||
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(prevCode) > 0 {
|
|
||||||
if s.hooks.OnCodeChangeV2 != nil {
|
|
||||||
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
|
|
||||||
} else if s.hooks.OnCodeChange != nil {
|
|
||||||
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, bool) {
|
|
||||||
var prevCode []byte
|
|
||||||
var prevCodeHash common.Hash
|
|
||||||
|
|
||||||
if s.hooks.OnCodeChange != nil || s.hooks.OnCodeChangeV2 != nil {
|
|
||||||
prevCodeHash = s.inner.GetCodeHash(address)
|
|
||||||
prevCode = s.inner.GetCode(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev, changed := s.inner.SelfDestruct6780(address)
|
|
||||||
|
|
||||||
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
|
|
||||||
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed && len(prevCode) > 0 {
|
|
||||||
if s.hooks.OnCodeChangeV2 != nil {
|
|
||||||
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
|
|
||||||
} else if s.hooks.OnCodeChange != nil {
|
|
||||||
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev, changed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) AddLog(log *types.Log) {
|
func (s *hookedStateDB) AddLog(log *types.Log) {
|
||||||
|
|
@ -272,17 +230,58 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
defer s.inner.Finalise(deleteEmptyObjects)
|
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
|
||||||
if s.hooks.OnBalanceChange == nil {
|
// Short circuit if no relevant hooks are set.
|
||||||
|
s.inner.Finalise(deleteEmptyObjects)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect all self-destructed addresses first, then sort them to ensure
|
||||||
|
// that state change hooks will be invoked in deterministic
|
||||||
|
// order when the accounts are deleted below
|
||||||
|
var selfDestructedAddrs []common.Address
|
||||||
for addr := range s.inner.journal.dirties {
|
for addr := range s.inner.journal.dirties {
|
||||||
obj := s.inner.stateObjects[addr]
|
obj := s.inner.stateObjects[addr]
|
||||||
if obj != nil && obj.selfDestructed {
|
if obj == nil || !obj.selfDestructed {
|
||||||
// If ether was sent to account post-selfdestruct it is burnt.
|
// Not self-destructed, keep searching.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
selfDestructedAddrs = append(selfDestructedAddrs, addr)
|
||||||
|
}
|
||||||
|
sort.Slice(selfDestructedAddrs, func(i, j int) bool {
|
||||||
|
return bytes.Compare(selfDestructedAddrs[i][:], selfDestructedAddrs[j][:]) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, addr := range selfDestructedAddrs {
|
||||||
|
obj := s.inner.stateObjects[addr]
|
||||||
|
// Bingo: state object was self-destructed, call relevant hooks.
|
||||||
|
|
||||||
|
// If ether was sent to account post-selfdestruct, record as burnt.
|
||||||
|
if s.hooks.OnBalanceChange != nil {
|
||||||
if bal := obj.Balance(); bal.Sign() != 0 {
|
if bal := obj.Balance(); bal.Sign() != 0 {
|
||||||
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
|
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nonce is set to reset on self-destruct.
|
||||||
|
if s.hooks.OnNonceChangeV2 != nil {
|
||||||
|
s.hooks.OnNonceChangeV2(addr, obj.Nonce(), 0, tracing.NonceChangeSelfdestruct)
|
||||||
|
} else if s.hooks.OnNonceChange != nil {
|
||||||
|
s.hooks.OnNonceChange(addr, obj.Nonce(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an initcode invokes selfdestruct, do not emit a code change.
|
||||||
|
prevCodeHash := s.inner.GetCodeHash(addr)
|
||||||
|
if prevCodeHash == types.EmptyCodeHash {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Otherwise, trace the change.
|
||||||
|
if s.hooks.OnCodeChangeV2 != nil {
|
||||||
|
s.hooks.OnCodeChangeV2(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
|
||||||
|
} else if s.hooks.OnCodeChange != nil {
|
||||||
|
s.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.inner.Finalise(deleteEmptyObjects)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ func TestBurn(t *testing.T) {
|
||||||
createAndDestroy := func(addr common.Address) {
|
createAndDestroy := func(addr common.Address) {
|
||||||
hooked.AddBalance(addr, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
|
hooked.AddBalance(addr, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
|
||||||
hooked.CreateContract(addr)
|
hooked.CreateContract(addr)
|
||||||
|
// Simulate what the opcode handler does: clear balance before selfdestruct
|
||||||
|
hooked.SubBalance(addr, hooked.GetBalance(addr), tracing.BalanceDecreaseSelfdestruct)
|
||||||
hooked.SelfDestruct(addr)
|
hooked.SelfDestruct(addr)
|
||||||
// sanity-check that balance is now 0
|
// sanity-check that balance is now 0
|
||||||
if have, want := hooked.GetBalance(addr), new(uint256.Int); !have.Eq(want) {
|
if have, want := hooked.GetBalance(addr), new(uint256.Int); !have.Eq(want) {
|
||||||
|
|
@ -140,8 +142,8 @@ func TestHooks_OnCodeChangeV2(t *testing.T) {
|
||||||
var result []string
|
var result []string
|
||||||
var wants = []string{
|
var wants = []string{
|
||||||
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) ContractCreation",
|
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) ContractCreation",
|
||||||
"0xaa00000000000000000000000000000000000000.code: 0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
|
|
||||||
"0xbb00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) ContractCreation",
|
"0xbb00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) ContractCreation",
|
||||||
|
"0xaa00000000000000000000000000000000000000.code: 0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
|
||||||
"0xbb00000000000000000000000000000000000000.code: 0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
|
"0xbb00000000000000000000000000000000000000.code: 0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
|
||||||
}
|
}
|
||||||
emitF := func(format string, a ...any) {
|
emitF := func(format string, a ...any) {
|
||||||
|
|
@ -157,7 +159,8 @@ func TestHooks_OnCodeChangeV2(t *testing.T) {
|
||||||
|
|
||||||
sdb.SetCode(common.Address{0xbb}, []byte{0x13, 38}, tracing.CodeChangeContractCreation)
|
sdb.SetCode(common.Address{0xbb}, []byte{0x13, 38}, tracing.CodeChangeContractCreation)
|
||||||
sdb.CreateContract(common.Address{0xbb})
|
sdb.CreateContract(common.Address{0xbb})
|
||||||
sdb.SelfDestruct6780(common.Address{0xbb})
|
sdb.SelfDestruct(common.Address{0xbb})
|
||||||
|
sdb.Finalise(true)
|
||||||
|
|
||||||
if len(result) != len(wants) {
|
if len(result) != len(wants) {
|
||||||
t.Fatalf("number of tracing events wrong, have %d want %d", len(result), len(wants))
|
t.Fatalf("number of tracing events wrong, have %d want %d", len(result), len(wants))
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func u64(val uint64) *uint64 { return &val }
|
func u64(val uint64) *uint64 { return &val }
|
||||||
|
|
@ -398,7 +398,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
|
||||||
var receipts []*types.Receipt
|
var receipts []*types.Receipt
|
||||||
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
|
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
|
||||||
// Preferably something unique. So let's use a combo of blocknum + txhash
|
// Preferably something unique. So let's use a combo of blocknum + txhash
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
hasher.Write(header.Number.Bytes())
|
hasher.Write(header.Number.Bytes())
|
||||||
var cumulativeGas uint64
|
var cumulativeGas uint64
|
||||||
var nBlobs int
|
var nBlobs int
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,9 @@ const _BalanceChangeReason_name = "UnspecifiedBalanceIncreaseRewardMineUncleBala
|
||||||
var _BalanceChangeReason_index = [...]uint16{0, 11, 41, 71, 96, 125, 160, 181, 205, 231, 256, 264, 276, 303, 330, 361, 367}
|
var _BalanceChangeReason_index = [...]uint16{0, 11, 41, 71, 96, 125, 160, 181, 205, 231, 256, 264, 276, 303, 330, 361, 367}
|
||||||
|
|
||||||
func (i BalanceChangeReason) String() string {
|
func (i BalanceChangeReason) String() string {
|
||||||
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_BalanceChangeReason_index)-1 {
|
||||||
return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]]
|
return _BalanceChangeReason_name[_BalanceChangeReason_index[idx]:_BalanceChangeReason_index[idx+1]]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ const _CodeChangeReason_name = "UnspecifiedContractCreationGenesisAuthorizationA
|
||||||
var _CodeChangeReason_index = [...]uint8{0, 11, 27, 34, 47, 65, 77, 83}
|
var _CodeChangeReason_index = [...]uint8{0, 11, 27, 34, 47, 65, 77, 83}
|
||||||
|
|
||||||
func (i CodeChangeReason) String() string {
|
func (i CodeChangeReason) String() string {
|
||||||
if i >= CodeChangeReason(len(_CodeChangeReason_index)-1) {
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_CodeChangeReason_index)-1 {
|
||||||
return "CodeChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
return "CodeChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
return _CodeChangeReason_name[_CodeChangeReason_index[i]:_CodeChangeReason_index[i+1]]
|
return _CodeChangeReason_name[_CodeChangeReason_index[idx]:_CodeChangeReason_index[idx+1]]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,17 @@ func _() {
|
||||||
_ = x[NonceChangeNewContract-4]
|
_ = x[NonceChangeNewContract-4]
|
||||||
_ = x[NonceChangeAuthorization-5]
|
_ = x[NonceChangeAuthorization-5]
|
||||||
_ = x[NonceChangeRevert-6]
|
_ = x[NonceChangeRevert-6]
|
||||||
|
_ = x[NonceChangeSelfdestruct-7]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _NonceChangeReason_name = "UnspecifiedGenesisEoACallContractCreatorNewContractAuthorizationRevert"
|
const _NonceChangeReason_name = "UnspecifiedGenesisEoACallContractCreatorNewContractAuthorizationRevertSelfdestruct"
|
||||||
|
|
||||||
var _NonceChangeReason_index = [...]uint8{0, 11, 18, 25, 40, 51, 64, 70}
|
var _NonceChangeReason_index = [...]uint8{0, 11, 18, 25, 40, 51, 64, 70, 82}
|
||||||
|
|
||||||
func (i NonceChangeReason) String() string {
|
func (i NonceChangeReason) String() string {
|
||||||
if i >= NonceChangeReason(len(_NonceChangeReason_index)-1) {
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_NonceChangeReason_index)-1 {
|
||||||
return "NonceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
return "NonceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
return _NonceChangeReason_name[_NonceChangeReason_index[i]:_NonceChangeReason_index[i+1]]
|
return _NonceChangeReason_name[_NonceChangeReason_index[idx]:_NonceChangeReason_index[idx+1]]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -432,6 +432,9 @@ const (
|
||||||
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
||||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
||||||
NonceChangeRevert NonceChangeReason = 6
|
NonceChangeRevert NonceChangeReason = 6
|
||||||
|
|
||||||
|
// NonceChangeSelfdestruct is emitted when the nonce is reset to zero due to a self-destruct
|
||||||
|
NonceChangeSelfdestruct NonceChangeReason = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
// CodeChangeReason is used to indicate the reason for a code change.
|
// CodeChangeReason is used to indicate the reason for a code change.
|
||||||
|
|
|
||||||
|
|
@ -2096,6 +2096,11 @@ func (p *BlobPool) Clear() {
|
||||||
p.index = make(map[common.Address][]*blobTxMeta)
|
p.index = make(map[common.Address][]*blobTxMeta)
|
||||||
p.spent = make(map[common.Address]*uint256.Int)
|
p.spent = make(map[common.Address]*uint256.Int)
|
||||||
|
|
||||||
|
// Reset counters and the gapped buffer
|
||||||
|
p.stored = 0
|
||||||
|
p.gapped = make(map[common.Address][]*types.Transaction)
|
||||||
|
p.gappedSource = make(map[common.Hash]common.Address)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load()))
|
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load()))
|
||||||
blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice)
|
blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice)
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,9 @@ var (
|
||||||
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
|
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
|
||||||
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
|
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
|
||||||
|
|
||||||
|
pendingAddrsGauge = metrics.NewRegisteredGauge("txpool/pending/accounts", nil)
|
||||||
|
queuedAddrsGauge = metrics.NewRegisteredGauge("txpool/queued/accounts", nil)
|
||||||
|
|
||||||
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
|
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -148,7 +151,7 @@ type Config struct {
|
||||||
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
|
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
|
||||||
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
|
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
|
||||||
|
|
||||||
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
|
Lifetime time.Duration // Maximum amount of time an account can remain stale in the non-executable pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig contains the default configurations for the transaction pool.
|
// DefaultConfig contains the default configurations for the transaction pool.
|
||||||
|
|
@ -775,7 +778,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
||||||
pool.queueTxEvent(tx)
|
pool.queueTxEvent(tx)
|
||||||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
|
|
||||||
// Successful promotion, bump the heartbeat
|
// Successful replacement. If needed, bump the heartbeat giving more time to queued txs.
|
||||||
pool.queue.bump(from)
|
pool.queue.bump(from)
|
||||||
return old != nil, nil
|
return old != nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -844,6 +847,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
|
||||||
// Try to insert the transaction into the pending queue
|
// Try to insert the transaction into the pending queue
|
||||||
if pool.pending[addr] == nil {
|
if pool.pending[addr] == nil {
|
||||||
pool.pending[addr] = newList(true)
|
pool.pending[addr] = newList(true)
|
||||||
|
pendingAddrsGauge.Inc(1)
|
||||||
}
|
}
|
||||||
list := pool.pending[addr]
|
list := pool.pending[addr]
|
||||||
|
|
||||||
|
|
@ -867,7 +871,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
|
||||||
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
||||||
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
||||||
|
|
||||||
// Successful promotion, bump the heartbeat
|
// Successful promotion, bump the heartbeat, giving more time to queued txs.
|
||||||
pool.queue.bump(addr)
|
pool.queue.bump(addr)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -904,8 +908,8 @@ func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
|
||||||
func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
// Filter out known ones without obtaining the pool lock or recovering signatures
|
// Filter out known ones without obtaining the pool lock or recovering signatures
|
||||||
var (
|
var (
|
||||||
errs = make([]error, len(txs))
|
hasValid bool
|
||||||
news = make([]*types.Transaction, 0, len(txs))
|
errs = make([]error, len(txs))
|
||||||
)
|
)
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
// If the transaction is known, pre-set the error slot
|
// If the transaction is known, pre-set the error slot
|
||||||
|
|
@ -923,26 +927,17 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
invalidTxMeter.Mark(1)
|
invalidTxMeter.Mark(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Accumulate all unknown transactions for deeper processing
|
hasValid = true
|
||||||
news = append(news, tx)
|
|
||||||
}
|
}
|
||||||
if len(news) == 0 {
|
if !hasValid {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process all the new transaction and merge any errors into the original slice
|
// Process all the new transaction and merge any errors into the original slice
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
newErrs, dirtyAddrs := pool.addTxsLocked(news)
|
dirtyAddrs := pool.addTxsLocked(txs, errs)
|
||||||
pool.mu.Unlock()
|
pool.mu.Unlock()
|
||||||
|
|
||||||
var nilSlot = 0
|
|
||||||
for _, err := range newErrs {
|
|
||||||
for errs[nilSlot] != nil {
|
|
||||||
nilSlot++
|
|
||||||
}
|
|
||||||
errs[nilSlot] = err
|
|
||||||
nilSlot++
|
|
||||||
}
|
|
||||||
// Reorg the pool internals if needed and return
|
// Reorg the pool internals if needed and return
|
||||||
done := pool.requestPromoteExecutables(dirtyAddrs)
|
done := pool.requestPromoteExecutables(dirtyAddrs)
|
||||||
if sync {
|
if sync {
|
||||||
|
|
@ -953,14 +948,19 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
|
|
||||||
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
||||||
// The transaction pool lock must be held.
|
// The transaction pool lock must be held.
|
||||||
// Returns the error for each tx, and the set of accounts that might became promotable.
|
// Sets the error for each tx, and the set of accounts that might became promotable.
|
||||||
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) {
|
// We only try to add txs that have no error set in the errs slice.
|
||||||
|
// If adding the transaction returns an error, we set the error in the errs slice.
|
||||||
|
// Requires len(txs) == len(errs).
|
||||||
|
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, errs []error) *accountSet {
|
||||||
var (
|
var (
|
||||||
dirty = newAccountSet(pool.signer)
|
dirty = newAccountSet(pool.signer)
|
||||||
errs = make([]error, len(txs))
|
|
||||||
valid int64
|
valid int64
|
||||||
)
|
)
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
|
if errs[i] != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
replaced, err := pool.add(tx)
|
replaced, err := pool.add(tx)
|
||||||
errs[i] = err
|
errs[i] = err
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -971,7 +971,7 @@ func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accoun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
validTxMeter.Mark(valid)
|
validTxMeter.Mark(valid)
|
||||||
return errs, dirty
|
return dirty
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status returns the status (unknown/pending/queued) of a batch of transactions
|
// Status returns the status (unknown/pending/queued) of a batch of transactions
|
||||||
|
|
@ -1083,6 +1083,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
||||||
// If no more pending transactions are left, remove the list
|
// If no more pending transactions are left, remove the list
|
||||||
if pending.Empty() {
|
if pending.Empty() {
|
||||||
delete(pool.pending, addr)
|
delete(pool.pending, addr)
|
||||||
|
pendingAddrsGauge.Dec(1)
|
||||||
}
|
}
|
||||||
// Postpone any invalidated transactions
|
// Postpone any invalidated transactions
|
||||||
for _, tx := range invalids {
|
for _, tx := range invalids {
|
||||||
|
|
@ -1392,7 +1393,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
|
||||||
// Inject any transactions discarded due to reorgs
|
// Inject any transactions discarded due to reorgs
|
||||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||||
core.SenderCacher().Recover(pool.signer, reinject)
|
core.SenderCacher().Recover(pool.signer, reinject)
|
||||||
pool.addTxsLocked(reinject)
|
pool.addTxsLocked(reinject, make([]error, len(reinject)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// promoteExecutables moves transactions that have become processable from the
|
// promoteExecutables moves transactions that have become processable from the
|
||||||
|
|
@ -1563,6 +1564,7 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
||||||
// Internal shuffle shouldn't touch the lookup set.
|
// Internal shuffle shouldn't touch the lookup set.
|
||||||
pool.enqueueTx(hash, tx, false)
|
pool.enqueueTx(hash, tx, false)
|
||||||
}
|
}
|
||||||
|
pool.priced.Removed(len(olds) + len(drops))
|
||||||
pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
|
pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
|
||||||
|
|
||||||
// If there's a gap in front, alert (should never happen) and postpone all transactions
|
// If there's a gap in front, alert (should never happen) and postpone all transactions
|
||||||
|
|
@ -1580,6 +1582,7 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
||||||
// Delete the entire pending entry if it became empty.
|
// Delete the entire pending entry if it became empty.
|
||||||
if list.Empty() {
|
if list.Empty() {
|
||||||
delete(pool.pending, addr)
|
delete(pool.pending, addr)
|
||||||
|
pendingAddrsGauge.Dec(1)
|
||||||
if _, ok := pool.queue.get(addr); !ok {
|
if _, ok := pool.queue.get(addr); !ok {
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
|
|
@ -1839,6 +1842,13 @@ func (pool *LegacyPool) Clear() {
|
||||||
pool.pending = make(map[common.Address]*list)
|
pool.pending = make(map[common.Address]*list)
|
||||||
pool.queue = newQueue(pool.config, pool.signer)
|
pool.queue = newQueue(pool.config, pool.signer)
|
||||||
pool.pendingNonces = newNoncer(pool.currentState)
|
pool.pendingNonces = newNoncer(pool.currentState)
|
||||||
|
|
||||||
|
// Reset gauges
|
||||||
|
pendingGauge.Update(0)
|
||||||
|
queuedGauge.Update(0)
|
||||||
|
slotsGauge.Update(0)
|
||||||
|
pendingAddrsGauge.Update(0)
|
||||||
|
queuedAddrsGauge.Update(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasPendingAuth returns a flag indicating whether there are pending
|
// HasPendingAuth returns a flag indicating whether there are pending
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,12 @@ func (q *queue) get(addr common.Address) (*list, bool) {
|
||||||
return l, ok
|
return l, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bump updates the heartbeat for the given account address.
|
||||||
|
// If the address is unknown, the call is a no-op.
|
||||||
func (q *queue) bump(addr common.Address) {
|
func (q *queue) bump(addr common.Address) {
|
||||||
q.beats[addr] = time.Now()
|
if _, ok := q.beats[addr]; ok {
|
||||||
|
q.beats[addr] = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *queue) addresses() []common.Address {
|
func (q *queue) addresses() []common.Address {
|
||||||
|
|
@ -114,6 +118,7 @@ func (q *queue) remove(addr common.Address, tx *types.Transaction) {
|
||||||
if future.Empty() {
|
if future.Empty() {
|
||||||
delete(q.queued, addr)
|
delete(q.queued, addr)
|
||||||
delete(q.beats, addr)
|
delete(q.beats, addr)
|
||||||
|
queuedAddrsGauge.Dec(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +128,7 @@ func (q *queue) add(tx *types.Transaction) (*common.Hash, error) {
|
||||||
from, _ := types.Sender(q.signer, tx) // already validated
|
from, _ := types.Sender(q.signer, tx) // already validated
|
||||||
if q.queued[from] == nil {
|
if q.queued[from] == nil {
|
||||||
q.queued[from] = newList(false)
|
q.queued[from] = newList(false)
|
||||||
|
queuedAddrsGauge.Inc(1)
|
||||||
}
|
}
|
||||||
inserted, old := q.queued[from].Add(tx, q.config.PriceBump)
|
inserted, old := q.queued[from].Add(tx, q.config.PriceBump)
|
||||||
if !inserted {
|
if !inserted {
|
||||||
|
|
@ -200,6 +206,7 @@ func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, c
|
||||||
if list.Empty() {
|
if list.Empty() {
|
||||||
delete(q.queued, addr)
|
delete(q.queued, addr)
|
||||||
delete(q.beats, addr)
|
delete(q.beats, addr)
|
||||||
|
queuedAddrsGauge.Dec(1)
|
||||||
removedAddresses = append(removedAddresses, addr)
|
removedAddresses = append(removedAddresses, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -424,3 +424,33 @@ func EncodeBlockReceiptLists(receipts []Receipts) []rlp.RawValue {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SlimReceipt is a wrapper around a Receipt with RLP serialization that omits
|
||||||
|
// the Bloom field and includes the tx type. Used for era files.
|
||||||
|
type SlimReceipt Receipt
|
||||||
|
|
||||||
|
type slimReceiptRLP struct {
|
||||||
|
Type uint8
|
||||||
|
StatusEncoding []byte
|
||||||
|
CumulativeGasUsed uint64
|
||||||
|
Logs []*Log
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRLP implements rlp.Encoder, encoding the receipt as
|
||||||
|
// [tx-type, post-state-or-status, cumulative-gas, logs].
|
||||||
|
func (r *SlimReceipt) EncodeRLP(w io.Writer) error {
|
||||||
|
data := &slimReceiptRLP{r.Type, (*Receipt)(r).statusEncoding(), r.CumulativeGasUsed, r.Logs}
|
||||||
|
return rlp.Encode(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRLP implements rlp.Decoder.
|
||||||
|
func (r *SlimReceipt) DecodeRLP(s *rlp.Stream) error {
|
||||||
|
var data slimReceiptRLP
|
||||||
|
if err := s.Decode(&data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Type = data.Type
|
||||||
|
r.CumulativeGasUsed = data.CumulativeGasUsed
|
||||||
|
r.Logs = data.Logs
|
||||||
|
return (*Receipt)(r).setStatus(data.StatusEncoding)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -512,6 +512,45 @@ func TestReceiptUnmarshalBinary(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSlimReceiptEncodingDecoding(t *testing.T) {
|
||||||
|
tests := []*Receipt{
|
||||||
|
legacyReceipt,
|
||||||
|
accessListReceipt,
|
||||||
|
eip1559Receipt,
|
||||||
|
{
|
||||||
|
Type: BlobTxType,
|
||||||
|
Status: ReceiptStatusSuccessful,
|
||||||
|
CumulativeGasUsed: 100,
|
||||||
|
Logs: []*Log{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, want := range tests {
|
||||||
|
enc, err := rlp.EncodeToBytes((*SlimReceipt)(want))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test %d: encode error: %v", i, err)
|
||||||
|
}
|
||||||
|
got := new(SlimReceipt)
|
||||||
|
if err := rlp.DecodeBytes(enc, got); err != nil {
|
||||||
|
t.Fatalf("test %d: decode error: %v", i, err)
|
||||||
|
}
|
||||||
|
if got.Type != want.Type {
|
||||||
|
t.Errorf("test %d: Type mismatch: got %d, want %d", i, got.Type, want.Type)
|
||||||
|
}
|
||||||
|
if got.Status != want.Status {
|
||||||
|
t.Errorf("test %d: Status mismatch: got %d, want %d", i, got.Status, want.Status)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got.PostState, want.PostState) {
|
||||||
|
t.Errorf("test %d: PostState mismatch: got %x, want %x", i, got.PostState, want.PostState)
|
||||||
|
}
|
||||||
|
if got.CumulativeGasUsed != want.CumulativeGasUsed {
|
||||||
|
t.Errorf("test %d: CumulativeGasUsed mismatch: got %d, want %d", i, got.CumulativeGasUsed, want.CumulativeGasUsed)
|
||||||
|
}
|
||||||
|
if len(got.Logs) != len(want.Logs) {
|
||||||
|
t.Errorf("test %d: Logs length mismatch: got %d, want %d", i, len(got.Logs), len(want.Logs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt {
|
func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt {
|
||||||
r := make([]*Receipt, len(receipts))
|
r := make([]*Receipt, len(receipts))
|
||||||
for i, receipt := range receipts {
|
for i, receipt := range receipts {
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,10 @@ func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
|
||||||
if tx.inner.chainID().Sign() != 0 && tx.inner.chainID().Cmp(s.chainID) != 0 {
|
if tx.inner.chainID().Sign() != 0 && tx.inner.chainID().Cmp(s.chainID) != 0 {
|
||||||
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.inner.chainID(), s.chainID)
|
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.inner.chainID(), s.chainID)
|
||||||
}
|
}
|
||||||
R, S, _ = decodeSignature(sig)
|
R, S, _, err = decodeSignature(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
V = big.NewInt(int64(sig[64]))
|
V = big.NewInt(int64(sig[64]))
|
||||||
return R, S, V, nil
|
return R, S, V, nil
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +330,7 @@ func NewEIP2930Signer(chainId *big.Int) Signer {
|
||||||
// are replay-protected as well as unprotected homestead transactions.
|
// are replay-protected as well as unprotected homestead transactions.
|
||||||
// Deprecated: always use the Signer interface type
|
// Deprecated: always use the Signer interface type
|
||||||
type EIP155Signer struct {
|
type EIP155Signer struct {
|
||||||
chainId, chainIdMul *big.Int
|
chainId *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEIP155Signer(chainId *big.Int) EIP155Signer {
|
func NewEIP155Signer(chainId *big.Int) EIP155Signer {
|
||||||
|
|
@ -335,8 +338,7 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer {
|
||||||
chainId = new(big.Int)
|
chainId = new(big.Int)
|
||||||
}
|
}
|
||||||
return EIP155Signer{
|
return EIP155Signer{
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -362,7 +364,8 @@ func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
|
||||||
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
|
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
|
||||||
}
|
}
|
||||||
V, R, S := tx.RawSignatureValues()
|
V, R, S := tx.RawSignatureValues()
|
||||||
V = new(big.Int).Sub(V, s.chainIdMul)
|
V = new(big.Int).Sub(V, s.chainId)
|
||||||
|
V = new(big.Int).Sub(V, s.chainId)
|
||||||
V.Sub(V, big8)
|
V.Sub(V, big8)
|
||||||
return recoverPlain(s.Hash(tx), R, S, V, true)
|
return recoverPlain(s.Hash(tx), R, S, V, true)
|
||||||
}
|
}
|
||||||
|
|
@ -373,10 +376,14 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
|
||||||
if tx.Type() != LegacyTxType {
|
if tx.Type() != LegacyTxType {
|
||||||
return nil, nil, nil, ErrTxTypeNotSupported
|
return nil, nil, nil, ErrTxTypeNotSupported
|
||||||
}
|
}
|
||||||
R, S, V = decodeSignature(sig)
|
R, S, V, err = decodeSignature(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
if s.chainId.Sign() != 0 {
|
if s.chainId.Sign() != 0 {
|
||||||
V = big.NewInt(int64(sig[64] + 35))
|
V = big.NewInt(int64(sig[64] + 35))
|
||||||
V.Add(V, s.chainIdMul)
|
V.Add(V, s.chainId)
|
||||||
|
V.Add(V, s.chainId)
|
||||||
}
|
}
|
||||||
return R, S, V, nil
|
return R, S, V, nil
|
||||||
}
|
}
|
||||||
|
|
@ -442,8 +449,8 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *
|
||||||
if tx.Type() != LegacyTxType {
|
if tx.Type() != LegacyTxType {
|
||||||
return nil, nil, nil, ErrTxTypeNotSupported
|
return nil, nil, nil, ErrTxTypeNotSupported
|
||||||
}
|
}
|
||||||
r, s, v = decodeSignature(sig)
|
r, s, v, err = decodeSignature(sig)
|
||||||
return r, s, v, nil
|
return r, s, v, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash to be signed by the sender.
|
// Hash returns the hash to be signed by the sender.
|
||||||
|
|
@ -459,14 +466,14 @@ func (fs FrontierSigner) Hash(tx *Transaction) common.Hash {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeSignature(sig []byte) (r, s, v *big.Int) {
|
func decodeSignature(sig []byte) (r, s, v *big.Int, err error) {
|
||||||
if len(sig) != crypto.SignatureLength {
|
if len(sig) != crypto.SignatureLength {
|
||||||
panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength))
|
return nil, nil, nil, fmt.Errorf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)
|
||||||
}
|
}
|
||||||
r = new(big.Int).SetBytes(sig[:32])
|
r = new(big.Int).SetBytes(sig[:32])
|
||||||
s = new(big.Int).SetBytes(sig[32:64])
|
s = new(big.Int).SetBytes(sig[32:64])
|
||||||
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
||||||
return r, s, v
|
return r, s, v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) {
|
func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) {
|
||||||
|
|
|
||||||
|
|
@ -200,3 +200,28 @@ func Benchmark_modernSigner_Equal(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSignatureValuesError(t *testing.T) {
|
||||||
|
// 1. Setup a valid transaction
|
||||||
|
tx := NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||||
|
signer := HomesteadSigner{}
|
||||||
|
|
||||||
|
// 2. Call WithSignature with invalid length sig (not 65 bytes)
|
||||||
|
invalidSig := make([]byte, 64)
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Fatalf("Panicked for invalid signature length, expected error: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err := tx.WithSignature(signer, invalidSig)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error for invalid signature length, got nil")
|
||||||
|
} else {
|
||||||
|
// This is just a sanity check to ensure we got an error,
|
||||||
|
// the exact error message is verified in unit tests elsewhere if needed.
|
||||||
|
t.Logf("Got expected error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,8 @@ func (sc *BlobTxSidecar) ToV1() error {
|
||||||
}
|
}
|
||||||
if sc.Version == BlobSidecarVersion0 {
|
if sc.Version == BlobSidecarVersion0 {
|
||||||
proofs := make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob)
|
proofs := make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob)
|
||||||
for _, blob := range sc.Blobs {
|
for i := range sc.Blobs {
|
||||||
cellProofs, err := kzg4844.ComputeCellProofs(&blob)
|
cellProofs, err := kzg4844.ComputeCellProofs(&sc.Blobs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,10 @@ func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAutho
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SetCodeAuthorization{}, err
|
return SetCodeAuthorization{}, err
|
||||||
}
|
}
|
||||||
r, s, _ := decodeSignature(sig)
|
r, s, _, err := decodeSignature(sig)
|
||||||
|
if err != nil {
|
||||||
|
return SetCodeAuthorization{}, err
|
||||||
|
}
|
||||||
return SetCodeAuthorization{
|
return SetCodeAuthorization{
|
||||||
ChainID: auth.ChainID,
|
ChainID: auth.ChainID,
|
||||||
Address: auth.Address,
|
Address: auth.Address,
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,8 @@ type BlockContext struct {
|
||||||
type TxContext struct {
|
type TxContext struct {
|
||||||
// Message information
|
// Message information
|
||||||
Origin common.Address // Provides information for ORIGIN
|
Origin common.Address // Provides information for ORIGIN
|
||||||
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
|
GasPrice *uint256.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
|
||||||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||||
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
|
||||||
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,9 +124,6 @@ type EVM struct {
|
||||||
// jumpDests stores results of JUMPDEST analysis.
|
// jumpDests stores results of JUMPDEST analysis.
|
||||||
jumpDests JumpDestCache
|
jumpDests JumpDestCache
|
||||||
|
|
||||||
hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
|
|
||||||
hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes
|
|
||||||
|
|
||||||
readOnly bool // Whether to throw on stateful modifications
|
readOnly bool // Whether to throw on stateful modifications
|
||||||
returnData []byte // Last CALL's return data for subsequent reuse
|
returnData []byte // Last CALL's return data for subsequent reuse
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +140,6 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
|
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
|
||||||
jumpDests: newMapJumpDests(),
|
jumpDests: newMapJumpDests(),
|
||||||
hasher: crypto.NewKeccakState(),
|
|
||||||
}
|
}
|
||||||
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||||
|
|
||||||
|
|
@ -618,7 +613,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui
|
||||||
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
|
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
|
||||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||||
func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
inithash := crypto.HashData(evm.hasher, code)
|
inithash := crypto.Keccak256Hash(code)
|
||||||
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
|
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
|
||||||
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
|
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
y, x = stack.Back(1), stack.Back(0)
|
y, x = stack.Back(1), stack.Back(0)
|
||||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
|
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
|
||||||
|
|
@ -181,6 +184,9 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
|
||||||
// (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
|
// (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
|
||||||
// (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
|
// (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
|
||||||
func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
// If we fail the minimum gas availability invariant, fail (0)
|
// If we fail the minimum gas availability invariant, fail (0)
|
||||||
if contract.Gas <= params.SstoreSentryGasEIP2200 {
|
if contract.Gas <= params.SstoreSentryGasEIP2200 {
|
||||||
return 0, errors.New("not enough gas for reentrancy sentry")
|
return 0, errors.New("not enough gas for reentrancy sentry")
|
||||||
|
|
@ -374,6 +380,10 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||||
transfersValue = !stack.Back(2).IsZero()
|
transfersValue = !stack.Back(2).IsZero()
|
||||||
address = common.Address(stack.Back(1).Bytes20())
|
address = common.Address(stack.Back(1).Bytes20())
|
||||||
)
|
)
|
||||||
|
if evm.readOnly && transfersValue {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
|
|
||||||
if evm.chainRules.IsEIP158 {
|
if evm.chainRules.IsEIP158 {
|
||||||
if transfersValue && evm.StateDB.Empty(address) {
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
gas += params.CallNewAccountGas
|
gas += params.CallNewAccountGas
|
||||||
|
|
@ -462,6 +472,10 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
|
|
||||||
var gas uint64
|
var gas uint64
|
||||||
// EIP150 homestead gas reprice fork:
|
// EIP150 homestead gas reprice fork:
|
||||||
if evm.chainRules.IsEIP150 {
|
if evm.chainRules.IsEIP150 {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -233,14 +234,12 @@ func opKeccak256(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
offset, size := scope.Stack.pop(), scope.Stack.peek()
|
offset, size := scope.Stack.pop(), scope.Stack.peek()
|
||||||
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
|
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
|
||||||
|
|
||||||
evm.hasher.Reset()
|
hash := crypto.Keccak256Hash(data)
|
||||||
evm.hasher.Write(data)
|
|
||||||
evm.hasher.Read(evm.hasherBuf[:])
|
|
||||||
|
|
||||||
if evm.Config.EnablePreimageRecording {
|
if evm.Config.EnablePreimageRecording {
|
||||||
evm.StateDB.AddPreimage(evm.hasherBuf, data)
|
evm.StateDB.AddPreimage(hash, data)
|
||||||
}
|
}
|
||||||
size.SetBytes(evm.hasherBuf[:])
|
size.SetBytes(hash[:])
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,8 +416,7 @@ func opExtCodeHash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
v, _ := uint256.FromBig(evm.GasPrice)
|
scope.Stack.push(evm.GasPrice.Clone())
|
||||||
scope.Stack.push(v)
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -885,13 +883,24 @@ func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.readOnly {
|
if evm.readOnly {
|
||||||
return nil, ErrWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
beneficiary := scope.Stack.pop()
|
var (
|
||||||
balance := evm.StateDB.GetBalance(scope.Contract.Address())
|
this = scope.Contract.Address()
|
||||||
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
balance = evm.StateDB.GetBalance(this)
|
||||||
evm.StateDB.SelfDestruct(scope.Contract.Address())
|
top = scope.Stack.pop()
|
||||||
|
beneficiary = common.Address(top.Bytes20())
|
||||||
|
)
|
||||||
|
// The funds are burned immediately if the beneficiary is the caller itself,
|
||||||
|
// in this case, the beneficiary's balance is not increased.
|
||||||
|
if this != beneficiary {
|
||||||
|
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
|
}
|
||||||
|
// Clear any leftover funds for the account being destructed.
|
||||||
|
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||||
|
evm.StateDB.SelfDestruct(this)
|
||||||
|
|
||||||
if tracer := evm.Config.Tracer; tracer != nil {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
if tracer.OnEnter != nil {
|
if tracer.OnEnter != nil {
|
||||||
tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig())
|
tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), this, beneficiary, []byte{}, 0, balance.ToBig())
|
||||||
}
|
}
|
||||||
if tracer.OnExit != nil {
|
if tracer.OnExit != nil {
|
||||||
tracer.OnExit(evm.depth, []byte{}, 0, nil, false)
|
tracer.OnExit(evm.depth, []byte{}, 0, nil, false)
|
||||||
|
|
@ -904,14 +913,31 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
||||||
if evm.readOnly {
|
if evm.readOnly {
|
||||||
return nil, ErrWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
beneficiary := scope.Stack.pop()
|
var (
|
||||||
balance := evm.StateDB.GetBalance(scope.Contract.Address())
|
this = scope.Contract.Address()
|
||||||
evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
|
balance = evm.StateDB.GetBalance(this)
|
||||||
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
top = scope.Stack.pop()
|
||||||
evm.StateDB.SelfDestruct6780(scope.Contract.Address())
|
beneficiary = common.Address(top.Bytes20())
|
||||||
|
newContract = evm.StateDB.IsNewContract(this)
|
||||||
|
)
|
||||||
|
// Contract is new and will actually be deleted.
|
||||||
|
if newContract {
|
||||||
|
if this != beneficiary { // Skip no-op transfer when self-destructing to self.
|
||||||
|
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
|
}
|
||||||
|
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||||
|
evm.StateDB.SelfDestruct(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract already exists, only do transfer if beneficiary is not self.
|
||||||
|
if !newContract && this != beneficiary {
|
||||||
|
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||||
|
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
|
}
|
||||||
|
|
||||||
if tracer := evm.Config.Tracer; tracer != nil {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
if tracer.OnEnter != nil {
|
if tracer.OnEnter != nil {
|
||||||
tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig())
|
tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), this, beneficiary, []byte{}, 0, balance.ToBig())
|
||||||
}
|
}
|
||||||
if tracer.OnExit != nil {
|
if tracer.OnExit != nil {
|
||||||
tracer.OnExit(evm.depth, []byte{}, 0, nil, false)
|
tracer.OnExit(evm.depth, []byte{}, 0, nil, false)
|
||||||
|
|
@ -945,11 +971,11 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
code := scope.Contract.Code
|
code := scope.Contract.Code
|
||||||
i := *pc + 1
|
i := *pc + 1
|
||||||
|
|
||||||
// Ensure an immediate byte exists after DUPN
|
// If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
|
||||||
if i >= uint64(len(code)) {
|
var x byte
|
||||||
return nil, &ErrInvalidOpCode{opcode: INVALID}
|
if i < uint64(len(code)) {
|
||||||
|
x = code[i]
|
||||||
}
|
}
|
||||||
x := code[i]
|
|
||||||
|
|
||||||
// This range is excluded to preserve compatibility with existing opcodes.
|
// This range is excluded to preserve compatibility with existing opcodes.
|
||||||
if x > 90 && x < 128 {
|
if x > 90 && x < 128 {
|
||||||
|
|
@ -972,11 +998,11 @@ func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
code := scope.Contract.Code
|
code := scope.Contract.Code
|
||||||
i := *pc + 1
|
i := *pc + 1
|
||||||
|
|
||||||
// Ensure an immediate byte exists after SWAPN
|
// If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
|
||||||
if i >= uint64(len(code)) {
|
var x byte
|
||||||
return nil, &ErrInvalidOpCode{opcode: INVALID}
|
if i < uint64(len(code)) {
|
||||||
|
x = code[i]
|
||||||
}
|
}
|
||||||
x := code[i]
|
|
||||||
|
|
||||||
// This range is excluded to preserve compatibility with existing opcodes.
|
// This range is excluded to preserve compatibility with existing opcodes.
|
||||||
if x > 90 && x < 128 {
|
if x > 90 && x < 128 {
|
||||||
|
|
@ -1001,11 +1027,11 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
code := scope.Contract.Code
|
code := scope.Contract.Code
|
||||||
i := *pc + 1
|
i := *pc + 1
|
||||||
|
|
||||||
// Ensure an immediate byte exists after EXCHANGE
|
// If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
|
||||||
if i >= uint64(len(code)) {
|
var x byte
|
||||||
return nil, &ErrInvalidOpCode{opcode: INVALID}
|
if i < uint64(len(code)) {
|
||||||
|
x = code[i]
|
||||||
}
|
}
|
||||||
x := code[i]
|
|
||||||
|
|
||||||
// This range is excluded both to preserve compatibility with existing opcodes
|
// This range is excluded both to preserve compatibility with existing opcodes
|
||||||
// and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–79, 128–255).
|
// and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–79, 128–255).
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package vm
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -1013,10 +1014,11 @@ func TestEIP8024_Execution(t *testing.T) {
|
||||||
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
|
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
codeHex string
|
codeHex string
|
||||||
wantErr bool
|
wantErr error
|
||||||
wantVals []uint64
|
wantOpcode OpCode
|
||||||
|
wantVals []uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "DUPN",
|
name: "DUPN",
|
||||||
|
|
@ -1027,6 +1029,15 @@ func TestEIP8024_Execution(t *testing.T) {
|
||||||
1,
|
1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "DUPN_MISSING_IMMEDIATE",
|
||||||
|
codeHex: "60016000808080808080808080808080808080e6",
|
||||||
|
wantVals: []uint64{
|
||||||
|
1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "SWAPN",
|
name: "SWAPN",
|
||||||
codeHex: "600160008080808080808080808080808080806002e700",
|
codeHex: "600160008080808080808080808080808080806002e700",
|
||||||
|
|
@ -1036,76 +1047,94 @@ func TestEIP8024_Execution(t *testing.T) {
|
||||||
2,
|
2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "SWAPN_MISSING_IMMEDIATE",
|
||||||
|
codeHex: "600160008080808080808080808080808080806002e7",
|
||||||
|
wantVals: []uint64{
|
||||||
|
1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "EXCHANGE",
|
name: "EXCHANGE",
|
||||||
codeHex: "600060016002e801",
|
codeHex: "600060016002e801",
|
||||||
wantVals: []uint64{2, 0, 1},
|
wantVals: []uint64{2, 0, 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "INVALID_SWAPN_LOW",
|
name: "EXCHANGE_MISSING_IMMEDIATE",
|
||||||
codeHex: "e75b",
|
codeHex: "600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060016002e8",
|
||||||
wantErr: true,
|
wantVals: []uint64{
|
||||||
|
2,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "INVALID_SWAPN_LOW",
|
||||||
|
codeHex: "e75b",
|
||||||
|
wantErr: &ErrInvalidOpCode{},
|
||||||
|
wantOpcode: SWAPN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "JUMP over INVALID_DUPN",
|
name: "JUMP over INVALID_DUPN",
|
||||||
codeHex: "600456e65b",
|
codeHex: "600456e65b",
|
||||||
wantErr: false,
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UNDERFLOW_DUPN_1",
|
||||||
|
codeHex: "6000808080808080808080808080808080e600",
|
||||||
|
wantErr: &ErrStackUnderflow{},
|
||||||
|
wantOpcode: DUPN,
|
||||||
},
|
},
|
||||||
// Additional test cases
|
// Additional test cases
|
||||||
{
|
{
|
||||||
name: "INVALID_DUPN_LOW",
|
name: "INVALID_DUPN_LOW",
|
||||||
codeHex: "e65b",
|
codeHex: "e65b",
|
||||||
wantErr: true,
|
wantErr: &ErrInvalidOpCode{},
|
||||||
|
wantOpcode: DUPN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "INVALID_EXCHANGE_LOW",
|
name: "INVALID_EXCHANGE_LOW",
|
||||||
codeHex: "e850",
|
codeHex: "e850",
|
||||||
wantErr: true,
|
wantErr: &ErrInvalidOpCode{},
|
||||||
|
wantOpcode: EXCHANGE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "INVALID_DUPN_HIGH",
|
name: "INVALID_DUPN_HIGH",
|
||||||
codeHex: "e67f",
|
codeHex: "e67f",
|
||||||
wantErr: true,
|
wantErr: &ErrInvalidOpCode{},
|
||||||
|
wantOpcode: DUPN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "INVALID_SWAPN_HIGH",
|
name: "INVALID_SWAPN_HIGH",
|
||||||
codeHex: "e77f",
|
codeHex: "e77f",
|
||||||
wantErr: true,
|
wantErr: &ErrInvalidOpCode{},
|
||||||
|
wantOpcode: SWAPN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "INVALID_EXCHANGE_HIGH",
|
name: "INVALID_EXCHANGE_HIGH",
|
||||||
codeHex: "e87f",
|
codeHex: "e87f",
|
||||||
wantErr: true,
|
wantErr: &ErrInvalidOpCode{},
|
||||||
|
wantOpcode: EXCHANGE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UNDERFLOW_DUPN",
|
name: "UNDERFLOW_DUPN_2",
|
||||||
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
|
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
|
||||||
wantErr: true,
|
wantErr: &ErrStackUnderflow{},
|
||||||
|
wantOpcode: DUPN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UNDERFLOW_SWAPN",
|
name: "UNDERFLOW_SWAPN",
|
||||||
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
|
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
|
||||||
wantErr: true,
|
wantErr: &ErrStackUnderflow{},
|
||||||
|
wantOpcode: SWAPN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UNDERFLOW_EXCHANGE",
|
name: "UNDERFLOW_EXCHANGE",
|
||||||
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
|
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
|
||||||
wantErr: true,
|
wantErr: &ErrStackUnderflow{},
|
||||||
},
|
wantOpcode: EXCHANGE,
|
||||||
{
|
|
||||||
name: "MISSING_IMMEDIATE_DUPN",
|
|
||||||
codeHex: "e6", // no operand
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MISSING_IMMEDIATE_SWAPN",
|
|
||||||
codeHex: "e7", // no operand
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MISSING_IMMEDIATE_EXCHANGE",
|
|
||||||
codeHex: "e8", // no operand
|
|
||||||
wantErr: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PC_INCREMENT",
|
name: "PC_INCREMENT",
|
||||||
|
|
@ -1121,37 +1150,63 @@ func TestEIP8024_Execution(t *testing.T) {
|
||||||
pc := uint64(0)
|
pc := uint64(0)
|
||||||
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
|
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
|
||||||
var err error
|
var err error
|
||||||
|
var errOp OpCode
|
||||||
for pc < uint64(len(code)) && err == nil {
|
for pc < uint64(len(code)) && err == nil {
|
||||||
op := code[pc]
|
op := code[pc]
|
||||||
switch op {
|
switch OpCode(op) {
|
||||||
case 0x00:
|
case STOP:
|
||||||
return
|
return
|
||||||
case 0x60:
|
case PUSH1:
|
||||||
_, err = opPush1(&pc, evm, scope)
|
_, err = opPush1(&pc, evm, scope)
|
||||||
case 0x80:
|
case DUP1:
|
||||||
dup1 := makeDup(1)
|
dup1 := makeDup(1)
|
||||||
_, err = dup1(&pc, evm, scope)
|
_, err = dup1(&pc, evm, scope)
|
||||||
case 0x56:
|
case JUMP:
|
||||||
_, err = opJump(&pc, evm, scope)
|
_, err = opJump(&pc, evm, scope)
|
||||||
case 0x5b:
|
case JUMPDEST:
|
||||||
_, err = opJumpdest(&pc, evm, scope)
|
_, err = opJumpdest(&pc, evm, scope)
|
||||||
case 0x15:
|
case ISZERO:
|
||||||
_, err = opIszero(&pc, evm, scope)
|
_, err = opIszero(&pc, evm, scope)
|
||||||
case 0xe6:
|
case PUSH0:
|
||||||
|
_, err = opPush0(&pc, evm, scope)
|
||||||
|
case DUPN:
|
||||||
_, err = opDupN(&pc, evm, scope)
|
_, err = opDupN(&pc, evm, scope)
|
||||||
case 0xe7:
|
case SWAPN:
|
||||||
_, err = opSwapN(&pc, evm, scope)
|
_, err = opSwapN(&pc, evm, scope)
|
||||||
case 0xe8:
|
case EXCHANGE:
|
||||||
_, err = opExchange(&pc, evm, scope)
|
_, err = opExchange(&pc, evm, scope)
|
||||||
default:
|
default:
|
||||||
err = &ErrInvalidOpCode{opcode: OpCode(op)}
|
t.Fatalf("unexpected opcode %s at pc=%d", OpCode(op), pc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errOp = OpCode(op)
|
||||||
}
|
}
|
||||||
pc++
|
pc++
|
||||||
}
|
}
|
||||||
if tc.wantErr {
|
if tc.wantErr != nil {
|
||||||
|
// Fail because we wanted an error, but didn't get one.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error, got nil")
|
t.Fatalf("expected error, got nil")
|
||||||
}
|
}
|
||||||
|
// Fail if the wrong opcode threw an error.
|
||||||
|
if errOp != tc.wantOpcode {
|
||||||
|
t.Fatalf("expected error from opcode %s, got %s", tc.wantOpcode, errOp)
|
||||||
|
}
|
||||||
|
// Fail if we don't get the error we expect.
|
||||||
|
switch tc.wantErr.(type) {
|
||||||
|
case *ErrInvalidOpCode:
|
||||||
|
var want *ErrInvalidOpCode
|
||||||
|
if !errors.As(err, &want) {
|
||||||
|
t.Fatalf("expected ErrInvalidOpCode, got %v", err)
|
||||||
|
}
|
||||||
|
case *ErrStackUnderflow:
|
||||||
|
var want *ErrStackUnderflow
|
||||||
|
if !errors.As(err, &want) {
|
||||||
|
t.Fatalf("expected ErrStackUnderflow, got %v", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported wantErr type %T", tc.wantErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -57,19 +57,17 @@ type StateDB interface {
|
||||||
GetTransientState(addr common.Address, key common.Hash) common.Hash
|
GetTransientState(addr common.Address, key common.Hash) common.Hash
|
||||||
SetTransientState(addr common.Address, key, value common.Hash)
|
SetTransientState(addr common.Address, key, value common.Hash)
|
||||||
|
|
||||||
SelfDestruct(common.Address) uint256.Int
|
SelfDestruct(common.Address)
|
||||||
HasSelfDestructed(common.Address) bool
|
HasSelfDestructed(common.Address) bool
|
||||||
|
|
||||||
// SelfDestruct6780 is post-EIP6780 selfdestruct, which means that it's a
|
|
||||||
// send-all-to-beneficiary, unless the contract was created in this same
|
|
||||||
// transaction, in which case it will be destructed.
|
|
||||||
// This method returns the prior balance, along with a boolean which is
|
|
||||||
// true iff the object was indeed destructed.
|
|
||||||
SelfDestruct6780(common.Address) (uint256.Int, bool)
|
|
||||||
|
|
||||||
// Exist reports whether the given account exists in state.
|
// Exist reports whether the given account exists in state.
|
||||||
// Notably this also returns true for self-destructed accounts within the current transaction.
|
// Notably this also returns true for self-destructed accounts within the current transaction.
|
||||||
Exist(common.Address) bool
|
Exist(common.Address) bool
|
||||||
|
|
||||||
|
// IsNewContract reports whether the contract at the given address was deployed
|
||||||
|
// during the current transaction.
|
||||||
|
IsNewContract(addr common.Address) bool
|
||||||
|
|
||||||
// Empty returns whether the given account is empty. Empty
|
// Empty returns whether the given account is empty. Empty
|
||||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||||
Empty(common.Address) bool
|
Empty(common.Address) bool
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ import (
|
||||||
|
|
||||||
func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
|
func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
// If we fail the minimum gas availability invariant, fail (0)
|
// If we fail the minimum gas availability invariant, fail (0)
|
||||||
if contract.Gas <= params.SstoreSentryGasEIP2200 {
|
if contract.Gas <= params.SstoreSentryGasEIP2200 {
|
||||||
return 0, errors.New("not enough gas for reentrancy sentry")
|
return 0, errors.New("not enough gas for reentrancy sentry")
|
||||||
|
|
@ -226,10 +229,19 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
||||||
gas uint64
|
gas uint64
|
||||||
address = common.Address(stack.peek().Bytes20())
|
address = common.Address(stack.peek().Bytes20())
|
||||||
)
|
)
|
||||||
|
if evm.readOnly {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
if !evm.StateDB.AddressInAccessList(address) {
|
if !evm.StateDB.AddressInAccessList(address) {
|
||||||
// If the caller cannot afford the cost, this change will be rolled back
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
evm.StateDB.AddAddressToAccessList(address)
|
evm.StateDB.AddAddressToAccessList(address)
|
||||||
gas = params.ColdAccountAccessCostEIP2929
|
gas = params.ColdAccountAccessCostEIP2929
|
||||||
|
|
||||||
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
|
// it can effectively prevent accessing the states in the following steps
|
||||||
|
if contract.Gas < gas {
|
||||||
|
return 0, ErrOutOfGas
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if empty and transfers value
|
// if empty and transfers value
|
||||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||||
|
|
@ -244,12 +256,24 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
|
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
|
||||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
|
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
|
||||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
|
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
|
||||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
|
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
// Return early if this call attempts to transfer value in a static context.
|
||||||
|
// Although it's checked in `gasCall`, EIP-7702 loads the target's code before
|
||||||
|
// to determine if it is resolving a delegation. This could incorrectly record
|
||||||
|
// the target in the block access list (BAL) if the call later fails.
|
||||||
|
transfersValue := !stack.Back(2).IsZero()
|
||||||
|
if evm.readOnly && transfersValue {
|
||||||
|
return 0, ErrWriteProtection
|
||||||
|
}
|
||||||
|
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||||
|
}
|
||||||
|
|
||||||
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ package runtime
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEnv(cfg *Config) *vm.EVM {
|
func NewEnv(cfg *Config) *vm.EVM {
|
||||||
txContext := vm.TxContext{
|
txContext := vm.TxContext{
|
||||||
Origin: cfg.Origin,
|
Origin: cfg.Origin,
|
||||||
GasPrice: cfg.GasPrice,
|
GasPrice: uint256.MustFromBig(cfg.GasPrice),
|
||||||
BlobHashes: cfg.BlobHashes,
|
BlobHashes: cfg.BlobHashes,
|
||||||
BlobFeeCap: cfg.BlobFeeCap,
|
|
||||||
}
|
}
|
||||||
blockContext := vm.BlockContext{
|
blockContext := vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,9 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b
|
||||||
if prv.PublicKey.Curve != pub.Curve {
|
if prv.PublicKey.Curve != pub.Curve {
|
||||||
return nil, ErrInvalidCurve
|
return nil, ErrInvalidCurve
|
||||||
}
|
}
|
||||||
|
if pub.X == nil || pub.Y == nil || !pub.Curve.IsOnCurve(pub.X, pub.Y) {
|
||||||
|
return nil, ErrInvalidPublicKey
|
||||||
|
}
|
||||||
if skLen+macLen > MaxSharedKeyLength(pub) {
|
if skLen+macLen > MaxSharedKeyLength(pub) {
|
||||||
return nil, ErrSharedKeyTooBig
|
return nil, ErrSharedKeyTooBig
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,17 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"golang.org/x/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewKeccakState creates a new KeccakState
|
// NewKeccakState creates a new KeccakState
|
||||||
func NewKeccakState() KeccakState {
|
func NewKeccakState() KeccakState {
|
||||||
return sha3.NewLegacyKeccak256().(KeccakState)
|
return keccak.NewLegacyKeccak256().(KeccakState)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasherPool = sync.Pool{
|
var hasherPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
return sha3.NewLegacyKeccak256().(KeccakState)
|
return keccak.NewLegacyKeccak256().(KeccakState)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
27
crypto/keccak/LICENSE
Normal file
27
crypto/keccak/LICENSE
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
6
crypto/keccak/README.md
Normal file
6
crypto/keccak/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
This is a vendored and modified copy of golang.org/x/crypto/sha3, with an assembly
|
||||||
|
implementation of keccak256. We wish to retain the assembly implementation,
|
||||||
|
which was removed in v0.44.0.
|
||||||
|
|
||||||
|
Ethereum uses a 'legacy' variant of Keccak, which was defined before it became SHA3. As
|
||||||
|
such, we cannot use the standard library crypto/sha3 package.
|
||||||
44
crypto/keccak/hashes.go
Normal file
44
crypto/keccak/hashes.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package keccak
|
||||||
|
|
||||||
|
// This file provides functions for creating instances of the SHA-3
|
||||||
|
// and SHAKE hash functions, as well as utility functions for hashing
|
||||||
|
// bytes.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dsbyteSHA3 = 0b00000110
|
||||||
|
dsbyteKeccak = 0b00000001
|
||||||
|
dsbyteShake = 0b00011111
|
||||||
|
dsbyteCShake = 0b00000100
|
||||||
|
|
||||||
|
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
|
||||||
|
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
|
||||||
|
rateK256 = (1600 - 256) / 8
|
||||||
|
rateK448 = (1600 - 448) / 8
|
||||||
|
rateK512 = (1600 - 512) / 8
|
||||||
|
rateK768 = (1600 - 768) / 8
|
||||||
|
rateK1024 = (1600 - 1024) / 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLegacyKeccak256 creates a new Keccak-256 hash.
|
||||||
|
//
|
||||||
|
// Only use this function if you require compatibility with an existing cryptosystem
|
||||||
|
// that uses non-standard padding. All other users should use New256 instead.
|
||||||
|
func NewLegacyKeccak256() hash.Hash {
|
||||||
|
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLegacyKeccak512 creates a new Keccak-512 hash.
|
||||||
|
//
|
||||||
|
// Only use this function if you require compatibility with an existing cryptosystem
|
||||||
|
// that uses non-standard padding. All other users should use New512 instead.
|
||||||
|
func NewLegacyKeccak512() hash.Hash {
|
||||||
|
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
|
||||||
|
}
|
||||||
414
crypto/keccak/keccakf.go
Normal file
414
crypto/keccak/keccakf.go
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !amd64 || purego || !gc
|
||||||
|
|
||||||
|
package keccak
|
||||||
|
|
||||||
|
import "math/bits"
|
||||||
|
|
||||||
|
// rc stores the round constants for use in the ι step.
|
||||||
|
var rc = [24]uint64{
|
||||||
|
0x0000000000000001,
|
||||||
|
0x0000000000008082,
|
||||||
|
0x800000000000808A,
|
||||||
|
0x8000000080008000,
|
||||||
|
0x000000000000808B,
|
||||||
|
0x0000000080000001,
|
||||||
|
0x8000000080008081,
|
||||||
|
0x8000000000008009,
|
||||||
|
0x000000000000008A,
|
||||||
|
0x0000000000000088,
|
||||||
|
0x0000000080008009,
|
||||||
|
0x000000008000000A,
|
||||||
|
0x000000008000808B,
|
||||||
|
0x800000000000008B,
|
||||||
|
0x8000000000008089,
|
||||||
|
0x8000000000008003,
|
||||||
|
0x8000000000008002,
|
||||||
|
0x8000000000000080,
|
||||||
|
0x000000000000800A,
|
||||||
|
0x800000008000000A,
|
||||||
|
0x8000000080008081,
|
||||||
|
0x8000000000008080,
|
||||||
|
0x0000000080000001,
|
||||||
|
0x8000000080008008,
|
||||||
|
}
|
||||||
|
|
||||||
|
// keccakF1600 applies the Keccak permutation to a 1600b-wide
|
||||||
|
// state represented as a slice of 25 uint64s.
|
||||||
|
func keccakF1600(a *[25]uint64) {
|
||||||
|
// Implementation translated from Keccak-inplace.c
|
||||||
|
// in the keccak reference code.
|
||||||
|
var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64
|
||||||
|
|
||||||
|
for i := 0; i < 24; i += 4 {
|
||||||
|
// Combines the 5 steps in each round into 2 steps.
|
||||||
|
// Unrolls 4 rounds per loop and spreads some steps across rounds.
|
||||||
|
|
||||||
|
// Round 1
|
||||||
|
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
|
||||||
|
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
|
||||||
|
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
|
||||||
|
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
|
||||||
|
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
|
||||||
|
d0 = bc4 ^ (bc1<<1 | bc1>>63)
|
||||||
|
d1 = bc0 ^ (bc2<<1 | bc2>>63)
|
||||||
|
d2 = bc1 ^ (bc3<<1 | bc3>>63)
|
||||||
|
d3 = bc2 ^ (bc4<<1 | bc4>>63)
|
||||||
|
d4 = bc3 ^ (bc0<<1 | bc0>>63)
|
||||||
|
|
||||||
|
bc0 = a[0] ^ d0
|
||||||
|
t = a[6] ^ d1
|
||||||
|
bc1 = bits.RotateLeft64(t, 44)
|
||||||
|
t = a[12] ^ d2
|
||||||
|
bc2 = bits.RotateLeft64(t, 43)
|
||||||
|
t = a[18] ^ d3
|
||||||
|
bc3 = bits.RotateLeft64(t, 21)
|
||||||
|
t = a[24] ^ d4
|
||||||
|
bc4 = bits.RotateLeft64(t, 14)
|
||||||
|
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i]
|
||||||
|
a[6] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[12] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[18] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[24] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[10] ^ d0
|
||||||
|
bc2 = bits.RotateLeft64(t, 3)
|
||||||
|
t = a[16] ^ d1
|
||||||
|
bc3 = bits.RotateLeft64(t, 45)
|
||||||
|
t = a[22] ^ d2
|
||||||
|
bc4 = bits.RotateLeft64(t, 61)
|
||||||
|
t = a[3] ^ d3
|
||||||
|
bc0 = bits.RotateLeft64(t, 28)
|
||||||
|
t = a[9] ^ d4
|
||||||
|
bc1 = bits.RotateLeft64(t, 20)
|
||||||
|
a[10] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[16] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[22] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[3] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[9] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[20] ^ d0
|
||||||
|
bc4 = bits.RotateLeft64(t, 18)
|
||||||
|
t = a[1] ^ d1
|
||||||
|
bc0 = bits.RotateLeft64(t, 1)
|
||||||
|
t = a[7] ^ d2
|
||||||
|
bc1 = bits.RotateLeft64(t, 6)
|
||||||
|
t = a[13] ^ d3
|
||||||
|
bc2 = bits.RotateLeft64(t, 25)
|
||||||
|
t = a[19] ^ d4
|
||||||
|
bc3 = bits.RotateLeft64(t, 8)
|
||||||
|
a[20] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[1] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[7] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[13] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[19] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[5] ^ d0
|
||||||
|
bc1 = bits.RotateLeft64(t, 36)
|
||||||
|
t = a[11] ^ d1
|
||||||
|
bc2 = bits.RotateLeft64(t, 10)
|
||||||
|
t = a[17] ^ d2
|
||||||
|
bc3 = bits.RotateLeft64(t, 15)
|
||||||
|
t = a[23] ^ d3
|
||||||
|
bc4 = bits.RotateLeft64(t, 56)
|
||||||
|
t = a[4] ^ d4
|
||||||
|
bc0 = bits.RotateLeft64(t, 27)
|
||||||
|
a[5] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[11] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[17] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[23] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[4] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[15] ^ d0
|
||||||
|
bc3 = bits.RotateLeft64(t, 41)
|
||||||
|
t = a[21] ^ d1
|
||||||
|
bc4 = bits.RotateLeft64(t, 2)
|
||||||
|
t = a[2] ^ d2
|
||||||
|
bc0 = bits.RotateLeft64(t, 62)
|
||||||
|
t = a[8] ^ d3
|
||||||
|
bc1 = bits.RotateLeft64(t, 55)
|
||||||
|
t = a[14] ^ d4
|
||||||
|
bc2 = bits.RotateLeft64(t, 39)
|
||||||
|
a[15] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[21] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[2] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[8] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[14] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
// Round 2
|
||||||
|
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
|
||||||
|
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
|
||||||
|
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
|
||||||
|
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
|
||||||
|
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
|
||||||
|
d0 = bc4 ^ (bc1<<1 | bc1>>63)
|
||||||
|
d1 = bc0 ^ (bc2<<1 | bc2>>63)
|
||||||
|
d2 = bc1 ^ (bc3<<1 | bc3>>63)
|
||||||
|
d3 = bc2 ^ (bc4<<1 | bc4>>63)
|
||||||
|
d4 = bc3 ^ (bc0<<1 | bc0>>63)
|
||||||
|
|
||||||
|
bc0 = a[0] ^ d0
|
||||||
|
t = a[16] ^ d1
|
||||||
|
bc1 = bits.RotateLeft64(t, 44)
|
||||||
|
t = a[7] ^ d2
|
||||||
|
bc2 = bits.RotateLeft64(t, 43)
|
||||||
|
t = a[23] ^ d3
|
||||||
|
bc3 = bits.RotateLeft64(t, 21)
|
||||||
|
t = a[14] ^ d4
|
||||||
|
bc4 = bits.RotateLeft64(t, 14)
|
||||||
|
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1]
|
||||||
|
a[16] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[7] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[23] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[14] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[20] ^ d0
|
||||||
|
bc2 = bits.RotateLeft64(t, 3)
|
||||||
|
t = a[11] ^ d1
|
||||||
|
bc3 = bits.RotateLeft64(t, 45)
|
||||||
|
t = a[2] ^ d2
|
||||||
|
bc4 = bits.RotateLeft64(t, 61)
|
||||||
|
t = a[18] ^ d3
|
||||||
|
bc0 = bits.RotateLeft64(t, 28)
|
||||||
|
t = a[9] ^ d4
|
||||||
|
bc1 = bits.RotateLeft64(t, 20)
|
||||||
|
a[20] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[11] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[2] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[18] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[9] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[15] ^ d0
|
||||||
|
bc4 = bits.RotateLeft64(t, 18)
|
||||||
|
t = a[6] ^ d1
|
||||||
|
bc0 = bits.RotateLeft64(t, 1)
|
||||||
|
t = a[22] ^ d2
|
||||||
|
bc1 = bits.RotateLeft64(t, 6)
|
||||||
|
t = a[13] ^ d3
|
||||||
|
bc2 = bits.RotateLeft64(t, 25)
|
||||||
|
t = a[4] ^ d4
|
||||||
|
bc3 = bits.RotateLeft64(t, 8)
|
||||||
|
a[15] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[6] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[22] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[13] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[4] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[10] ^ d0
|
||||||
|
bc1 = bits.RotateLeft64(t, 36)
|
||||||
|
t = a[1] ^ d1
|
||||||
|
bc2 = bits.RotateLeft64(t, 10)
|
||||||
|
t = a[17] ^ d2
|
||||||
|
bc3 = bits.RotateLeft64(t, 15)
|
||||||
|
t = a[8] ^ d3
|
||||||
|
bc4 = bits.RotateLeft64(t, 56)
|
||||||
|
t = a[24] ^ d4
|
||||||
|
bc0 = bits.RotateLeft64(t, 27)
|
||||||
|
a[10] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[1] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[17] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[8] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[24] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[5] ^ d0
|
||||||
|
bc3 = bits.RotateLeft64(t, 41)
|
||||||
|
t = a[21] ^ d1
|
||||||
|
bc4 = bits.RotateLeft64(t, 2)
|
||||||
|
t = a[12] ^ d2
|
||||||
|
bc0 = bits.RotateLeft64(t, 62)
|
||||||
|
t = a[3] ^ d3
|
||||||
|
bc1 = bits.RotateLeft64(t, 55)
|
||||||
|
t = a[19] ^ d4
|
||||||
|
bc2 = bits.RotateLeft64(t, 39)
|
||||||
|
a[5] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[21] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[12] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[3] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[19] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
// Round 3
|
||||||
|
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
|
||||||
|
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
|
||||||
|
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
|
||||||
|
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
|
||||||
|
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
|
||||||
|
d0 = bc4 ^ (bc1<<1 | bc1>>63)
|
||||||
|
d1 = bc0 ^ (bc2<<1 | bc2>>63)
|
||||||
|
d2 = bc1 ^ (bc3<<1 | bc3>>63)
|
||||||
|
d3 = bc2 ^ (bc4<<1 | bc4>>63)
|
||||||
|
d4 = bc3 ^ (bc0<<1 | bc0>>63)
|
||||||
|
|
||||||
|
bc0 = a[0] ^ d0
|
||||||
|
t = a[11] ^ d1
|
||||||
|
bc1 = bits.RotateLeft64(t, 44)
|
||||||
|
t = a[22] ^ d2
|
||||||
|
bc2 = bits.RotateLeft64(t, 43)
|
||||||
|
t = a[8] ^ d3
|
||||||
|
bc3 = bits.RotateLeft64(t, 21)
|
||||||
|
t = a[19] ^ d4
|
||||||
|
bc4 = bits.RotateLeft64(t, 14)
|
||||||
|
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2]
|
||||||
|
a[11] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[22] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[8] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[19] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[15] ^ d0
|
||||||
|
bc2 = bits.RotateLeft64(t, 3)
|
||||||
|
t = a[1] ^ d1
|
||||||
|
bc3 = bits.RotateLeft64(t, 45)
|
||||||
|
t = a[12] ^ d2
|
||||||
|
bc4 = bits.RotateLeft64(t, 61)
|
||||||
|
t = a[23] ^ d3
|
||||||
|
bc0 = bits.RotateLeft64(t, 28)
|
||||||
|
t = a[9] ^ d4
|
||||||
|
bc1 = bits.RotateLeft64(t, 20)
|
||||||
|
a[15] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[1] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[12] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[23] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[9] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[5] ^ d0
|
||||||
|
bc4 = bits.RotateLeft64(t, 18)
|
||||||
|
t = a[16] ^ d1
|
||||||
|
bc0 = bits.RotateLeft64(t, 1)
|
||||||
|
t = a[2] ^ d2
|
||||||
|
bc1 = bits.RotateLeft64(t, 6)
|
||||||
|
t = a[13] ^ d3
|
||||||
|
bc2 = bits.RotateLeft64(t, 25)
|
||||||
|
t = a[24] ^ d4
|
||||||
|
bc3 = bits.RotateLeft64(t, 8)
|
||||||
|
a[5] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[16] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[2] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[13] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[24] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[20] ^ d0
|
||||||
|
bc1 = bits.RotateLeft64(t, 36)
|
||||||
|
t = a[6] ^ d1
|
||||||
|
bc2 = bits.RotateLeft64(t, 10)
|
||||||
|
t = a[17] ^ d2
|
||||||
|
bc3 = bits.RotateLeft64(t, 15)
|
||||||
|
t = a[3] ^ d3
|
||||||
|
bc4 = bits.RotateLeft64(t, 56)
|
||||||
|
t = a[14] ^ d4
|
||||||
|
bc0 = bits.RotateLeft64(t, 27)
|
||||||
|
a[20] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[6] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[17] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[3] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[14] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[10] ^ d0
|
||||||
|
bc3 = bits.RotateLeft64(t, 41)
|
||||||
|
t = a[21] ^ d1
|
||||||
|
bc4 = bits.RotateLeft64(t, 2)
|
||||||
|
t = a[7] ^ d2
|
||||||
|
bc0 = bits.RotateLeft64(t, 62)
|
||||||
|
t = a[18] ^ d3
|
||||||
|
bc1 = bits.RotateLeft64(t, 55)
|
||||||
|
t = a[4] ^ d4
|
||||||
|
bc2 = bits.RotateLeft64(t, 39)
|
||||||
|
a[10] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[21] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[7] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[18] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[4] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
// Round 4
|
||||||
|
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
|
||||||
|
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
|
||||||
|
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
|
||||||
|
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
|
||||||
|
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
|
||||||
|
d0 = bc4 ^ (bc1<<1 | bc1>>63)
|
||||||
|
d1 = bc0 ^ (bc2<<1 | bc2>>63)
|
||||||
|
d2 = bc1 ^ (bc3<<1 | bc3>>63)
|
||||||
|
d3 = bc2 ^ (bc4<<1 | bc4>>63)
|
||||||
|
d4 = bc3 ^ (bc0<<1 | bc0>>63)
|
||||||
|
|
||||||
|
bc0 = a[0] ^ d0
|
||||||
|
t = a[1] ^ d1
|
||||||
|
bc1 = bits.RotateLeft64(t, 44)
|
||||||
|
t = a[2] ^ d2
|
||||||
|
bc2 = bits.RotateLeft64(t, 43)
|
||||||
|
t = a[3] ^ d3
|
||||||
|
bc3 = bits.RotateLeft64(t, 21)
|
||||||
|
t = a[4] ^ d4
|
||||||
|
bc4 = bits.RotateLeft64(t, 14)
|
||||||
|
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3]
|
||||||
|
a[1] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[2] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[3] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[4] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[5] ^ d0
|
||||||
|
bc2 = bits.RotateLeft64(t, 3)
|
||||||
|
t = a[6] ^ d1
|
||||||
|
bc3 = bits.RotateLeft64(t, 45)
|
||||||
|
t = a[7] ^ d2
|
||||||
|
bc4 = bits.RotateLeft64(t, 61)
|
||||||
|
t = a[8] ^ d3
|
||||||
|
bc0 = bits.RotateLeft64(t, 28)
|
||||||
|
t = a[9] ^ d4
|
||||||
|
bc1 = bits.RotateLeft64(t, 20)
|
||||||
|
a[5] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[6] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[7] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[8] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[9] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[10] ^ d0
|
||||||
|
bc4 = bits.RotateLeft64(t, 18)
|
||||||
|
t = a[11] ^ d1
|
||||||
|
bc0 = bits.RotateLeft64(t, 1)
|
||||||
|
t = a[12] ^ d2
|
||||||
|
bc1 = bits.RotateLeft64(t, 6)
|
||||||
|
t = a[13] ^ d3
|
||||||
|
bc2 = bits.RotateLeft64(t, 25)
|
||||||
|
t = a[14] ^ d4
|
||||||
|
bc3 = bits.RotateLeft64(t, 8)
|
||||||
|
a[10] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[11] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[12] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[13] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[14] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[15] ^ d0
|
||||||
|
bc1 = bits.RotateLeft64(t, 36)
|
||||||
|
t = a[16] ^ d1
|
||||||
|
bc2 = bits.RotateLeft64(t, 10)
|
||||||
|
t = a[17] ^ d2
|
||||||
|
bc3 = bits.RotateLeft64(t, 15)
|
||||||
|
t = a[18] ^ d3
|
||||||
|
bc4 = bits.RotateLeft64(t, 56)
|
||||||
|
t = a[19] ^ d4
|
||||||
|
bc0 = bits.RotateLeft64(t, 27)
|
||||||
|
a[15] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[16] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[17] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[18] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[19] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
|
||||||
|
t = a[20] ^ d0
|
||||||
|
bc3 = bits.RotateLeft64(t, 41)
|
||||||
|
t = a[21] ^ d1
|
||||||
|
bc4 = bits.RotateLeft64(t, 2)
|
||||||
|
t = a[22] ^ d2
|
||||||
|
bc0 = bits.RotateLeft64(t, 62)
|
||||||
|
t = a[23] ^ d3
|
||||||
|
bc1 = bits.RotateLeft64(t, 55)
|
||||||
|
t = a[24] ^ d4
|
||||||
|
bc2 = bits.RotateLeft64(t, 39)
|
||||||
|
a[20] = bc0 ^ (bc2 &^ bc1)
|
||||||
|
a[21] = bc1 ^ (bc3 &^ bc2)
|
||||||
|
a[22] = bc2 ^ (bc4 &^ bc3)
|
||||||
|
a[23] = bc3 ^ (bc0 &^ bc4)
|
||||||
|
a[24] = bc4 ^ (bc1 &^ bc0)
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crypto/keccak/keccakf_amd64.go
Normal file
13
crypto/keccak/keccakf_amd64.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build amd64 && !purego && gc
|
||||||
|
|
||||||
|
package keccak
|
||||||
|
|
||||||
|
// This function is implemented in keccakf_amd64.s.
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
|
||||||
|
func keccakF1600(a *[25]uint64)
|
||||||
5419
crypto/keccak/keccakf_amd64.s
Normal file
5419
crypto/keccak/keccakf_amd64.s
Normal file
File diff suppressed because it is too large
Load diff
244
crypto/keccak/sha3.go
Normal file
244
crypto/keccak/sha3.go
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package keccak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spongeDirection indicates the direction bytes are flowing through the sponge.
|
||||||
|
type spongeDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// spongeAbsorbing indicates that the sponge is absorbing input.
|
||||||
|
spongeAbsorbing spongeDirection = iota
|
||||||
|
// spongeSqueezing indicates that the sponge is being squeezed.
|
||||||
|
spongeSqueezing
|
||||||
|
)
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
a [1600 / 8]byte // main state of the hash
|
||||||
|
|
||||||
|
// a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR
|
||||||
|
// into before running the permutation. If squeezing, it's the remaining
|
||||||
|
// output to produce before running the permutation.
|
||||||
|
n, rate int
|
||||||
|
|
||||||
|
// dsbyte contains the "domain separation" bits and the first bit of
|
||||||
|
// the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the
|
||||||
|
// SHA-3 and SHAKE functions by appending bitstrings to the message.
|
||||||
|
// Using a little-endian bit-ordering convention, these are "01" for SHA-3
|
||||||
|
// and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the
|
||||||
|
// padding rule from section 5.1 is applied to pad the message to a multiple
|
||||||
|
// of the rate, which involves adding a "1" bit, zero or more "0" bits, and
|
||||||
|
// a final "1" bit. We merge the first "1" bit from the padding into dsbyte,
|
||||||
|
// giving 00000110b (0x06) and 00011111b (0x1f).
|
||||||
|
// [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf
|
||||||
|
// "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and
|
||||||
|
// Extendable-Output Functions (May 2014)"
|
||||||
|
dsbyte byte
|
||||||
|
|
||||||
|
outputLen int // the default output size in bytes
|
||||||
|
state spongeDirection // whether the sponge is absorbing or squeezing
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockSize returns the rate of sponge underlying this hash function.
|
||||||
|
func (d *state) BlockSize() int { return d.rate }
|
||||||
|
|
||||||
|
// Size returns the output size of the hash function in bytes.
|
||||||
|
func (d *state) Size() int { return d.outputLen }
|
||||||
|
|
||||||
|
// Reset clears the internal state by zeroing the sponge state and
|
||||||
|
// the buffer indexes, and setting Sponge.state to absorbing.
|
||||||
|
func (d *state) Reset() {
|
||||||
|
// Zero the permutation's state.
|
||||||
|
for i := range d.a {
|
||||||
|
d.a[i] = 0
|
||||||
|
}
|
||||||
|
d.state = spongeAbsorbing
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *state) clone() *state {
|
||||||
|
ret := *d
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// permute applies the KeccakF-1600 permutation.
|
||||||
|
func (d *state) permute() {
|
||||||
|
var a *[25]uint64
|
||||||
|
if cpu.IsBigEndian {
|
||||||
|
a = new([25]uint64)
|
||||||
|
for i := range a {
|
||||||
|
a[i] = binary.LittleEndian.Uint64(d.a[i*8:])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a = (*[25]uint64)(unsafe.Pointer(&d.a))
|
||||||
|
}
|
||||||
|
|
||||||
|
keccakF1600(a)
|
||||||
|
d.n = 0
|
||||||
|
|
||||||
|
if cpu.IsBigEndian {
|
||||||
|
for i := range a {
|
||||||
|
binary.LittleEndian.PutUint64(d.a[i*8:], a[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pads appends the domain separation bits in dsbyte, applies
|
||||||
|
// the multi-bitrate 10..1 padding rule, and permutes the state.
|
||||||
|
func (d *state) padAndPermute() {
|
||||||
|
// Pad with this instance's domain-separator bits. We know that there's
|
||||||
|
// at least one byte of space in the sponge because, if it were full,
|
||||||
|
// permute would have been called to empty it. dsbyte also contains the
|
||||||
|
// first one bit for the padding. See the comment in the state struct.
|
||||||
|
d.a[d.n] ^= d.dsbyte
|
||||||
|
// This adds the final one bit for the padding. Because of the way that
|
||||||
|
// bits are numbered from the LSB upwards, the final bit is the MSB of
|
||||||
|
// the last byte.
|
||||||
|
d.a[d.rate-1] ^= 0x80
|
||||||
|
// Apply the permutation
|
||||||
|
d.permute()
|
||||||
|
d.state = spongeSqueezing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write absorbs more data into the hash's state. It panics if any
|
||||||
|
// output has already been read.
|
||||||
|
func (d *state) Write(p []byte) (n int, err error) {
|
||||||
|
if d.state != spongeAbsorbing {
|
||||||
|
panic("sha3: Write after Read")
|
||||||
|
}
|
||||||
|
|
||||||
|
n = len(p)
|
||||||
|
|
||||||
|
for len(p) > 0 {
|
||||||
|
x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p)
|
||||||
|
d.n += x
|
||||||
|
p = p[x:]
|
||||||
|
|
||||||
|
// If the sponge is full, apply the permutation.
|
||||||
|
if d.n == d.rate {
|
||||||
|
d.permute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read squeezes an arbitrary number of bytes from the sponge.
|
||||||
|
func (d *state) Read(out []byte) (n int, err error) {
|
||||||
|
// If we're still absorbing, pad and apply the permutation.
|
||||||
|
if d.state == spongeAbsorbing {
|
||||||
|
d.padAndPermute()
|
||||||
|
}
|
||||||
|
|
||||||
|
n = len(out)
|
||||||
|
|
||||||
|
// Now, do the squeezing.
|
||||||
|
for len(out) > 0 {
|
||||||
|
// Apply the permutation if we've squeezed the sponge dry.
|
||||||
|
if d.n == d.rate {
|
||||||
|
d.permute()
|
||||||
|
}
|
||||||
|
|
||||||
|
x := copy(out, d.a[d.n:d.rate])
|
||||||
|
d.n += x
|
||||||
|
out = out[x:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum applies padding to the hash state and then squeezes out the desired
|
||||||
|
// number of output bytes. It panics if any output has already been read.
|
||||||
|
func (d *state) Sum(in []byte) []byte {
|
||||||
|
if d.state != spongeAbsorbing {
|
||||||
|
panic("sha3: Sum after Read")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a copy of the original hash so that caller can keep writing
|
||||||
|
// and summing.
|
||||||
|
dup := d.clone()
|
||||||
|
hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation
|
||||||
|
dup.Read(hash)
|
||||||
|
return append(in, hash...)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
magicSHA3 = "sha\x08"
|
||||||
|
magicShake = "sha\x09"
|
||||||
|
magicCShake = "sha\x0a"
|
||||||
|
magicKeccak = "sha\x0b"
|
||||||
|
// magic || rate || main state || n || sponge direction
|
||||||
|
marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *state) MarshalBinary() ([]byte, error) {
|
||||||
|
return d.AppendBinary(make([]byte, 0, marshaledSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *state) AppendBinary(b []byte) ([]byte, error) {
|
||||||
|
switch d.dsbyte {
|
||||||
|
case dsbyteSHA3:
|
||||||
|
b = append(b, magicSHA3...)
|
||||||
|
case dsbyteShake:
|
||||||
|
b = append(b, magicShake...)
|
||||||
|
case dsbyteCShake:
|
||||||
|
b = append(b, magicCShake...)
|
||||||
|
case dsbyteKeccak:
|
||||||
|
b = append(b, magicKeccak...)
|
||||||
|
default:
|
||||||
|
panic("unknown dsbyte")
|
||||||
|
}
|
||||||
|
// rate is at most 168, and n is at most rate.
|
||||||
|
b = append(b, byte(d.rate))
|
||||||
|
b = append(b, d.a[:]...)
|
||||||
|
b = append(b, byte(d.n), byte(d.state))
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *state) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) != marshaledSize {
|
||||||
|
return errors.New("sha3: invalid hash state")
|
||||||
|
}
|
||||||
|
|
||||||
|
magic := string(b[:len(magicSHA3)])
|
||||||
|
b = b[len(magicSHA3):]
|
||||||
|
switch {
|
||||||
|
case magic == magicSHA3 && d.dsbyte == dsbyteSHA3:
|
||||||
|
case magic == magicShake && d.dsbyte == dsbyteShake:
|
||||||
|
case magic == magicCShake && d.dsbyte == dsbyteCShake:
|
||||||
|
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
|
||||||
|
default:
|
||||||
|
return errors.New("sha3: invalid hash state identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
rate := int(b[0])
|
||||||
|
b = b[1:]
|
||||||
|
if rate != d.rate {
|
||||||
|
return errors.New("sha3: invalid hash state function")
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(d.a[:], b)
|
||||||
|
b = b[len(d.a):]
|
||||||
|
|
||||||
|
n, state := int(b[0]), spongeDirection(b[1])
|
||||||
|
if n > d.rate {
|
||||||
|
return errors.New("sha3: invalid hash state")
|
||||||
|
}
|
||||||
|
d.n = n
|
||||||
|
if state != spongeAbsorbing && state != spongeSqueezing {
|
||||||
|
return errors.New("sha3: invalid hash state")
|
||||||
|
}
|
||||||
|
d.state = state
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
210
crypto/keccak/sha3_test.go
Normal file
210
crypto/keccak/sha3_test.go
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package keccak
|
||||||
|
|
||||||
|
// Tests include all the ShortMsgKATs provided by the Keccak team at
|
||||||
|
// https://github.com/gvanas/KeccakCodePackage
|
||||||
|
//
|
||||||
|
// They only include the zero-bit case of the bitwise testvectors
|
||||||
|
// published by NIST in the draft of FIPS-202.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/flate"
|
||||||
|
"encoding"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"hash"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testString = "brekeccakkeccak koax koax"
|
||||||
|
katFilename = "testdata/keccakKats.json.deflate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testDigests contains functions returning hash.Hash instances
|
||||||
|
// with output-length equal to the KAT length for SHA-3, Keccak
|
||||||
|
// and SHAKE instances.
|
||||||
|
var testDigests = map[string]func() hash.Hash{
|
||||||
|
"Keccak-256": NewLegacyKeccak256,
|
||||||
|
"Keccak-512": NewLegacyKeccak512,
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeHex converts a hex-encoded string into a raw byte string.
|
||||||
|
func decodeHex(s string) []byte {
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// structs used to marshal JSON test-cases.
|
||||||
|
type KeccakKats struct {
|
||||||
|
Kats map[string][]struct {
|
||||||
|
Digest string `json:"digest"`
|
||||||
|
Length int64 `json:"length"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
|
||||||
|
// Defined only for cSHAKE
|
||||||
|
N string `json:"N"`
|
||||||
|
S string `json:"S"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestKeccakKats tests the SHA-3 and Shake implementations against all the
|
||||||
|
// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
|
||||||
|
// (The testvectors are stored in keccakKats.json.deflate due to their length.)
|
||||||
|
func TestKeccakKats(t *testing.T) {
|
||||||
|
// Read the KATs.
|
||||||
|
deflated, err := os.Open(katFilename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error opening %s: %s", katFilename, err)
|
||||||
|
}
|
||||||
|
file := flate.NewReader(deflated)
|
||||||
|
dec := json.NewDecoder(file)
|
||||||
|
var katSet KeccakKats
|
||||||
|
err = dec.Decode(&katSet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error decoding KATs: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for algo, function := range testDigests {
|
||||||
|
d := function()
|
||||||
|
for _, kat := range katSet.Kats[algo] {
|
||||||
|
d.Reset()
|
||||||
|
in, err := hex.DecodeString(kat.Message)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error decoding KAT: %s", err)
|
||||||
|
}
|
||||||
|
d.Write(in[:kat.Length/8])
|
||||||
|
got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
|
||||||
|
if got != kat.Digest {
|
||||||
|
t.Errorf("function=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s",
|
||||||
|
algo, kat.Length, kat.Message, got, kat.Digest)
|
||||||
|
t.Logf("wanted %+v", kat)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestKeccak does a basic test of the non-standardized Keccak hash functions.
|
||||||
|
func TestKeccak(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
fn func() hash.Hash
|
||||||
|
data []byte
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
NewLegacyKeccak256,
|
||||||
|
[]byte("abc"),
|
||||||
|
"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NewLegacyKeccak512,
|
||||||
|
[]byte("abc"),
|
||||||
|
"18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range tests {
|
||||||
|
h := u.fn()
|
||||||
|
h.Write(u.data)
|
||||||
|
got := h.Sum(nil)
|
||||||
|
want := decodeHex(u.want)
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("unexpected hash for size %d: got '%x' want '%s'", h.Size()*8, got, u.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnalignedWrite tests that writing data in an arbitrary pattern with
|
||||||
|
// small input buffers.
|
||||||
|
func TestUnalignedWrite(t *testing.T) {
|
||||||
|
buf := sequentialBytes(0x10000)
|
||||||
|
for alg, df := range testDigests {
|
||||||
|
d := df()
|
||||||
|
d.Reset()
|
||||||
|
d.Write(buf)
|
||||||
|
want := d.Sum(nil)
|
||||||
|
d.Reset()
|
||||||
|
for i := 0; i < len(buf); {
|
||||||
|
// Cycle through offsets which make a 137 byte sequence.
|
||||||
|
// Because 137 is prime this sequence should exercise all corner cases.
|
||||||
|
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
|
||||||
|
for _, j := range offsets {
|
||||||
|
if v := len(buf) - i; v < j {
|
||||||
|
j = v
|
||||||
|
}
|
||||||
|
d.Write(buf[i : i+j])
|
||||||
|
i += j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got := d.Sum(nil)
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
|
||||||
|
//
|
||||||
|
// The alignment of each slice is intentionally randomized to detect alignment
|
||||||
|
// issues in the implementation. See https://golang.org/issue/37644.
|
||||||
|
// Ideally, the compiler should fuzz the alignment itself.
|
||||||
|
// (See https://golang.org/issue/35128.)
|
||||||
|
func sequentialBytes(size int) []byte {
|
||||||
|
alignmentOffset := rand.Intn(8)
|
||||||
|
result := make([]byte, size+alignmentOffset)[alignmentOffset:]
|
||||||
|
for i := range result {
|
||||||
|
result[i] = byte(i)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshal(t *testing.T) {
|
||||||
|
t.Run("Keccak-256", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak256()) })
|
||||||
|
t.Run("Keccak-512", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak512()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(filippo): move this to crypto/internal/cryptotest.
|
||||||
|
func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
|
||||||
|
buf := make([]byte, 200)
|
||||||
|
rand.Read(buf)
|
||||||
|
n := rand.Intn(200)
|
||||||
|
h.Write(buf)
|
||||||
|
want := h.Sum(nil)
|
||||||
|
h.Reset()
|
||||||
|
h.Write(buf[:n])
|
||||||
|
b, err := h.(encoding.BinaryMarshaler).MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalBinary: %v", err)
|
||||||
|
}
|
||||||
|
h.Write(bytes.Repeat([]byte{0}, 200))
|
||||||
|
if err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary(b); err != nil {
|
||||||
|
t.Errorf("UnmarshalBinary: %v", err)
|
||||||
|
}
|
||||||
|
h.Write(buf[n:])
|
||||||
|
got := h.Sum(nil)
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("got %x, want %x", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkPermutationFunction measures the speed of the permutation function
|
||||||
|
// with no input data.
|
||||||
|
func BenchmarkPermutationFunction(b *testing.B) {
|
||||||
|
b.SetBytes(int64(200))
|
||||||
|
var lanes [25]uint64
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
keccakF1600(&lanes)
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
crypto/keccak/testdata/keccakKats.json.deflate
vendored
Normal file
BIN
crypto/keccak/testdata/keccakKats.json.deflate
vendored
Normal file
Binary file not shown.
|
|
@ -143,9 +143,9 @@ func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []Proof{}, err
|
return []Proof{}, err
|
||||||
}
|
}
|
||||||
var p []Proof
|
p := make([]Proof, len(proofs))
|
||||||
for _, proof := range proofs {
|
for i, proof := range proofs {
|
||||||
p = append(p, (Proof)(proof))
|
p[i] = (Proof)(proof)
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,9 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []Proof{}, err
|
return []Proof{}, err
|
||||||
}
|
}
|
||||||
var p []Proof
|
p := make([]Proof, len(proofs))
|
||||||
for _, proof := range proofs {
|
for i, proof := range proofs {
|
||||||
p = append(p, (Proof)(proof))
|
p[i] = (Proof)(proof)
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,17 +222,19 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
options = &core.BlockChainConfig{
|
options = &core.BlockChainConfig{
|
||||||
TrieCleanLimit: config.TrieCleanCache,
|
TrieCleanLimit: config.TrieCleanCache,
|
||||||
NoPrefetch: config.NoPrefetch,
|
NoPrefetch: config.NoPrefetch,
|
||||||
TrieDirtyLimit: config.TrieDirtyCache,
|
TrieDirtyLimit: config.TrieDirtyCache,
|
||||||
ArchiveMode: config.NoPruning,
|
ArchiveMode: config.NoPruning,
|
||||||
TrieTimeLimit: config.TrieTimeout,
|
TrieTimeLimit: config.TrieTimeout,
|
||||||
SnapshotLimit: config.SnapshotCache,
|
SnapshotLimit: config.SnapshotCache,
|
||||||
Preimages: config.Preimages,
|
Preimages: config.Preimages,
|
||||||
StateHistory: config.StateHistory,
|
StateHistory: config.StateHistory,
|
||||||
StateScheme: scheme,
|
TrienodeHistory: config.TrienodeHistory,
|
||||||
ChainHistoryMode: config.HistoryMode,
|
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
||||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
StateScheme: scheme,
|
||||||
|
ChainHistoryMode: config.HistoryMode,
|
||||||
|
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||||
VmConfig: vm.Config{
|
VmConfig: vm.Config{
|
||||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||||
EnableWitnessStats: config.EnableWitnessStats,
|
EnableWitnessStats: config.EnableWitnessStats,
|
||||||
|
|
@ -493,6 +495,9 @@ func (s *Ethereum) updateFilterMapsHeads() {
|
||||||
if head == nil || newHead.Hash() != head.Hash() {
|
if head == nil || newHead.Hash() != head.Hash() {
|
||||||
head = newHead
|
head = newHead
|
||||||
chainView := s.newChainView(head)
|
chainView := s.newChainView(head)
|
||||||
|
if chainView == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
historyCutoff, _ := s.blockchain.HistoryPruningCutoff()
|
historyCutoff, _ := s.blockchain.HistoryPruningCutoff()
|
||||||
var finalBlock uint64
|
var finalBlock uint64
|
||||||
if fb := s.blockchain.CurrentFinalBlock(); fb != nil {
|
if fb := s.blockchain.CurrentFinalBlock(); fb != nil {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FullNodeGPO contains default gasprice oracle settings for full node.
|
// FullNodeGPO contains default gasprice oracle settings for full node.
|
||||||
|
|
@ -49,30 +50,33 @@ var FullNodeGPO = gasprice.Config{
|
||||||
|
|
||||||
// Defaults contains default settings for use on the Ethereum main net.
|
// Defaults contains default settings for use on the Ethereum main net.
|
||||||
var Defaults = Config{
|
var Defaults = Config{
|
||||||
HistoryMode: history.KeepAll,
|
HistoryMode: history.KeepAll,
|
||||||
SyncMode: SnapSync,
|
SyncMode: SnapSync,
|
||||||
NetworkId: 0, // enable auto configuration of networkID == chainID
|
NetworkId: 0, // enable auto configuration of networkID == chainID
|
||||||
TxLookupLimit: 2350000,
|
TxLookupLimit: 2350000,
|
||||||
TransactionHistory: 2350000,
|
TransactionHistory: 2350000,
|
||||||
LogHistory: 2350000,
|
LogHistory: 2350000,
|
||||||
StateHistory: params.FullImmutabilityThreshold,
|
StateHistory: pathdb.Defaults.StateHistory,
|
||||||
DatabaseCache: 512,
|
TrienodeHistory: pathdb.Defaults.TrienodeHistory,
|
||||||
TrieCleanCache: 154,
|
NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
|
||||||
TrieDirtyCache: 256,
|
DatabaseCache: 512,
|
||||||
TrieTimeout: 60 * time.Minute,
|
TrieCleanCache: 154,
|
||||||
SnapshotCache: 102,
|
TrieDirtyCache: 256,
|
||||||
FilterLogCacheSize: 32,
|
TrieTimeout: 60 * time.Minute,
|
||||||
LogQueryLimit: 1000,
|
SnapshotCache: 102,
|
||||||
Miner: miner.DefaultConfig,
|
FilterLogCacheSize: 32,
|
||||||
TxPool: legacypool.DefaultConfig,
|
LogQueryLimit: 1000,
|
||||||
BlobPool: blobpool.DefaultConfig,
|
Miner: miner.DefaultConfig,
|
||||||
RPCGasCap: 50000000,
|
TxPool: legacypool.DefaultConfig,
|
||||||
RPCEVMTimeout: 5 * time.Second,
|
BlobPool: blobpool.DefaultConfig,
|
||||||
GPO: FullNodeGPO,
|
RPCGasCap: 50000000,
|
||||||
RPCTxFeeCap: 1, // 1 ether
|
RPCEVMTimeout: 5 * time.Second,
|
||||||
TxSyncDefaultTimeout: 20 * time.Second,
|
GPO: FullNodeGPO,
|
||||||
TxSyncMaxTimeout: 1 * time.Minute,
|
RPCTxFeeCap: 1, // 1 ether
|
||||||
SlowBlockThreshold: time.Second * 2,
|
TxSyncDefaultTimeout: 20 * time.Second,
|
||||||
|
TxSyncMaxTimeout: 1 * time.Minute,
|
||||||
|
SlowBlockThreshold: -1, // Disabled by default; set via --debug.logslowblock flag
|
||||||
|
RangeLimit: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
|
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
|
||||||
|
|
@ -108,6 +112,13 @@ type Config struct {
|
||||||
LogNoHistory bool `toml:",omitempty"` // No log search index is maintained.
|
LogNoHistory bool `toml:",omitempty"` // No log search index is maintained.
|
||||||
LogExportCheckpoints string // export log index checkpoints to file
|
LogExportCheckpoints string // export log index checkpoints to file
|
||||||
StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved.
|
StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved.
|
||||||
|
TrienodeHistory int64 `toml:",omitempty"` // Number of blocks from the chain head for which trienode histories are retained
|
||||||
|
|
||||||
|
// The frequency of full-value encoding. For example, a value of 16 means
|
||||||
|
// that, on average, for a given trie node across its 16 consecutive historical
|
||||||
|
// versions, only one version is stored in full format, while the others
|
||||||
|
// are stored in diff mode for storage compression.
|
||||||
|
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
|
||||||
|
|
||||||
// State scheme represents the scheme used to store ethereum states and trie
|
// State scheme represents the scheme used to store ethereum states and trie
|
||||||
// nodes on top. It can be 'hash', 'path', or none which means use the scheme
|
// nodes on top. It can be 'hash', 'path', or none which means use the scheme
|
||||||
|
|
@ -119,8 +130,9 @@ type Config struct {
|
||||||
// presence of these blocks for every new peer connection.
|
// presence of these blocks for every new peer connection.
|
||||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||||
|
|
||||||
// SlowBlockThreshold is the block execution speed threshold (Mgas/s)
|
// SlowBlockThreshold is the block execution time threshold beyond which
|
||||||
// below which detailed statistics are logged.
|
// detailed statistics are logged. Negative means disabled (default), zero
|
||||||
|
// logs all blocks, positive filters by execution time.
|
||||||
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
||||||
|
|
||||||
// Database options
|
// Database options
|
||||||
|
|
@ -194,6 +206,9 @@ type Config struct {
|
||||||
// EIP-7966: eth_sendRawTransactionSync timeouts
|
// EIP-7966: eth_sendRawTransactionSync timeouts
|
||||||
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||||
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||||
|
|
||||||
|
// RangeLimit restricts the maximum range (end - start) for range queries.
|
||||||
|
RangeLimit uint64 `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
LogNoHistory bool `toml:",omitempty"`
|
LogNoHistory bool `toml:",omitempty"`
|
||||||
LogExportCheckpoints string
|
LogExportCheckpoints string
|
||||||
StateHistory uint64 `toml:",omitempty"`
|
StateHistory uint64 `toml:",omitempty"`
|
||||||
|
TrienodeHistory int64 `toml:",omitempty"`
|
||||||
|
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
|
||||||
StateScheme string `toml:",omitempty"`
|
StateScheme string `toml:",omitempty"`
|
||||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||||
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
||||||
|
|
@ -65,6 +67,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||||
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||||
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||||
|
RangeLimit uint64 `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
var enc Config
|
var enc Config
|
||||||
enc.Genesis = c.Genesis
|
enc.Genesis = c.Genesis
|
||||||
|
|
@ -81,6 +84,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.LogNoHistory = c.LogNoHistory
|
enc.LogNoHistory = c.LogNoHistory
|
||||||
enc.LogExportCheckpoints = c.LogExportCheckpoints
|
enc.LogExportCheckpoints = c.LogExportCheckpoints
|
||||||
enc.StateHistory = c.StateHistory
|
enc.StateHistory = c.StateHistory
|
||||||
|
enc.TrienodeHistory = c.TrienodeHistory
|
||||||
|
enc.NodeFullValueCheckpoint = c.NodeFullValueCheckpoint
|
||||||
enc.StateScheme = c.StateScheme
|
enc.StateScheme = c.StateScheme
|
||||||
enc.RequiredBlocks = c.RequiredBlocks
|
enc.RequiredBlocks = c.RequiredBlocks
|
||||||
enc.SlowBlockThreshold = c.SlowBlockThreshold
|
enc.SlowBlockThreshold = c.SlowBlockThreshold
|
||||||
|
|
@ -115,6 +120,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.OverrideVerkle = c.OverrideVerkle
|
enc.OverrideVerkle = c.OverrideVerkle
|
||||||
enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout
|
enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout
|
||||||
enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout
|
enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout
|
||||||
|
enc.RangeLimit = c.RangeLimit
|
||||||
return &enc, nil
|
return &enc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,6 +141,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
LogNoHistory *bool `toml:",omitempty"`
|
LogNoHistory *bool `toml:",omitempty"`
|
||||||
LogExportCheckpoints *string
|
LogExportCheckpoints *string
|
||||||
StateHistory *uint64 `toml:",omitempty"`
|
StateHistory *uint64 `toml:",omitempty"`
|
||||||
|
TrienodeHistory *int64 `toml:",omitempty"`
|
||||||
|
NodeFullValueCheckpoint *uint32 `toml:",omitempty"`
|
||||||
StateScheme *string `toml:",omitempty"`
|
StateScheme *string `toml:",omitempty"`
|
||||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||||
SlowBlockThreshold *time.Duration `toml:",omitempty"`
|
SlowBlockThreshold *time.Duration `toml:",omitempty"`
|
||||||
|
|
@ -169,6 +177,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||||
TxSyncDefaultTimeout *time.Duration `toml:",omitempty"`
|
TxSyncDefaultTimeout *time.Duration `toml:",omitempty"`
|
||||||
TxSyncMaxTimeout *time.Duration `toml:",omitempty"`
|
TxSyncMaxTimeout *time.Duration `toml:",omitempty"`
|
||||||
|
RangeLimit *uint64 `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
var dec Config
|
var dec Config
|
||||||
if err := unmarshal(&dec); err != nil {
|
if err := unmarshal(&dec); err != nil {
|
||||||
|
|
@ -216,6 +225,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.StateHistory != nil {
|
if dec.StateHistory != nil {
|
||||||
c.StateHistory = *dec.StateHistory
|
c.StateHistory = *dec.StateHistory
|
||||||
}
|
}
|
||||||
|
if dec.TrienodeHistory != nil {
|
||||||
|
c.TrienodeHistory = *dec.TrienodeHistory
|
||||||
|
}
|
||||||
|
if dec.NodeFullValueCheckpoint != nil {
|
||||||
|
c.NodeFullValueCheckpoint = *dec.NodeFullValueCheckpoint
|
||||||
|
}
|
||||||
if dec.StateScheme != nil {
|
if dec.StateScheme != nil {
|
||||||
c.StateScheme = *dec.StateScheme
|
c.StateScheme = *dec.StateScheme
|
||||||
}
|
}
|
||||||
|
|
@ -318,5 +333,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.TxSyncMaxTimeout != nil {
|
if dec.TxSyncMaxTimeout != nil {
|
||||||
c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout
|
c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout
|
||||||
}
|
}
|
||||||
|
if dec.RangeLimit != nil {
|
||||||
|
c.RangeLimit = *dec.RangeLimit
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,19 @@ type txFetcherTest struct {
|
||||||
steps []interface{}
|
steps []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newTestTxFetcher creates a tx fetcher with noop callbacks, simulated clock,
|
||||||
|
// and deterministic randomness.
|
||||||
|
func newTestTxFetcher() *TxFetcher {
|
||||||
|
return NewTxFetcher(
|
||||||
|
func(common.Hash, byte) error { return nil },
|
||||||
|
func(txs []*types.Transaction) []error {
|
||||||
|
return make([]error, len(txs))
|
||||||
|
},
|
||||||
|
func(string, []common.Hash) error { return nil },
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that transaction announcements with associated metadata are added to a
|
// Tests that transaction announcements with associated metadata are added to a
|
||||||
// waitlist, and none of them are scheduled for retrieval until the wait expires.
|
// waitlist, and none of them are scheduled for retrieval until the wait expires.
|
||||||
//
|
//
|
||||||
|
|
@ -95,14 +108,7 @@ type txFetcherTest struct {
|
||||||
// with all the useless extra fields.
|
// with all the useless extra fields.
|
||||||
func TestTransactionFetcherWaiting(t *testing.T) {
|
func TestTransactionFetcherWaiting(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Initial announcement to get something into the waitlist
|
// Initial announcement to get something into the waitlist
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
||||||
|
|
@ -297,14 +303,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
|
||||||
// already scheduled.
|
// already scheduled.
|
||||||
func TestTransactionFetcherSkipWaiting(t *testing.T) {
|
func TestTransactionFetcherSkipWaiting(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{
|
doTxNotify{
|
||||||
|
|
@ -387,14 +386,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) {
|
||||||
// and subsequent announces block or get allotted to someone else.
|
// and subsequent announces block or get allotted to someone else.
|
||||||
func TestTransactionFetcherSingletonRequesting(t *testing.T) {
|
func TestTransactionFetcherSingletonRequesting(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
||||||
|
|
@ -493,15 +485,12 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
|
||||||
proceed := make(chan struct{})
|
proceed := make(chan struct{})
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(common.Hash, byte) error { return nil },
|
f.fetchTxs = func(origin string, hashes []common.Hash) error {
|
||||||
nil,
|
<-proceed
|
||||||
func(origin string, hashes []common.Hash) error {
|
return errors.New("peer disconnected")
|
||||||
<-proceed
|
}
|
||||||
return errors.New("peer disconnected")
|
return f
|
||||||
},
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
|
|
@ -576,16 +565,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
|
||||||
// are cleaned up.
|
// are cleaned up.
|
||||||
func TestTransactionFetcherCleanup(t *testing.T) {
|
func TestTransactionFetcherCleanup(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
||||||
|
|
@ -620,16 +600,7 @@ func TestTransactionFetcherCleanup(t *testing.T) {
|
||||||
// this was a bug)).
|
// this was a bug)).
|
||||||
func TestTransactionFetcherCleanupEmpty(t *testing.T) {
|
func TestTransactionFetcherCleanupEmpty(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
||||||
|
|
@ -663,16 +634,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
|
||||||
// different peer, or self if they are after the cutoff point.
|
// different peer, or self if they are after the cutoff point.
|
||||||
func TestTransactionFetcherMissingRescheduling(t *testing.T) {
|
func TestTransactionFetcherMissingRescheduling(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{peer: "A",
|
doTxNotify{peer: "A",
|
||||||
|
|
@ -724,16 +686,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) {
|
||||||
// delivered, the peer gets properly cleaned out from the internal state.
|
// delivered, the peer gets properly cleaned out from the internal state.
|
||||||
func TestTransactionFetcherMissingCleanup(t *testing.T) {
|
func TestTransactionFetcherMissingCleanup(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{peer: "A",
|
doTxNotify{peer: "A",
|
||||||
|
|
@ -773,16 +726,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) {
|
||||||
// Tests that transaction broadcasts properly clean up announcements.
|
// Tests that transaction broadcasts properly clean up announcements.
|
||||||
func TestTransactionFetcherBroadcasts(t *testing.T) {
|
func TestTransactionFetcherBroadcasts(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Set up three transactions to be in different stats, waiting, queued and fetching
|
// Set up three transactions to be in different stats, waiting, queued and fetching
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
||||||
|
|
@ -829,14 +773,7 @@ func TestTransactionFetcherBroadcasts(t *testing.T) {
|
||||||
// Tests that the waiting list timers properly reset and reschedule.
|
// Tests that the waiting list timers properly reset and reschedule.
|
||||||
func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
||||||
isWaiting(map[string][]announce{
|
isWaiting(map[string][]announce{
|
||||||
|
|
@ -899,16 +836,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
|
||||||
// out and be re-scheduled for someone else.
|
// out and be re-scheduled for someone else.
|
||||||
func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
|
func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Push an initial announcement through to the scheduled stage
|
// Push an initial announcement through to the scheduled stage
|
||||||
doTxNotify{
|
doTxNotify{
|
||||||
|
|
@ -977,14 +905,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
|
||||||
// Tests that the fetching timeout timers properly reset and reschedule.
|
// Tests that the fetching timeout timers properly reset and reschedule.
|
||||||
func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
|
func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: txArriveTimeout, step: true},
|
||||||
|
|
@ -1055,14 +976,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Announce all the transactions, wait a bit and ensure only a small
|
// Announce all the transactions, wait a bit and ensure only a small
|
||||||
// percentage gets requested
|
// percentage gets requested
|
||||||
|
|
@ -1085,14 +999,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
|
||||||
// be requested at a time, to keep the responses below a reasonable level.
|
// be requested at a time, to keep the responses below a reasonable level.
|
||||||
func TestTransactionFetcherBandwidthLimiting(t *testing.T) {
|
func TestTransactionFetcherBandwidthLimiting(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Announce mid size transactions from A to verify that multiple
|
// Announce mid size transactions from A to verify that multiple
|
||||||
// ones can be piled into a single request.
|
// ones can be piled into a single request.
|
||||||
|
|
@ -1202,14 +1109,7 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Announce half of the transaction and wait for them to be scheduled
|
// Announce half of the transaction and wait for them to be scheduled
|
||||||
doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2], types: typesA[:maxTxAnnounces/2], sizes: sizesA[:maxTxAnnounces/2]},
|
doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2], types: typesA[:maxTxAnnounces/2], sizes: sizesA[:maxTxAnnounces/2]},
|
||||||
|
|
@ -1270,24 +1170,21 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
|
||||||
func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(common.Hash, byte) error { return nil },
|
f.addTxs = func(txs []*types.Transaction) []error {
|
||||||
func(txs []*types.Transaction) []error {
|
errs := make([]error, len(txs))
|
||||||
errs := make([]error, len(txs))
|
for i := 0; i < len(errs); i++ {
|
||||||
for i := 0; i < len(errs); i++ {
|
if i%3 == 0 {
|
||||||
if i%3 == 0 {
|
errs[i] = txpool.ErrUnderpriced
|
||||||
errs[i] = txpool.ErrUnderpriced
|
} else if i%3 == 1 {
|
||||||
} else if i%3 == 1 {
|
errs[i] = txpool.ErrReplaceUnderpriced
|
||||||
errs[i] = txpool.ErrReplaceUnderpriced
|
} else {
|
||||||
} else {
|
errs[i] = txpool.ErrTxGasPriceTooLow
|
||||||
errs[i] = txpool.ErrTxGasPriceTooLow
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return errs
|
}
|
||||||
},
|
return errs
|
||||||
func(string, []common.Hash) error { return nil },
|
}
|
||||||
nil,
|
return f
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Deliver a transaction through the fetcher, but reject as underpriced
|
// Deliver a transaction through the fetcher, but reject as underpriced
|
||||||
|
|
@ -1371,18 +1268,15 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
|
||||||
}
|
}
|
||||||
testTransactionFetcher(t, txFetcherTest{
|
testTransactionFetcher(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(common.Hash, byte) error { return nil },
|
f.addTxs = func(txs []*types.Transaction) []error {
|
||||||
func(txs []*types.Transaction) []error {
|
errs := make([]error, len(txs))
|
||||||
errs := make([]error, len(txs))
|
for i := 0; i < len(errs); i++ {
|
||||||
for i := 0; i < len(errs); i++ {
|
errs[i] = txpool.ErrUnderpriced
|
||||||
errs[i] = txpool.ErrUnderpriced
|
}
|
||||||
}
|
return errs
|
||||||
return errs
|
}
|
||||||
},
|
return f
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: append(steps, []interface{}{
|
steps: append(steps, []interface{}{
|
||||||
// The preparation of the test has already been done in `steps`, add the last check
|
// The preparation of the test has already been done in `steps`, add the last check
|
||||||
|
|
@ -1402,16 +1296,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
|
||||||
// Tests that unexpected deliveries don't corrupt the internal state.
|
// Tests that unexpected deliveries don't corrupt the internal state.
|
||||||
func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
|
func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Deliver something out of the blue
|
// Deliver something out of the blue
|
||||||
isWaiting(nil),
|
isWaiting(nil),
|
||||||
|
|
@ -1461,16 +1346,7 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
|
||||||
// live or dangling stages.
|
// live or dangling stages.
|
||||||
func TestTransactionFetcherDrop(t *testing.T) {
|
func TestTransactionFetcherDrop(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Set up a few hashes into various stages
|
// Set up a few hashes into various stages
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
||||||
|
|
@ -1535,16 +1411,7 @@ func TestTransactionFetcherDrop(t *testing.T) {
|
||||||
// available peer.
|
// available peer.
|
||||||
func TestTransactionFetcherDropRescheduling(t *testing.T) {
|
func TestTransactionFetcherDropRescheduling(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Set up a few hashes into various stages
|
// Set up a few hashes into various stages
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
|
||||||
|
|
@ -1582,14 +1449,9 @@ func TestInvalidAnnounceMetadata(t *testing.T) {
|
||||||
drop := make(chan string, 2)
|
drop := make(chan string, 2)
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(common.Hash, byte) error { return nil },
|
f.dropPeer = func(peer string) { drop <- peer }
|
||||||
func(txs []*types.Transaction) []error {
|
return f
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
func(peer string) { drop <- peer },
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Initial announcement to get something into the waitlist
|
// Initial announcement to get something into the waitlist
|
||||||
|
|
@ -1664,16 +1526,7 @@ func TestInvalidAnnounceMetadata(t *testing.T) {
|
||||||
// announced one.
|
// announced one.
|
||||||
func TestTransactionFetcherFuzzCrash01(t *testing.T) {
|
func TestTransactionFetcherFuzzCrash01(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
||||||
|
|
@ -1692,16 +1545,7 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) {
|
||||||
// concurrently announced one.
|
// concurrently announced one.
|
||||||
func TestTransactionFetcherFuzzCrash02(t *testing.T) {
|
func TestTransactionFetcherFuzzCrash02(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
||||||
|
|
@ -1722,16 +1566,7 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) {
|
||||||
// with a concurrent notify.
|
// with a concurrent notify.
|
||||||
func TestTransactionFetcherFuzzCrash03(t *testing.T) {
|
func TestTransactionFetcherFuzzCrash03(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
doTxNotify{
|
doTxNotify{
|
||||||
|
|
@ -1762,17 +1597,12 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
|
||||||
|
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(common.Hash, byte) error { return nil },
|
f.fetchTxs = func(string, []common.Hash) error {
|
||||||
func(txs []*types.Transaction) []error {
|
<-proceed
|
||||||
return make([]error, len(txs))
|
return errors.New("peer disconnected")
|
||||||
},
|
}
|
||||||
func(string, []common.Hash) error {
|
return f
|
||||||
<-proceed
|
|
||||||
return errors.New("peer disconnected")
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Get a transaction into fetching mode and make it dangling with a broadcast
|
// Get a transaction into fetching mode and make it dangling with a broadcast
|
||||||
|
|
@ -1796,14 +1626,7 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
|
||||||
// once they are announced in the network.
|
// once they are announced in the network.
|
||||||
func TestBlobTransactionAnnounce(t *testing.T) {
|
func TestBlobTransactionAnnounce(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
nil,
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Initial announcement to get something into the waitlist
|
// Initial announcement to get something into the waitlist
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
||||||
|
|
@ -1864,16 +1687,7 @@ func TestBlobTransactionAnnounce(t *testing.T) {
|
||||||
|
|
||||||
func TestTransactionFetcherDropAlternates(t *testing.T) {
|
func TestTransactionFetcherDropAlternates(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: newTestTxFetcher,
|
||||||
return NewTxFetcher(
|
|
||||||
func(common.Hash, byte) error { return nil },
|
|
||||||
func(txs []*types.Transaction) []error {
|
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
|
||||||
doWait{time: txArriveTimeout, step: true},
|
doWait{time: txArriveTimeout, step: true},
|
||||||
|
|
@ -1915,20 +1729,15 @@ func TestTransactionFetcherDropAlternates(t *testing.T) {
|
||||||
func TestTransactionFetcherWrongMetadata(t *testing.T) {
|
func TestTransactionFetcherWrongMetadata(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(_ common.Hash, kind byte) error {
|
f.validateMeta = func(name common.Hash, kind byte) error {
|
||||||
switch kind {
|
switch kind {
|
||||||
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
|
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return types.ErrTxTypeNotSupported
|
return types.ErrTxTypeNotSupported
|
||||||
},
|
}
|
||||||
func(txs []*types.Transaction) []error {
|
return f
|
||||||
return make([]error, len(txs))
|
|
||||||
},
|
|
||||||
func(string, []common.Hash) error { return nil },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{0xff, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{0xff, types.LegacyTxType}, sizes: []uint32{111, 222}},
|
||||||
|
|
@ -1976,20 +1785,16 @@ func TestTransactionProtocolViolation(t *testing.T) {
|
||||||
)
|
)
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
return NewTxFetcher(
|
f := newTestTxFetcher()
|
||||||
func(common.Hash, byte) error { return nil },
|
f.addTxs = func(txs []*types.Transaction) []error {
|
||||||
func(txs []*types.Transaction) []error {
|
var errs []error
|
||||||
var errs []error
|
for range txs {
|
||||||
for range txs {
|
errs = append(errs, txpool.ErrKZGVerificationError)
|
||||||
errs = append(errs, txpool.ErrKZGVerificationError)
|
}
|
||||||
}
|
return errs
|
||||||
return errs
|
}
|
||||||
},
|
f.dropPeer = func(string) { drop <- struct{}{} }
|
||||||
func(a string, b []common.Hash) error {
|
return f
|
||||||
return nil
|
|
||||||
},
|
|
||||||
func(peer string) { drop <- struct{}{} },
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
steps: []interface{}{
|
steps: []interface{}{
|
||||||
// Initial announcement to get something into the waitlist
|
// Initial announcement to get something into the waitlist
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ type FilterAPI struct {
|
||||||
filters map[rpc.ID]*filter
|
filters map[rpc.ID]*filter
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
logQueryLimit int
|
logQueryLimit int
|
||||||
|
rangeLimit uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFilterAPI returns a new FilterAPI instance.
|
// NewFilterAPI returns a new FilterAPI instance.
|
||||||
|
|
@ -99,6 +100,7 @@ func NewFilterAPI(system *FilterSystem) *FilterAPI {
|
||||||
filters: make(map[rpc.ID]*filter),
|
filters: make(map[rpc.ID]*filter),
|
||||||
timeout: system.cfg.Timeout,
|
timeout: system.cfg.Timeout,
|
||||||
logQueryLimit: system.cfg.LogQueryLimit,
|
logQueryLimit: system.cfg.LogQueryLimit,
|
||||||
|
rangeLimit: system.cfg.RangeLimit,
|
||||||
}
|
}
|
||||||
go api.timeoutLoop(system.cfg.Timeout)
|
go api.timeoutLoop(system.cfg.Timeout)
|
||||||
|
|
||||||
|
|
@ -479,7 +481,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
|
||||||
return nil, &history.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
|
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics, api.rangeLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the filter and return all the logs
|
// Run the filter and return all the logs
|
||||||
|
|
@ -531,7 +533,7 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
|
||||||
end = f.crit.ToBlock.Int64()
|
end = f.crit.ToBlock.Int64()
|
||||||
}
|
}
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics)
|
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit)
|
||||||
}
|
}
|
||||||
// Run the filter and return all the logs
|
// Run the filter and return all the logs
|
||||||
logs, err := filter.Logs(ctx)
|
logs, err := filter.Logs(ctx)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package filters
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -44,15 +45,17 @@ type Filter struct {
|
||||||
begin, end int64 // Range interval if filtering multiple blocks
|
begin, end int64 // Range interval if filtering multiple blocks
|
||||||
|
|
||||||
rangeLogsTestHook chan rangeLogsTestEvent
|
rangeLogsTestHook chan rangeLogsTestEvent
|
||||||
|
rangeLimit uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
|
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
|
||||||
// figure out whether a particular block is interesting or not.
|
// figure out whether a particular block is interesting or not.
|
||||||
func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
|
func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash, rangeLimit uint64) *Filter {
|
||||||
// Create a generic filter and convert it into a range filter
|
// Create a generic filter and convert it into a range filter
|
||||||
filter := newFilter(sys, addresses, topics)
|
filter := newFilter(sys, addresses, topics)
|
||||||
filter.begin = begin
|
filter.begin = begin
|
||||||
filter.end = end
|
filter.end = end
|
||||||
|
filter.rangeLimit = rangeLimit
|
||||||
|
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +146,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if f.rangeLimit != 0 && (end-begin) > f.rangeLimit {
|
||||||
|
return nil, fmt.Errorf("exceed maximum block range: %d", f.rangeLimit)
|
||||||
|
}
|
||||||
return f.rangeLogs(ctx, begin, end)
|
return f.rangeLogs(ctx, begin, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,7 +500,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ
|
||||||
|
|
||||||
// filterLogs creates a slice of logs matching the given criteria.
|
// filterLogs creates a slice of logs matching the given criteria.
|
||||||
func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
|
func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
|
||||||
var check = func(log *types.Log) bool {
|
check := func(log *types.Log) bool {
|
||||||
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
|
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ type Config struct {
|
||||||
LogCacheSize int // maximum number of cached blocks (default: 32)
|
LogCacheSize int // maximum number of cached blocks (default: 32)
|
||||||
Timeout time.Duration // how long filters stay active (default: 5min)
|
Timeout time.Duration // how long filters stay active (default: 5min)
|
||||||
LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000)
|
LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000)
|
||||||
|
RangeLimit uint64 // maximum block range allowed in filter criteria (default: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg Config) withDefaults() Config {
|
func (cfg Config) withDefaults() Config {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ func benchmarkFilters(b *testing.B, history uint64, noHistory bool) {
|
||||||
backend.startFilterMaps(history, noHistory, filtermaps.DefaultParams)
|
backend.startFilterMaps(history, noHistory, filtermaps.DefaultParams)
|
||||||
defer backend.stopFilterMaps()
|
defer backend.stopFilterMaps()
|
||||||
|
|
||||||
filter := sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{addr1, addr2, addr3, addr4}, nil)
|
filter := sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{addr1, addr2, addr3, addr4}, nil, 0)
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
filter.begin = 0
|
filter.begin = 0
|
||||||
logs, _ := filter.Logs(context.Background())
|
logs, _ := filter.Logs(context.Background())
|
||||||
|
|
@ -317,70 +317,70 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}),
|
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}, 0),
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}),
|
f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}),
|
f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}, 0),
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}),
|
f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}, 0),
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}),
|
f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}, 0),
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}),
|
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil),
|
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}),
|
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
||||||
err: "safe header not found",
|
err: "safe header not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil, 0),
|
||||||
err: "safe header not found",
|
err: "safe header not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil, 0),
|
||||||
err: "safe header not found",
|
err: "safe header not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil, 0),
|
||||||
err: errPendingLogsUnsupported.Error(),
|
err: errPendingLogsUnsupported.Error(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil, 0),
|
||||||
err: errPendingLogsUnsupported.Error(),
|
err: errPendingLogsUnsupported.Error(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
||||||
err: errPendingLogsUnsupported.Error(),
|
err: errPendingLogsUnsupported.Error(),
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
|
@ -403,7 +403,7 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("timeout", func(t *testing.T) {
|
t.Run("timeout", func(t *testing.T) {
|
||||||
f := sys.NewRangeFilter(0, rpc.LatestBlockNumber.Int64(), nil, nil)
|
f := sys.NewRangeFilter(0, rpc.LatestBlockNumber.Int64(), nil, nil, 0)
|
||||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour))
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_, err := f.Logs(ctx)
|
_, err := f.Logs(ctx)
|
||||||
|
|
@ -464,7 +464,7 @@ func TestRangeLogs(t *testing.T) {
|
||||||
newFilter := func(begin, end int64) {
|
newFilter := func(begin, end int64) {
|
||||||
testCase++
|
testCase++
|
||||||
event = 0
|
event = 0
|
||||||
filter = sys.NewRangeFilter(begin, end, addresses, nil)
|
filter = sys.NewRangeFilter(begin, end, addresses, nil, 0)
|
||||||
filter.rangeLogsTestHook = make(chan rangeLogsTestEvent)
|
filter.rangeLogsTestHook = make(chan rangeLogsTestEvent)
|
||||||
go func(filter *Filter) {
|
go func(filter *Filter) {
|
||||||
filter.Logs(context.Background())
|
filter.Logs(context.Background())
|
||||||
|
|
@ -601,3 +601,39 @@ func TestRangeLogs(t *testing.T) {
|
||||||
expEvent(rangeLogsTestReorg, 400, 901)
|
expEvent(rangeLogsTestReorg, 400, 901)
|
||||||
expEvent(rangeLogsTestDone, 0, 0)
|
expEvent(rangeLogsTestDone, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRangeLimit(t *testing.T) {
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
backend, sys := newTestFilterSystem(db, Config{})
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
gspec := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{},
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
}
|
||||||
|
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {})
|
||||||
|
options := core.DefaultConfig().WithStateScheme(rawdb.HashScheme)
|
||||||
|
options.TxLookupLimit = 0
|
||||||
|
bc, err := core.NewBlockChain(db, gspec, ethash.NewFaker(), options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = bc.InsertChain(chain)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
backend.startFilterMaps(0, false, filtermaps.DefaultParams)
|
||||||
|
defer backend.stopFilterMaps()
|
||||||
|
|
||||||
|
// Set rangeLimit to 5, but request a range of 9 (end - begin = 9, from 0 to 9)
|
||||||
|
filter := sys.NewRangeFilter(0, 9, nil, nil, 5)
|
||||||
|
_, err = filter.Logs(context.Background())
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "exceed maximum block range") {
|
||||||
|
t.Fatalf("expected range limit error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sampleNumber = 3 // Number of transactions sampled in a block
|
const sampleNumber = 3 // Number of transactions sampled in a block
|
||||||
|
|
@ -257,12 +258,12 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit
|
||||||
sortedTxs := make([]*types.Transaction, len(txs))
|
sortedTxs := make([]*types.Transaction, len(txs))
|
||||||
copy(sortedTxs, txs)
|
copy(sortedTxs, txs)
|
||||||
baseFee := block.BaseFee()
|
baseFee := block.BaseFee()
|
||||||
|
baseFee256 := new(uint256.Int)
|
||||||
|
if baseFee != nil {
|
||||||
|
baseFee256.SetFromBig(baseFee)
|
||||||
|
}
|
||||||
slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int {
|
slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int {
|
||||||
// It's okay to discard the error because a tx would never be
|
return a.EffectiveGasTipCmp(b, baseFee256)
|
||||||
// accepted into a block with an invalid effective tip.
|
|
||||||
tip1, _ := a.EffectiveGasTip(baseFee)
|
|
||||||
tip2, _ := b.EffectiveGasTip(baseFee)
|
|
||||||
return tip1.Cmp(tip2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
var prices []*big.Int
|
var prices []*big.Int
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/internal/testrand"
|
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -41,7 +42,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHashing(t *testing.T) {
|
func TestHashing(t *testing.T) {
|
||||||
|
|
@ -55,7 +55,7 @@ func TestHashing(t *testing.T) {
|
||||||
}
|
}
|
||||||
var want, got string
|
var want, got string
|
||||||
var old = func() {
|
var old = func() {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
for i := 0; i < len(bytecodes); i++ {
|
for i := 0; i < len(bytecodes); i++ {
|
||||||
hasher.Reset()
|
hasher.Reset()
|
||||||
hasher.Write(bytecodes[i])
|
hasher.Write(bytecodes[i])
|
||||||
|
|
@ -88,7 +88,7 @@ func BenchmarkHashing(b *testing.B) {
|
||||||
bytecodes[i] = buf
|
bytecodes[i] = buf
|
||||||
}
|
}
|
||||||
var old = func() {
|
var old = func() {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
for i := 0; i < len(bytecodes); i++ {
|
for i := 0; i < len(bytecodes); i++ {
|
||||||
hasher.Reset()
|
hasher.Reset()
|
||||||
hasher.Write(bytecodes[i])
|
hasher.Write(bytecodes[i])
|
||||||
|
|
|
||||||
|
|
@ -1373,3 +1373,350 @@ func TestStandardTraceBlockToFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTraceBadBlock(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
accounts = newAccounts(2)
|
||||||
|
storageContract = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
|
||||||
|
signer = types.HomesteadSigner{}
|
||||||
|
txHashs = make([]common.Hash, 0, 2)
|
||||||
|
genesis = &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
storageContract: {
|
||||||
|
Nonce: 1,
|
||||||
|
Balance: big.NewInt(0),
|
||||||
|
Code: []byte{
|
||||||
|
byte(vm.PUSH1), 0x2a, // push 42
|
||||||
|
byte(vm.PUSH1), 0x00, // push slot 0
|
||||||
|
byte(vm.SSTORE), // sstore(0, 42)
|
||||||
|
byte(vm.STOP),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
|
||||||
|
// tx 0: plain transfer
|
||||||
|
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Gas: params.TxGas,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil}),
|
||||||
|
signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
txHashs = append(txHashs, tx.Hash())
|
||||||
|
|
||||||
|
// tx 1: call storage contract (executes PUSH1, PUSH1, SSTORE, STOP)
|
||||||
|
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 1,
|
||||||
|
To: &storageContract,
|
||||||
|
Value: big.NewInt(0),
|
||||||
|
Gas: 50000,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil}),
|
||||||
|
signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
txHashs = append(txHashs, tx.Hash())
|
||||||
|
})
|
||||||
|
defer backend.teardown()
|
||||||
|
|
||||||
|
// Write the block as a bad block so parent state is available
|
||||||
|
block := backend.chain.GetBlockByNumber(1)
|
||||||
|
rawdb.WriteBadBlock(backend.chaindb, block)
|
||||||
|
|
||||||
|
api := NewAPI(backend)
|
||||||
|
result, err := api.TraceBadBlock(context.Background(), block.Hash(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("want no error, have %v", err)
|
||||||
|
}
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Fatalf("expected 2 tx traces, got %d", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First tx: plain transfer
|
||||||
|
have, _ := json.Marshal(result)
|
||||||
|
var traces []struct {
|
||||||
|
TxHash common.Hash `json:"txHash"`
|
||||||
|
Result struct {
|
||||||
|
Gas uint64 `json:"gas"`
|
||||||
|
Failed bool `json:"failed"`
|
||||||
|
StructLogs []json.RawMessage `json:"structLogs"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(have, &traces); err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal traces: %v", err)
|
||||||
|
}
|
||||||
|
if traces[0].TxHash != txHashs[0] {
|
||||||
|
t.Errorf("tx 0: hash mismatch, have %v, want %v", traces[0].TxHash, txHashs[0])
|
||||||
|
}
|
||||||
|
if traces[0].Result.Gas != params.TxGas {
|
||||||
|
t.Errorf("tx 0: gas mismatch, have %d, want %d", traces[0].Result.Gas, params.TxGas)
|
||||||
|
}
|
||||||
|
if len(traces[0].Result.StructLogs) != 0 {
|
||||||
|
t.Errorf("tx 0: expected empty structLogs for plain transfer, got %d entries", len(traces[0].Result.StructLogs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second tx: contract call
|
||||||
|
if traces[1].TxHash != txHashs[1] {
|
||||||
|
t.Errorf("tx 1: hash mismatch, have %v, want %v", traces[1].TxHash, txHashs[1])
|
||||||
|
}
|
||||||
|
if traces[1].Result.Failed {
|
||||||
|
t.Error("tx 1: expected success, got failed")
|
||||||
|
}
|
||||||
|
// Contract has 4 opcodes: PUSH1, PUSH1, SSTORE, STOP
|
||||||
|
if len(traces[1].Result.StructLogs) != 4 {
|
||||||
|
t.Errorf("tx 1: expected 4 structLog entries for contract call, got %d", len(traces[1].Result.StructLogs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-existent bad block
|
||||||
|
_, err = api.TraceBadBlock(context.Background(), common.Hash{42}, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("want error for non-existent bad block, have none")
|
||||||
|
}
|
||||||
|
wantErr := fmt.Sprintf("bad block %#x not found", common.Hash{42})
|
||||||
|
if err.Error() != wantErr {
|
||||||
|
t.Errorf("error mismatch, want '%s', have '%v'", wantErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntermediateRoots(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Initialize test accounts and a contract that writes to storage.
|
||||||
|
var (
|
||||||
|
accounts = newAccounts(2)
|
||||||
|
storageContract = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
|
||||||
|
signer = types.HomesteadSigner{}
|
||||||
|
genesis = &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
// Contract: SSTORE(CALLVALUE, CALLVALUE)
|
||||||
|
storageContract: {
|
||||||
|
Nonce: 1,
|
||||||
|
Balance: big.NewInt(0),
|
||||||
|
Code: []byte{
|
||||||
|
byte(vm.CALLVALUE),
|
||||||
|
byte(vm.CALLVALUE),
|
||||||
|
byte(vm.SSTORE),
|
||||||
|
byte(vm.STOP),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
|
||||||
|
// tx 0: plain transfer
|
||||||
|
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Gas: params.TxGas,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil}),
|
||||||
|
signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// tx 1: sstore(1, 1)
|
||||||
|
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 1,
|
||||||
|
To: &storageContract,
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
Gas: 50000,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil}),
|
||||||
|
signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// tx 2: sstore(2, 2)
|
||||||
|
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 2,
|
||||||
|
To: &storageContract,
|
||||||
|
Value: big.NewInt(2),
|
||||||
|
Gas: 50000,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil}),
|
||||||
|
signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
})
|
||||||
|
defer backend.teardown()
|
||||||
|
|
||||||
|
api := NewAPI(backend)
|
||||||
|
block := backend.chain.GetBlockByNumber(1)
|
||||||
|
|
||||||
|
// Should return one root per tx
|
||||||
|
roots, err := api.IntermediateRoots(context.Background(), block.Hash(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("want no error, have %v", err)
|
||||||
|
}
|
||||||
|
if len(roots) != 3 {
|
||||||
|
t.Fatalf("root count mismatch, have %d, want 3", len(roots))
|
||||||
|
}
|
||||||
|
for i, root := range roots {
|
||||||
|
if root == (common.Hash{}) {
|
||||||
|
t.Errorf("root[%d] should not be zero", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if roots[0] == roots[1] {
|
||||||
|
t.Error("root[0] and root[1] should differ (transfer vs sstore)")
|
||||||
|
}
|
||||||
|
if roots[1] == roots[2] {
|
||||||
|
t.Error("root[1] and root[2] should differ (sstore to different slots)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate roots of a bad block
|
||||||
|
rawdb.WriteBadBlock(backend.chaindb, block)
|
||||||
|
badRoots, err := api.IntermediateRoots(context.Background(), block.Hash(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("want no error for bad block fallback, have %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(roots, badRoots) {
|
||||||
|
t.Errorf("bad block roots mismatch, have %v, want %v", badRoots, roots)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genesis block: should return error
|
||||||
|
genesisBlock := backend.chain.GetBlockByNumber(0)
|
||||||
|
_, err = api.IntermediateRoots(context.Background(), genesisBlock.Hash(), nil)
|
||||||
|
if err == nil || err.Error() != "genesis is not traceable" {
|
||||||
|
t.Fatalf("want 'genesis is not traceable' error, have %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-existent block: should return error
|
||||||
|
_, err = api.IntermediateRoots(context.Background(), common.Hash{42}, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("want error for non-existent block, have none")
|
||||||
|
}
|
||||||
|
wantErr := fmt.Sprintf("block %#x not found", common.Hash{42})
|
||||||
|
if err.Error() != wantErr {
|
||||||
|
t.Errorf("error mismatch, want '%s', have '%v'", wantErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStandardTraceBadBlockToFile(t *testing.T) {
|
||||||
|
var (
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
funds = big.NewInt(1000000000000000)
|
||||||
|
|
||||||
|
aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43")
|
||||||
|
aaCode = []byte{byte(vm.PUSH1), 0x00, byte(vm.POP)}
|
||||||
|
|
||||||
|
bb = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f44")
|
||||||
|
bbCode = []byte{byte(vm.PUSH2), 0x00, 0x01, byte(vm.POP)}
|
||||||
|
)
|
||||||
|
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
address: {Balance: funds},
|
||||||
|
aa: {
|
||||||
|
Code: aaCode,
|
||||||
|
Nonce: 1,
|
||||||
|
Balance: big.NewInt(0),
|
||||||
|
},
|
||||||
|
bb: {
|
||||||
|
Code: bbCode,
|
||||||
|
Nonce: 1,
|
||||||
|
Balance: big.NewInt(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
txHashs := make([]common.Hash, 0, 2)
|
||||||
|
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
|
||||||
|
b.SetCoinbase(common.Address{1})
|
||||||
|
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
To: &aa,
|
||||||
|
Value: big.NewInt(0),
|
||||||
|
Gas: 50000,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil,
|
||||||
|
}), types.HomesteadSigner{}, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
txHashs = append(txHashs, tx.Hash())
|
||||||
|
|
||||||
|
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 1,
|
||||||
|
To: &bb,
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
Gas: 100000,
|
||||||
|
GasPrice: b.BaseFee(),
|
||||||
|
Data: nil,
|
||||||
|
}), types.HomesteadSigner{}, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
txHashs = append(txHashs, tx.Hash())
|
||||||
|
})
|
||||||
|
defer backend.teardown()
|
||||||
|
|
||||||
|
// Write the block as a bad block
|
||||||
|
block := backend.chain.GetBlockByNumber(1)
|
||||||
|
rawdb.WriteBadBlock(backend.chaindb, block)
|
||||||
|
|
||||||
|
var testSuite = []struct {
|
||||||
|
config *StdTraceConfig
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// All txs traced
|
||||||
|
config: nil,
|
||||||
|
want: []string{
|
||||||
|
`{"pc":0,"op":96,"gas":"0x7148","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
|
||||||
|
{"pc":2,"op":80,"gas":"0x7145","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP"}
|
||||||
|
{"pc":3,"op":0,"gas":"0x7143","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
|
||||||
|
{"output":"","gasUsed":"0x5"}
|
||||||
|
`,
|
||||||
|
`{"pc":0,"op":97,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}
|
||||||
|
{"pc":3,"op":80,"gas":"0x13495","gasCost":"0x2","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"POP"}
|
||||||
|
{"pc":4,"op":0,"gas":"0x13493","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
|
||||||
|
{"output":"","gasUsed":"0x5"}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Specific tx traced
|
||||||
|
config: &StdTraceConfig{TxHash: txHashs[1]},
|
||||||
|
want: []string{
|
||||||
|
`{"pc":0,"op":97,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}
|
||||||
|
{"pc":3,"op":80,"gas":"0x13495","gasCost":"0x2","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"POP"}
|
||||||
|
{"pc":4,"op":0,"gas":"0x13493","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
|
||||||
|
{"output":"","gasUsed":"0x5"}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
api := NewAPI(backend)
|
||||||
|
for i, tc := range testSuite {
|
||||||
|
txTraces, err := api.StandardTraceBadBlockToFile(context.Background(), block.Hash(), tc.config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test %d: unexpected error %v", i, err)
|
||||||
|
}
|
||||||
|
if len(txTraces) != len(tc.want) {
|
||||||
|
t.Fatalf("test %d: file count mismatch, have %d, want %d", i, len(txTraces), len(tc.want))
|
||||||
|
}
|
||||||
|
for j, traceFileName := range txTraces {
|
||||||
|
defer os.Remove(traceFileName)
|
||||||
|
traceReceived, err := os.ReadFile(traceFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test %d: could not read trace file: %v", i, err)
|
||||||
|
}
|
||||||
|
if tc.want[j] != string(traceReceived) {
|
||||||
|
t.Fatalf("test %d, trace %d: result mismatch\nhave:\n%s\nwant:\n%s", i, j, string(traceReceived), tc.want[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-existent bad block
|
||||||
|
_, err := api.StandardTraceBadBlockToFile(context.Background(), common.Hash{42}, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("want error for non-existent bad block, have none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ type callLog struct {
|
||||||
Address common.Address `json:"address"`
|
Address common.Address `json:"address"`
|
||||||
Topics []common.Hash `json:"topics"`
|
Topics []common.Hash `json:"topics"`
|
||||||
Data hexutil.Bytes `json:"data"`
|
Data hexutil.Bytes `json:"data"`
|
||||||
|
Index hexutil.Uint `json:"index"`
|
||||||
Position hexutil.Uint `json:"position"`
|
Position hexutil.Uint `json:"position"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +301,7 @@ func TestInternals(t *testing.T) {
|
||||||
byte(vm.LOG0),
|
byte(vm.LOG0),
|
||||||
},
|
},
|
||||||
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
|
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
|
||||||
want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
|
want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","index":"0x0","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Leads to OOM on the prestate tracer
|
// Leads to OOM on the prestate tracer
|
||||||
|
|
|
||||||
653
eth/tracers/internal/tracetest/selfdestruct_state_test.go
Normal file
653
eth/tracers/internal/tracetest/selfdestruct_state_test.go
Normal file
|
|
@ -0,0 +1,653 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package tracetest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
"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/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// accountState represents the expected final state of an account
|
||||||
|
type accountState struct {
|
||||||
|
Balance *big.Int
|
||||||
|
Nonce uint64
|
||||||
|
Code []byte
|
||||||
|
Exists bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// selfdestructStateTracer tracks state changes during selfdestruct operations
|
||||||
|
type selfdestructStateTracer struct {
|
||||||
|
env *tracing.VMContext
|
||||||
|
accounts map[common.Address]*accountState
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSelfdestructStateTracer() *selfdestructStateTracer {
|
||||||
|
return &selfdestructStateTracer{
|
||||||
|
accounts: make(map[common.Address]*accountState),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
|
||||||
|
t.env = env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) OnTxEnd(receipt *types.Receipt, err error) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) getOrCreateAccount(addr common.Address) *accountState {
|
||||||
|
if acc, ok := t.accounts[addr]; ok {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with current state from statedb
|
||||||
|
acc := &accountState{
|
||||||
|
Balance: t.env.StateDB.GetBalance(addr).ToBig(),
|
||||||
|
Nonce: t.env.StateDB.GetNonce(addr),
|
||||||
|
Code: t.env.StateDB.GetCode(addr),
|
||||||
|
Exists: t.env.StateDB.Exist(addr),
|
||||||
|
}
|
||||||
|
t.accounts[addr] = acc
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) OnBalanceChange(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
|
||||||
|
acc := t.getOrCreateAccount(addr)
|
||||||
|
acc.Balance = new
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) OnNonceChangeV2(addr common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
|
||||||
|
acc := t.getOrCreateAccount(addr)
|
||||||
|
acc.Nonce = new
|
||||||
|
|
||||||
|
// If this is a selfdestruct nonce change, mark account as not existing
|
||||||
|
if reason == tracing.NonceChangeSelfdestruct {
|
||||||
|
acc.Exists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
|
||||||
|
acc := t.getOrCreateAccount(addr)
|
||||||
|
acc.Code = code
|
||||||
|
|
||||||
|
// If this is a selfdestruct code change, mark account as not existing
|
||||||
|
if reason == tracing.CodeChangeSelfDestruct {
|
||||||
|
acc.Exists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) Hooks() *tracing.Hooks {
|
||||||
|
return &tracing.Hooks{
|
||||||
|
OnTxStart: t.OnTxStart,
|
||||||
|
OnTxEnd: t.OnTxEnd,
|
||||||
|
OnBalanceChange: t.OnBalanceChange,
|
||||||
|
OnNonceChangeV2: t.OnNonceChangeV2,
|
||||||
|
OnCodeChangeV2: t.OnCodeChangeV2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *selfdestructStateTracer) Accounts() map[common.Address]*accountState {
|
||||||
|
return t.accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyAccountState compares actual and expected account state and reports any mismatches
|
||||||
|
func verifyAccountState(t *testing.T, addr common.Address, actual, expected *accountState) {
|
||||||
|
if actual.Balance.Cmp(expected.Balance) != 0 {
|
||||||
|
t.Errorf("address %s: balance mismatch: have %s, want %s",
|
||||||
|
addr.Hex(), actual.Balance, expected.Balance)
|
||||||
|
}
|
||||||
|
if actual.Nonce != expected.Nonce {
|
||||||
|
t.Errorf("address %s: nonce mismatch: have %d, want %d",
|
||||||
|
addr.Hex(), actual.Nonce, expected.Nonce)
|
||||||
|
}
|
||||||
|
if len(actual.Code) != len(expected.Code) {
|
||||||
|
t.Errorf("address %s: code length mismatch: have %d, want %d",
|
||||||
|
addr.Hex(), len(actual.Code), len(expected.Code))
|
||||||
|
}
|
||||||
|
if actual.Exists != expected.Exists {
|
||||||
|
t.Errorf("address %s: exists mismatch: have %v, want %v",
|
||||||
|
addr.Hex(), actual.Exists, expected.Exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupTestBlockchain creates a blockchain with the given genesis and transaction,
|
||||||
|
// returns the blockchain, the first block, and a statedb at genesis for testing
|
||||||
|
func setupTestBlockchain(t *testing.T, genesis *core.Genesis, tx *types.Transaction, useBeacon bool) (*core.BlockChain, *types.Block, *state.StateDB) {
|
||||||
|
var engine consensus.Engine
|
||||||
|
if useBeacon {
|
||||||
|
engine = beacon.New(ethash.NewFaker())
|
||||||
|
} else {
|
||||||
|
engine = ethash.NewFaker()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 1, func(i int, b *core.BlockGen) {
|
||||||
|
b.AddTx(tx)
|
||||||
|
})
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
blockchain, err := core.NewBlockChain(db, genesis, engine, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create blockchain: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := blockchain.InsertChain(blocks); err != nil {
|
||||||
|
t.Fatalf("failed to insert chain: %v", err)
|
||||||
|
}
|
||||||
|
genesisBlock := blockchain.GetBlockByNumber(0)
|
||||||
|
if genesisBlock == nil {
|
||||||
|
t.Fatalf("failed to get genesis block")
|
||||||
|
}
|
||||||
|
statedb, err := blockchain.StateAt(genesisBlock.Root())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockchain, blocks[0], statedb
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelfdestructStateTracer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Gas limit high enough for all test scenarios (factory creation + multiple calls)
|
||||||
|
testGasLimit = 500000
|
||||||
|
|
||||||
|
// Common balance amounts used across tests
|
||||||
|
testBalanceInitial = 100 // Initial balance for contracts being tested
|
||||||
|
testBalanceSent = 50 // Amount sent back in sendback tests
|
||||||
|
testBalanceFactory = 200 // Factory needs extra balance for contract creation
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper to create *big.Int for wei amounts
|
||||||
|
wei := func(amount int64) *big.Int {
|
||||||
|
return big.NewInt(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test account (transaction sender)
|
||||||
|
var (
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
caller = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simple selfdestruct test contracts
|
||||||
|
var (
|
||||||
|
contract = common.HexToAddress("0x00000000000000000000000000000000000000bb")
|
||||||
|
recipient = common.HexToAddress("0x00000000000000000000000000000000000000cc")
|
||||||
|
)
|
||||||
|
// Build selfdestruct code: PUSH20 <recipient> SELFDESTRUCT
|
||||||
|
selfdestructCode := []byte{byte(vm.PUSH20)}
|
||||||
|
selfdestructCode = append(selfdestructCode, recipient.Bytes()...)
|
||||||
|
selfdestructCode = append(selfdestructCode, byte(vm.SELFDESTRUCT))
|
||||||
|
|
||||||
|
// Factory test contracts (create-and-destroy pattern)
|
||||||
|
var (
|
||||||
|
factory = common.HexToAddress("0x00000000000000000000000000000000000000ff")
|
||||||
|
)
|
||||||
|
// Factory code: creates a contract with 100 wei and calls it to trigger selfdestruct back to factory
|
||||||
|
// See selfdestruct_test_contracts/factory.yul for source
|
||||||
|
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris factory.yul --bin
|
||||||
|
// (Using paris to avoid PUSH0 opcode which is not available pre-Shanghai)
|
||||||
|
var (
|
||||||
|
factoryCode = common.Hex2Bytes("6a6133ff6000526002601ef360a81b600052600080808080600b816064f05af100")
|
||||||
|
createdContractAddr = crypto.CreateAddress(factory, 0) // Address where factory creates the contract
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sendback test contracts (A→B→A pattern)
|
||||||
|
// For the refund test: Coordinator calls A, then B
|
||||||
|
// A selfdestructs to B, B sends funds back to A
|
||||||
|
var (
|
||||||
|
contractA = common.HexToAddress("0x00000000000000000000000000000000000000aa")
|
||||||
|
contractB = common.HexToAddress("0x00000000000000000000000000000000000000bb")
|
||||||
|
coordinator = common.HexToAddress("0x00000000000000000000000000000000000000cc")
|
||||||
|
)
|
||||||
|
// Contract A: if msg.value > 0, accept funds; else selfdestruct to B
|
||||||
|
// See selfdestruct_test_contracts/contractA.yul for source
|
||||||
|
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris contractA.yul --bin
|
||||||
|
contractACode := common.Hex2Bytes("60003411600a5760bbff5b00")
|
||||||
|
|
||||||
|
// Contract B: sends 50 wei back to contract A
|
||||||
|
// See selfdestruct_test_contracts/contractB.yul for source
|
||||||
|
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris contractB.yul --bin
|
||||||
|
contractBCode := common.Hex2Bytes("6000808080603260aa5af100")
|
||||||
|
|
||||||
|
// Coordinator: calls A (A selfdestructs to B), then calls B (B sends funds to A)
|
||||||
|
// See selfdestruct_test_contracts/coordinator.yul for source
|
||||||
|
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris coordinator.yul --bin
|
||||||
|
coordinatorCode := common.Hex2Bytes("60008080808060aa818080808060bb955af1505af100")
|
||||||
|
|
||||||
|
// Factory for create-and-refund test: creates A with 100 wei, calls A, calls B
|
||||||
|
// See selfdestruct_test_contracts/factoryRefund.yul for source
|
||||||
|
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris factoryRefund.yul --bin
|
||||||
|
var (
|
||||||
|
factoryRefund = common.HexToAddress("0x00000000000000000000000000000000000000dd")
|
||||||
|
factoryRefundCode = common.Hex2Bytes("60008080808060bb78600c600d600039600c6000f3fe60003411600a5760bbff5b0082528180808080601960076064f05af1505af100")
|
||||||
|
createdContractAddrA = crypto.CreateAddress(factoryRefund, 0) // Address where factory creates contract A
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self-destruct-to-self test contracts
|
||||||
|
var (
|
||||||
|
contractSelfDestruct = common.HexToAddress("0x00000000000000000000000000000000000000aa")
|
||||||
|
coordinatorSendAfter = common.HexToAddress("0x00000000000000000000000000000000000000ee")
|
||||||
|
)
|
||||||
|
// Contract that selfdestructs to self
|
||||||
|
// See selfdestruct_test_contracts/contractSelfDestruct.yul
|
||||||
|
contractSelfDestructCode := common.Hex2Bytes("30ff")
|
||||||
|
|
||||||
|
// Coordinator: calls contract (triggers selfdestruct to self), stores balance, sends 50 wei, stores balance again
|
||||||
|
// See selfdestruct_test_contracts/coordinatorSendAfter.yul
|
||||||
|
coordinatorSendAfterCode := common.Hex2Bytes("60aa600080808080855af150803160005560008080806032855af1503160015500")
|
||||||
|
|
||||||
|
// Factory with balance checking: creates contract, calls it, checks balances
|
||||||
|
// See selfdestruct_test_contracts/factorySelfDestructBalanceCheck.yul
|
||||||
|
var (
|
||||||
|
factorySelfDestructBalanceCheck = common.HexToAddress("0x00000000000000000000000000000000000000fd")
|
||||||
|
factorySelfDestructBalanceCheckCode = common.Hex2Bytes("6e6002600d60003960026000f3fe30ff600052600f60116064f0600080808080855af150803160005560008080806032855af1503160015500")
|
||||||
|
createdContractAddrSelfBalanceCheck = crypto.CreateAddress(factorySelfDestructBalanceCheck, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
targetContract common.Address
|
||||||
|
genesis *core.Genesis
|
||||||
|
useBeacon bool
|
||||||
|
expectedResults map[common.Address]accountState
|
||||||
|
expectedStorage map[common.Address]map[uint64]*big.Int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pre_6780_existing",
|
||||||
|
description: "Pre-EIP-6780: Existing contract selfdestructs to recipient. Contract should be destroyed and balance transferred.",
|
||||||
|
targetContract: contract,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllEthashProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
contract: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Code: selfdestructCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: false,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
contract: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
recipient: {
|
||||||
|
Balance: wei(testBalanceInitial), // Received contract's balance
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post_6780_existing",
|
||||||
|
description: "Post-EIP-6780: Existing contract selfdestructs to recipient. Balance transferred but contract NOT destroyed (code/storage remain).",
|
||||||
|
targetContract: contract,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
contract: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Code: selfdestructCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: true,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
contract: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: selfdestructCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
recipient: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre_6780_create_destroy",
|
||||||
|
description: "Pre-EIP-6780: Factory creates contract with 100 wei, contract selfdestructs back to factory. Contract destroyed, factory gets refund.",
|
||||||
|
targetContract: factory,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllEthashProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
factory: {
|
||||||
|
Balance: wei(testBalanceFactory),
|
||||||
|
Code: factoryCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: false,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
factory: {
|
||||||
|
Balance: wei(testBalanceFactory),
|
||||||
|
Nonce: 1,
|
||||||
|
Code: factoryCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
createdContractAddr: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post_6780_create_destroy",
|
||||||
|
description: "Post-EIP-6780: Factory creates contract with 100 wei, contract selfdestructs back to factory. Contract destroyed (EIP-6780 exception for same-tx creation).",
|
||||||
|
targetContract: factory,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
factory: {
|
||||||
|
Balance: wei(testBalanceFactory),
|
||||||
|
Code: factoryCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: true,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
factory: {
|
||||||
|
Balance: wei(testBalanceFactory),
|
||||||
|
Nonce: 1,
|
||||||
|
Code: factoryCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
createdContractAddr: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre_6780_sendback",
|
||||||
|
description: "Pre-EIP-6780: Contract A selfdestructs sending funds to B, then B sends funds back to A's address. Funds sent to destroyed address are burnt.",
|
||||||
|
targetContract: coordinator,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllEthashProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
contractA: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Code: contractACode,
|
||||||
|
},
|
||||||
|
contractB: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Code: contractBCode,
|
||||||
|
},
|
||||||
|
coordinator: {
|
||||||
|
Code: coordinatorCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: false,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
contractA: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
contractB: {
|
||||||
|
// 100 received - 50 sent back
|
||||||
|
Balance: wei(testBalanceSent),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: contractBCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post_6780_existing_sendback",
|
||||||
|
description: "Post-EIP-6780: Existing contract A selfdestructs to B, then B sends funds back to A. Funds are NOT burnt (A still exists post-6780).",
|
||||||
|
targetContract: coordinator,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
contractA: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Code: contractACode,
|
||||||
|
},
|
||||||
|
contractB: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Code: contractBCode,
|
||||||
|
},
|
||||||
|
coordinator: {
|
||||||
|
Code: coordinatorCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: true,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
contractA: {
|
||||||
|
Balance: wei(testBalanceSent),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: contractACode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
contractB: {
|
||||||
|
Balance: wei(testBalanceSent),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: contractBCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post_6780_create_destroy_sendback",
|
||||||
|
description: "Post-EIP-6780: Factory creates A, A selfdestructs to B, B sends funds back to A. Funds are burnt (A was destroyed via EIP-6780 exception).",
|
||||||
|
targetContract: factoryRefund,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
contractB: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Code: contractBCode,
|
||||||
|
},
|
||||||
|
factoryRefund: {
|
||||||
|
Balance: wei(testBalanceFactory),
|
||||||
|
Code: factoryRefundCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: true,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
createdContractAddrA: {
|
||||||
|
// Funds sent back are burnt!
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
contractB: {
|
||||||
|
Balance: wei(testBalanceSent),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: contractBCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post_6780_existing_to_self",
|
||||||
|
description: "Post-EIP-6780: Pre-existing contract selfdestructs to itself. Balance NOT burnt (selfdestruct-to-self is no-op for existing contracts).",
|
||||||
|
targetContract: coordinatorSendAfter,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
contractSelfDestruct: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Code: contractSelfDestructCode,
|
||||||
|
},
|
||||||
|
coordinatorSendAfter: {
|
||||||
|
Balance: wei(testBalanceInitial),
|
||||||
|
Code: coordinatorSendAfterCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: true,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
contractSelfDestruct: {
|
||||||
|
Balance: wei(150),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: contractSelfDestructCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
coordinatorSendAfter: {
|
||||||
|
Balance: wei(testBalanceSent),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: coordinatorSendAfterCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedStorage: map[common.Address]map[uint64]*big.Int{
|
||||||
|
coordinatorSendAfter: {
|
||||||
|
0: wei(testBalanceInitial),
|
||||||
|
1: wei(150),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post_6780_create_destroy_to_self",
|
||||||
|
description: "Post-EIP-6780: Factory creates contract, contract selfdestructs to itself. Balance IS burnt and contract destroyed (EIP-6780 exception for same-tx creation).",
|
||||||
|
targetContract: factorySelfDestructBalanceCheck,
|
||||||
|
genesis: &core.Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
caller: {Balance: big.NewInt(params.Ether)},
|
||||||
|
factorySelfDestructBalanceCheck: {
|
||||||
|
Balance: wei(testBalanceFactory),
|
||||||
|
Code: factorySelfDestructBalanceCheckCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useBeacon: true,
|
||||||
|
expectedResults: map[common.Address]accountState{
|
||||||
|
createdContractAddrSelfBalanceCheck: {
|
||||||
|
Balance: wei(0),
|
||||||
|
Nonce: 0,
|
||||||
|
Code: []byte{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
factorySelfDestructBalanceCheck: {
|
||||||
|
Balance: wei(testBalanceSent),
|
||||||
|
Nonce: 1,
|
||||||
|
Code: factorySelfDestructBalanceCheckCode,
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedStorage: map[common.Address]map[uint64]*big.Int{
|
||||||
|
factorySelfDestructBalanceCheck: {
|
||||||
|
0: wei(0),
|
||||||
|
1: wei(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
signer = types.HomesteadSigner{}
|
||||||
|
tx *types.Transaction
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
tx, err = types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
To: &tt.targetContract,
|
||||||
|
Value: big.NewInt(0),
|
||||||
|
Gas: testGasLimit,
|
||||||
|
GasPrice: big.NewInt(params.InitialBaseFee * 2),
|
||||||
|
Data: nil,
|
||||||
|
}), signer, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to sign transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockchain, block, statedb := setupTestBlockchain(t, tt.genesis, tx, tt.useBeacon)
|
||||||
|
defer blockchain.Stop()
|
||||||
|
|
||||||
|
tracer := newSelfdestructStateTracer()
|
||||||
|
hookedState := state.NewHookedState(statedb, tracer.Hooks())
|
||||||
|
msg, err := core.TransactionToMessage(tx, signer, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
|
}
|
||||||
|
context := core.NewEVMBlockContext(block.Header(), blockchain, nil)
|
||||||
|
evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()})
|
||||||
|
usedGas := uint64(0)
|
||||||
|
_, err = core.ApplyTransactionWithEVM(msg, new(core.GasPool).AddGas(tx.Gas()), statedb, block.Number(), block.Hash(), block.Time(), tx, &usedGas, evm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to execute transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := tracer.Accounts()
|
||||||
|
|
||||||
|
// Verify storage
|
||||||
|
for addr, expectedSlots := range tt.expectedStorage {
|
||||||
|
for slot, expectedValue := range expectedSlots {
|
||||||
|
actualValue := statedb.GetState(addr, common.BigToHash(big.NewInt(int64(slot))))
|
||||||
|
if actualValue.Big().Cmp(expectedValue) != 0 {
|
||||||
|
t.Errorf("address %s slot %d: storage mismatch: have %s, want %s",
|
||||||
|
addr.Hex(), slot, actualValue.Big(), expectedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify results
|
||||||
|
for addr, expected := range tt.expectedResults {
|
||||||
|
actual, ok := results[addr]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("address %s missing from results", addr.Hex())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
verifyAccountState(t, addr, actual, &expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
object "ContractA" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
// If receiving funds (msg.value > 0), just accept them and return
|
||||||
|
if gt(callvalue(), 0) {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, selfdestruct to B (transfers balance immediately, then stops execution)
|
||||||
|
let contractB := 0x00000000000000000000000000000000000000bb
|
||||||
|
selfdestruct(contractB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
object "ContractB" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
// Send 50 wei back to contract A
|
||||||
|
let contractA := 0x00000000000000000000000000000000000000aa
|
||||||
|
let success := call(gas(), contractA, 50, 0, 0, 0, 0)
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
object "ContractSelfDestruct" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
// Simply selfdestruct to self
|
||||||
|
selfdestruct(address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
object "Coordinator" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
let contractA := 0x00000000000000000000000000000000000000aa
|
||||||
|
let contractB := 0x00000000000000000000000000000000000000bb
|
||||||
|
|
||||||
|
// First, call A (A will selfdestruct to B)
|
||||||
|
pop(call(gas(), contractA, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
// Then, call B (B will send funds back to A)
|
||||||
|
pop(call(gas(), contractB, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
object "CoordinatorSendAfter" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
let contractAddr := 0x00000000000000000000000000000000000000aa
|
||||||
|
|
||||||
|
// Call contract (triggers selfdestruct to self, burning its balance)
|
||||||
|
pop(call(gas(), contractAddr, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
// Check contract's balance immediately after selfdestruct
|
||||||
|
// Store in slot 0 to verify it's 0 (proving immediate burn)
|
||||||
|
sstore(0, balance(contractAddr))
|
||||||
|
|
||||||
|
// Send 50 wei to the contract (after it selfdestructed)
|
||||||
|
pop(call(gas(), contractAddr, 50, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
// Check balance again after sending funds
|
||||||
|
// Store in slot 1 to verify it's 50 (new funds not burnt)
|
||||||
|
sstore(1, balance(contractAddr))
|
||||||
|
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
object "FactoryRefund" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
let contractB := 0x00000000000000000000000000000000000000bb
|
||||||
|
|
||||||
|
// Store the deploy bytecode for contract A in memory
|
||||||
|
// Full deploy bytecode from: solc --strict-assembly --evm-version paris contractA.yul --bin
|
||||||
|
// Including the 0xfe separator: 600c600d600039600c6000f3fe60003411600a5760bbff5b00
|
||||||
|
// That's 25 bytes, padded to 32 bytes with 7 zero bytes at the front
|
||||||
|
mstore(0, 0x0000000000000000000000000000600c600d600039600c6000f3fe60003411600a5760bbff5b00)
|
||||||
|
|
||||||
|
// CREATE contract A with 100 wei, using 25 bytes starting at position 7
|
||||||
|
let contractA := create(100, 7, 25)
|
||||||
|
|
||||||
|
// Call contract A (triggers selfdestruct to B)
|
||||||
|
pop(call(gas(), contractA, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
// Call contract B (B sends 50 wei back to A)
|
||||||
|
pop(call(gas(), contractB, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
object "FactorySelfDestructBalanceCheck" {
|
||||||
|
code {
|
||||||
|
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||||
|
return(0, datasize("Runtime"))
|
||||||
|
}
|
||||||
|
object "Runtime" {
|
||||||
|
code {
|
||||||
|
// Get the full deploy bytecode for ContractSelfDestruct
|
||||||
|
// Compiled with: solc --strict-assembly --evm-version paris contractSelfDestruct.yul --bin
|
||||||
|
// Full bytecode: 6002600d60003960026000f3fe30ff
|
||||||
|
// That's 15 bytes total, padded to 32 bytes with 17 zero bytes at front
|
||||||
|
mstore(0, 0x0000000000000000000000000000000000000000006002600d60003960026000f3fe30ff)
|
||||||
|
|
||||||
|
// CREATE contract with 100 wei, using deploy bytecode
|
||||||
|
// The bytecode is 15 bytes, starts at position 17 in the 32-byte word
|
||||||
|
let contractAddr := create(100, 17, 15)
|
||||||
|
|
||||||
|
// Call the created contract (triggers selfdestruct to self)
|
||||||
|
pop(call(gas(), contractAddr, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
// Check contract's balance immediately after selfdestruct
|
||||||
|
// Store in slot 0 to verify it's 0 (proving immediate burn)
|
||||||
|
sstore(0, balance(contractAddr))
|
||||||
|
|
||||||
|
// Send 50 wei to the contract (after it selfdestructed)
|
||||||
|
pop(call(gas(), contractAddr, 50, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
// Check balance again after sending funds
|
||||||
|
// Store in slot 1 to verify it's 0 (funds sent to destroyed contract are burnt)
|
||||||
|
sstore(1, balance(contractAddr))
|
||||||
|
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -94,7 +94,8 @@
|
||||||
"0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda"
|
"0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda"
|
||||||
],
|
],
|
||||||
"data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598",
|
"data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598",
|
||||||
"position": "0x0"
|
"position": "0x0",
|
||||||
|
"index": "0x0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": "0x200edd17f30485a8735878661960cd7a9a95733f",
|
"address": "0x200edd17f30485a8735878661960cd7a9a95733f",
|
||||||
|
|
@ -102,7 +103,8 @@
|
||||||
"0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da"
|
"0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da"
|
||||||
],
|
],
|
||||||
"data": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"data": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"position": "0x0"
|
"position": "0x0",
|
||||||
|
"index": "0x1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"value": "0x8ac7230489e80000",
|
"value": "0x8ac7230489e80000",
|
||||||
|
|
@ -112,4 +114,4 @@
|
||||||
"value": "0x8ac7230489e80000",
|
"value": "0x8ac7230489e80000",
|
||||||
"type": "CALL"
|
"type": "CALL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -256,6 +256,7 @@
|
||||||
"0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5"
|
"0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5"
|
||||||
],
|
],
|
||||||
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
|
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
|
||||||
|
"index": "0x0",
|
||||||
"position": "0x0"
|
"position": "0x0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -278,6 +279,7 @@
|
||||||
"0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd"
|
"0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd"
|
||||||
],
|
],
|
||||||
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
|
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
|
||||||
|
"index": "0x1",
|
||||||
"position": "0x0"
|
"position": "0x0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -308,6 +310,7 @@
|
||||||
"0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd"
|
"0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd"
|
||||||
],
|
],
|
||||||
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
|
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
|
||||||
|
"index": "0x2",
|
||||||
"position": "0x0"
|
"position": "0x0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -330,6 +333,7 @@
|
||||||
"0x000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a06"
|
"0x000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a06"
|
||||||
],
|
],
|
||||||
"data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848",
|
"data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848",
|
||||||
|
"index": "0x3",
|
||||||
"position": "0x0"
|
"position": "0x0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -394,6 +398,7 @@
|
||||||
"0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add"
|
"0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add"
|
||||||
],
|
],
|
||||||
"data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000",
|
"data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000",
|
||||||
|
"index": "0x4",
|
||||||
"position": "0x0"
|
"position": "0x0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -408,4 +413,4 @@
|
||||||
"value": "0x0",
|
"value": "0x0",
|
||||||
"type": "CALL"
|
"type": "CALL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue