mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
Merge branch 'master' into bs/cell-blobpool/sparse-v2
This commit is contained in:
commit
b4c1a26367
101 changed files with 3392 additions and 1499 deletions
|
|
@ -112,7 +112,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]any, data []byte) error {
|
||||||
// Copy performs the operation go format -> provided struct.
|
// Copy performs the operation go format -> provided struct.
|
||||||
func (arguments Arguments) Copy(v any, values []any) error {
|
func (arguments Arguments) Copy(v any, values []any) error {
|
||||||
// make sure the passed value is arguments pointer
|
// make sure the passed value is arguments pointer
|
||||||
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
if reflect.Pointer != reflect.ValueOf(v).Kind() {
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
}
|
}
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
||||||
switch t.T {
|
switch t.T {
|
||||||
case UintTy:
|
case UintTy:
|
||||||
// make sure to not pack a negative value into a uint type.
|
// make sure to not pack a negative value into a uint type.
|
||||||
if reflectValue.Kind() == reflect.Ptr {
|
if reflectValue.Kind() == reflect.Pointer {
|
||||||
val := new(big.Int).Set(reflectValue.Interface().(*big.Int))
|
val := new(big.Int).Set(reflectValue.Interface().(*big.Int))
|
||||||
if val.Sign() == -1 {
|
if val.Sign() == -1 {
|
||||||
return nil, errInvalidSign
|
return nil, errInvalidSign
|
||||||
|
|
@ -86,7 +86,7 @@ func packNum(value reflect.Value) []byte {
|
||||||
return math.U256Bytes(new(big.Int).SetUint64(value.Uint()))
|
return math.U256Bytes(new(big.Int).SetUint64(value.Uint()))
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return math.U256Bytes(big.NewInt(value.Int()))
|
return math.U256Bytes(big.NewInt(value.Int()))
|
||||||
case reflect.Ptr:
|
case reflect.Pointer:
|
||||||
return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int)))
|
return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int)))
|
||||||
default:
|
default:
|
||||||
panic("abi: fatal error")
|
panic("abi: fatal error")
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ func ConvertType(in interface{}, proto interface{}) interface{} {
|
||||||
// indirect recursively dereferences the value until it either gets the value
|
// indirect recursively dereferences the value until it either gets the value
|
||||||
// or finds a big.Int
|
// or finds a big.Int
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
func indirect(v reflect.Value) reflect.Value {
|
||||||
if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeFor[big.Int]() {
|
if v.Kind() == reflect.Pointer && v.Elem().Type() != reflect.TypeFor[big.Int]() {
|
||||||
return indirect(v.Elem())
|
return indirect(v.Elem())
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
|
@ -102,9 +102,9 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
||||||
func set(dst, src reflect.Value) error {
|
func set(dst, src reflect.Value) error {
|
||||||
dstType, srcType := dst.Type(), src.Type()
|
dstType, srcType := dst.Type(), src.Type()
|
||||||
switch {
|
switch {
|
||||||
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()):
|
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Pointer || dst.Elem().CanSet()):
|
||||||
return set(dst.Elem(), src)
|
return set(dst.Elem(), src)
|
||||||
case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeFor[big.Int]():
|
case dstType.Kind() == reflect.Pointer && dstType.Elem() != reflect.TypeFor[big.Int]():
|
||||||
return set(dst.Elem(), src)
|
return set(dst.Elem(), src)
|
||||||
case srcType.AssignableTo(dstType) && dst.CanSet():
|
case srcType.AssignableTo(dstType) && dst.CanSet():
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
|
|
@ -138,7 +138,7 @@ func setSlice(dst, src reflect.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setArray(dst, src reflect.Value) error {
|
func setArray(dst, src reflect.Value) error {
|
||||||
if src.Kind() == reflect.Ptr {
|
if src.Kind() == reflect.Pointer {
|
||||||
return set(dst, indirect(src))
|
return set(dst, indirect(src))
|
||||||
}
|
}
|
||||||
array := reflect.New(dst.Type()).Elem()
|
array := reflect.New(dst.Type()).Elem()
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,11 @@ func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error {
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *eth.TransactionsPacket:
|
case *eth.TransactionsPacket:
|
||||||
for _, tx := range txs {
|
received, err := msg.Items()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode received transactions: %w", err)
|
||||||
|
}
|
||||||
|
for _, tx := range received {
|
||||||
if _, ok := invalids[tx.Hash()]; ok {
|
if _, ok := invalids[tx.Hash()]; ok {
|
||||||
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,14 +183,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||||
)
|
)
|
||||||
vmContext := vm.BlockContext{
|
vmContext := vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
Coinbase: pre.Env.Coinbase,
|
Coinbase: pre.Env.Coinbase,
|
||||||
BlockNumber: new(big.Int).SetUint64(pre.Env.Number),
|
BlockNumber: new(big.Int).SetUint64(pre.Env.Number),
|
||||||
Time: pre.Env.Timestamp,
|
Time: pre.Env.Timestamp,
|
||||||
Difficulty: pre.Env.Difficulty,
|
Difficulty: pre.Env.Difficulty,
|
||||||
GasLimit: pre.Env.GasLimit,
|
GasLimit: pre.Env.GasLimit,
|
||||||
GetHash: getHash,
|
GetHash: getHash,
|
||||||
|
CostPerStateByte: params.CostPerStateByte,
|
||||||
}
|
}
|
||||||
if pre.Env.SlotNumber != nil {
|
if pre.Env.SlotNumber != nil {
|
||||||
vmContext.SlotNum = *pre.Env.SlotNumber
|
vmContext.SlotNum = *pre.Env.SlotNumber
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
// Check intrinsic gas
|
// Check intrinsic gas
|
||||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Error = err
|
r.Error = err
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(haveLines) != len(wantLines) {
|
if len(haveLines) != len(wantLines) {
|
||||||
t.Errorf("format %v, want %d lines, have %d", format, len(haveLines), len(wantLines))
|
t.Errorf("format %v, want %d lines, have %d", format, len(wantLines), len(haveLines))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/graphql"
|
"github.com/ethereum/go-ethereum/graphql"
|
||||||
"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/internal/memlimit"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/metrics/exp"
|
"github.com/ethereum/go-ethereum/metrics/exp"
|
||||||
|
|
@ -74,7 +75,6 @@ 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"
|
||||||
pcsclite "github.com/gballet/go-libpcsclite"
|
pcsclite "github.com/gballet/go-libpcsclite"
|
||||||
gopsutil "github.com/shirou/gopsutil/mem"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1726,16 +1726,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
setMiner(ctx, &cfg.Miner)
|
setMiner(ctx, &cfg.Miner)
|
||||||
setRequiredBlocks(ctx, cfg)
|
setRequiredBlocks(ctx, cfg)
|
||||||
|
|
||||||
// Cap the cache allowance and tune the garbage collector
|
// Cap the cache allowance and tune the garbage collector against
|
||||||
mem, err := gopsutil.VirtualMemory()
|
// the effective memory limit (cgroup-imposed when running in a
|
||||||
if err == nil {
|
// container, total system memory otherwise).
|
||||||
if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
|
total, source := memlimit.Limit()
|
||||||
log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024)
|
if total > 0 {
|
||||||
mem.Total = 2 * 1024 * 1024 * 1024
|
if 32<<(^uintptr(0)>>63) == 32 && total > 2*1024*1024*1024 {
|
||||||
|
log.Warn("Lowering memory allowance on 32bit arch", "available", total/1024/1024, "addressable", 2*1024)
|
||||||
|
total = 2 * 1024 * 1024 * 1024
|
||||||
}
|
}
|
||||||
allowance := int(mem.Total / 1024 / 1024 / 3)
|
allowance := int(total / 1024 / 1024 / 3)
|
||||||
if cache := ctx.Int(CacheFlag.Name); cache > allowance {
|
if cache := ctx.Int(CacheFlag.Name); cache > allowance {
|
||||||
log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
|
log.Warn("Sanitizing cache to Go's GC limits", "source", source, "provided", cache, "updated", allowance)
|
||||||
ctx.Set(CacheFlag.Name, strconv.Itoa(allowance))
|
ctx.Set(CacheFlag.Name, strconv.Itoa(allowance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1750,14 +1752,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync
|
cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync
|
||||||
} else if ctx.IsSet(SyncModeFlag.Name) {
|
} else if ctx.IsSet(SyncModeFlag.Name) {
|
||||||
value := ctx.String(SyncModeFlag.Name)
|
value := ctx.String(SyncModeFlag.Name)
|
||||||
if err = cfg.SyncMode.UnmarshalText([]byte(value)); err != nil {
|
if err := cfg.SyncMode.UnmarshalText([]byte(value)); err != nil {
|
||||||
Fatalf("--%v: %v", SyncModeFlag.Name, err)
|
Fatalf("--%v: %v", SyncModeFlag.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet(ChainHistoryFlag.Name) {
|
if ctx.IsSet(ChainHistoryFlag.Name) {
|
||||||
value := ctx.String(ChainHistoryFlag.Name)
|
value := ctx.String(ChainHistoryFlag.Name)
|
||||||
if err = cfg.HistoryMode.UnmarshalText([]byte(value)); err != nil {
|
if err := cfg.HistoryMode.UnmarshalText([]byte(value)); err != nil {
|
||||||
Fatalf("--%s: %v", ChainHistoryFlag.Name, err)
|
Fatalf("--%s: %v", ChainHistoryFlag.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,14 @@ func assertEmpty(t *testing.T, aa *bal.AccountAccess) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// txGasNewAccount covers the base tx cost plus the EIP-8037 account-creation
|
||||||
|
// state-gas charge (STATE_BYTES_PER_NEW_ACCOUNT × CPSB ≈ 183,600) that is
|
||||||
|
// incurred when value is transferred to a non-existent account under Amsterdam.
|
||||||
|
// params.TxGas (21,000) alone is insufficient: the transfer would run out of
|
||||||
|
// gas, the credit would revert, and the recipient would never get a balance
|
||||||
|
// change recorded in the BAL.
|
||||||
|
const txGasNewAccount = 250_000
|
||||||
|
|
||||||
// --- tx builders ---
|
// --- tx builders ---
|
||||||
|
|
||||||
func (e *balTestEnv) tx(nonce uint64, to *common.Address, value *big.Int, gas uint64, tipGwei int64, data []byte) *types.Transaction {
|
func (e *balTestEnv) tx(nonce uint64, to *common.Address, value *big.Int, gas uint64, tipGwei int64, data []byte) *types.Transaction {
|
||||||
|
|
@ -177,7 +185,7 @@ func TestBALTxSenderAndRecipient(t *testing.T) {
|
||||||
env := newBALTestEnv(nil)
|
env := newBALTestEnv(nil)
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &to, big.NewInt(1000), params.TxGas, 0, nil))
|
g.AddTx(env.tx(0, &to, big.NewInt(1000), txGasNewAccount, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
sender := assertPresent(t, b, env.from)
|
sender := assertPresent(t, b, env.from)
|
||||||
|
|
@ -260,7 +268,7 @@ func TestBALSystemAddressIncludedWhenTouched(t *testing.T) {
|
||||||
env := newBALTestEnv(nil)
|
env := newBALTestEnv(nil)
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &sys, big.NewInt(1000), params.TxGas, 0, nil))
|
g.AddTx(env.tx(0, &sys, big.NewInt(1000), txGasNewAccount, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, sys)
|
aa := assertPresent(t, b, sys)
|
||||||
|
|
@ -283,7 +291,7 @@ func TestBALPrecompileInvokedFromContractIncluded(t *testing.T) {
|
||||||
|
|
||||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, identity)
|
aa := assertPresent(t, b, identity)
|
||||||
|
|
@ -315,7 +323,7 @@ func TestBALPrecompileValueTransferRecordsBalance(t *testing.T) {
|
||||||
env := newBALTestEnv(nil)
|
env := newBALTestEnv(nil)
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &identity, big.NewInt(5), 50_000, 0, nil))
|
g.AddTx(env.tx(0, &identity, big.NewInt(5), txGasNewAccount, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, identity)
|
aa := assertPresent(t, b, identity)
|
||||||
|
|
@ -334,7 +342,7 @@ func TestBALBalanceProbeOnNonExistent(t *testing.T) {
|
||||||
|
|
||||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertEmpty(t, assertPresent(t, b, probe))
|
assertEmpty(t, assertPresent(t, b, probe))
|
||||||
|
|
@ -350,7 +358,7 @@ func TestBALExtCodeSizeProbeOnNonExistent(t *testing.T) {
|
||||||
|
|
||||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertEmpty(t, assertPresent(t, b, probe))
|
assertEmpty(t, assertPresent(t, b, probe))
|
||||||
|
|
@ -366,7 +374,7 @@ func TestBALExtCodeHashProbeOnNonExistent(t *testing.T) {
|
||||||
|
|
||||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertEmpty(t, assertPresent(t, b, probe))
|
assertEmpty(t, assertPresent(t, b, probe))
|
||||||
|
|
@ -385,7 +393,7 @@ func TestBALExtCodeCopyProbeOnNonExistent(t *testing.T) {
|
||||||
|
|
||||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertEmpty(t, assertPresent(t, b, probe))
|
assertEmpty(t, assertPresent(t, b, probe))
|
||||||
|
|
@ -447,7 +455,7 @@ func TestBALCallTargetWithEmptyChangeSet(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &target, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &target, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertEmpty(t, assertPresent(t, b, target))
|
assertEmpty(t, assertPresent(t, b, target))
|
||||||
|
|
@ -465,7 +473,7 @@ func TestBALCallCodeTargetIncluded(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 200_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertPresent(t, b, caller)
|
assertPresent(t, b, caller)
|
||||||
|
|
@ -483,7 +491,7 @@ func TestBALDelegateCallTargetIncluded(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 200_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertPresent(t, b, caller)
|
assertPresent(t, b, caller)
|
||||||
|
|
@ -501,7 +509,7 @@ func TestBALStaticCallTargetIncluded(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 200_000, 0, nil))
|
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertPresent(t, b, caller)
|
assertPresent(t, b, caller)
|
||||||
|
|
@ -519,7 +527,7 @@ func TestBALRevertedTxStillIncluded(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{reverter: {Code: revertCode, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{reverter: {Code: revertCode, Balance: common.Big0}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
assertEmpty(t, assertPresent(t, b, reverter))
|
assertEmpty(t, assertPresent(t, b, reverter))
|
||||||
|
|
@ -533,7 +541,7 @@ func TestBALSenderRecordedOnRevert(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{reverter: {Code: revertCode, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{reverter: {Code: revertCode, Balance: common.Big0}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
sender := assertPresent(t, b, env.from)
|
sender := assertPresent(t, b, env.from)
|
||||||
|
|
@ -557,7 +565,7 @@ func TestBALStorageWriteRecorded(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, contract)
|
aa := assertPresent(t, b, contract)
|
||||||
|
|
@ -578,7 +586,7 @@ func TestBALStorageSloadOnly(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, contract)
|
aa := assertPresent(t, b, contract)
|
||||||
|
|
@ -604,7 +612,7 @@ func TestBALStorageReadThenWriteOnlyInWrites(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, contract)
|
aa := assertPresent(t, b, contract)
|
||||||
|
|
@ -632,7 +640,7 @@ func TestBALNoOpSSTOREDemotesToRead(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, contract)
|
aa := assertPresent(t, b, contract)
|
||||||
|
|
@ -660,7 +668,7 @@ func TestBALStorageWriteZeroIsAWrite(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, contract)
|
aa := assertPresent(t, b, contract)
|
||||||
|
|
@ -687,7 +695,7 @@ func TestBALCreateDeploysCode(t *testing.T) {
|
||||||
init := []byte{0x60, 0x00, 0x60, 0x00, 0x53, 0x60, 0x01, 0x60, 0x00, 0xf3}
|
init := []byte{0x60, 0x00, 0x60, 0x00, 0x53, 0x60, 0x01, 0x60, 0x00, 0xf3}
|
||||||
|
|
||||||
b, receipts := env.run(t, func(g *BlockGen) {
|
b, receipts := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(7), 200_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(7), 1_000_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
created := receipts[0].ContractAddress
|
created := receipts[0].ContractAddress
|
||||||
|
|
@ -711,7 +719,7 @@ func TestBALCreateEmptyRuntimeNoCodeEntry(t *testing.T) {
|
||||||
init := []byte{0x60, 0x00, 0x60, 0x00, 0xf3}
|
init := []byte{0x60, 0x00, 0x60, 0x00, 0xf3}
|
||||||
|
|
||||||
b, receipts := env.run(t, func(g *BlockGen) {
|
b, receipts := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
created := receipts[0].ContractAddress
|
created := receipts[0].ContractAddress
|
||||||
|
|
@ -732,7 +740,7 @@ func TestBALCreateInitRevertEmptyChangeSet(t *testing.T) {
|
||||||
init := []byte{0x60, 0x00, 0x60, 0x00, 0xfd}
|
init := []byte{0x60, 0x00, 0x60, 0x00, 0xfd}
|
||||||
|
|
||||||
b, receipts := env.run(t, func(g *BlockGen) {
|
b, receipts := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
created := receipts[0].ContractAddress
|
created := receipts[0].ContractAddress
|
||||||
|
|
@ -743,11 +751,13 @@ func TestBALCreateInitRevertEmptyChangeSet(t *testing.T) {
|
||||||
// the deployed address in the BAL with an empty change set.
|
// the deployed address in the BAL with an empty change set.
|
||||||
func TestBALCreateInitOOGEmptyChangeSet(t *testing.T) {
|
func TestBALCreateInitOOGEmptyChangeSet(t *testing.T) {
|
||||||
env := newBALTestEnv(nil)
|
env := newBALTestEnv(nil)
|
||||||
// Infinite loop: JUMPDEST PUSH1 0 JUMP — burns gas until OOG.
|
// Infinite loop: JUMPDEST PUSH1 0 JUMP — burns gas until OOG. The
|
||||||
|
// gas budget must cover EIP-8037 intrinsic state gas (account creation)
|
||||||
|
// so the tx is accepted; OOG must happen inside the init code.
|
||||||
init := []byte{0x5b, 0x60, 0x00, 0x56}
|
init := []byte{0x5b, 0x60, 0x00, 0x56}
|
||||||
|
|
||||||
b, receipts := env.run(t, func(g *BlockGen) {
|
b, receipts := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 60_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(0), 220_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
created := receipts[0].ContractAddress
|
created := receipts[0].ContractAddress
|
||||||
|
|
@ -771,7 +781,7 @@ func TestBALCreateAddressCollisionStillIncluded(t *testing.T) {
|
||||||
// Init code doesn't matter — execution never starts.
|
// Init code doesn't matter — execution never starts.
|
||||||
init := []byte{0x60, 0x00, 0x60, 0x00, 0xf3}
|
init := []byte{0x60, 0x00, 0x60, 0x00, 0xf3}
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, collide)
|
aa := assertPresent(t, b, collide)
|
||||||
|
|
@ -799,7 +809,7 @@ func TestBALInEVMCreatePreAccessAbortDestinationExcluded(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &factory, big.NewInt(0), 200_000, 0, nil))
|
g.AddTx(env.tx(0, &factory, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
// The address that WOULD have been deployed had the create succeeded.
|
// The address that WOULD have been deployed had the create succeeded.
|
||||||
|
|
@ -842,7 +852,7 @@ func TestBALInEVMCreateDeploysContract(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{factory: {Code: code, Balance: common.Big0, Nonce: 1}})
|
env := newBALTestEnv(types.GenesisAlloc{factory: {Code: code, Balance: common.Big0, Nonce: 1}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &factory, big.NewInt(0), 300_000, 0, nil))
|
g.AddTx(env.tx(0, &factory, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Deployed address depends on the factory's nonce at the moment of CREATE,
|
// Deployed address depends on the factory's nonce at the moment of CREATE,
|
||||||
|
|
@ -870,7 +880,7 @@ func TestBALSelfDestructBeneficiaryWithZeroBalance(t *testing.T) {
|
||||||
init = append(init, 0xff)
|
init = append(init, 0xff)
|
||||||
|
|
||||||
b, receipts := env.run(t, func(g *BlockGen) {
|
b, receipts := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
created := receipts[0].ContractAddress
|
created := receipts[0].ContractAddress
|
||||||
|
|
@ -896,7 +906,7 @@ func TestBALSelfDestructBeneficiaryWithValueTransfer(t *testing.T) {
|
||||||
init = append(init, 0xff)
|
init = append(init, 0xff)
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, nil, big.NewInt(100), 200_000, 0, init))
|
g.AddTx(env.tx(0, nil, big.NewInt(100), 1_000_000, 0, init))
|
||||||
})
|
})
|
||||||
|
|
||||||
ben := assertPresent(t, b, beneficiary)
|
ben := assertPresent(t, b, beneficiary)
|
||||||
|
|
@ -921,7 +931,7 @@ func TestBALSelfDestructPreExistingContract(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &suicidal, big.NewInt(0), 200_000, 0, nil))
|
g.AddTx(env.tx(0, &suicidal, big.NewInt(0), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, suicidal)
|
aa := assertPresent(t, b, suicidal)
|
||||||
|
|
@ -966,7 +976,7 @@ func TestBALMidTxBalanceRoundTrip(t *testing.T) {
|
||||||
env := newBALTestEnv(types.GenesisAlloc{bouncer: {Code: code, Balance: common.Big0}})
|
env := newBALTestEnv(types.GenesisAlloc{bouncer: {Code: code, Balance: common.Big0}})
|
||||||
|
|
||||||
b, _ := env.run(t, func(g *BlockGen) {
|
b, _ := env.run(t, func(g *BlockGen) {
|
||||||
g.AddTx(env.tx(0, &bouncer, big.NewInt(1234), 200_000, 0, nil))
|
g.AddTx(env.tx(0, &bouncer, big.NewInt(1234), 1_000_000, 0, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
aa := assertPresent(t, b, bouncer)
|
aa := assertPresent(t, b, bouncer)
|
||||||
|
|
@ -1072,7 +1082,7 @@ func TestBALAuthorityIncludedOnSetCodeTx(t *testing.T) {
|
||||||
Nonce: 0,
|
Nonce: 0,
|
||||||
To: env.from,
|
To: env.from,
|
||||||
Value: new(uint256.Int),
|
Value: new(uint256.Int),
|
||||||
Gas: 200_000,
|
Gas: 1_000_000,
|
||||||
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
||||||
GasTipCap: new(uint256.Int),
|
GasTipCap: new(uint256.Int),
|
||||||
AuthList: []types.SetCodeAuthorization{auth},
|
AuthList: []types.SetCodeAuthorization{auth},
|
||||||
|
|
@ -1112,7 +1122,7 @@ func TestBALDelegationTargetNotIncludedOnAuthOnly(t *testing.T) {
|
||||||
Nonce: 0,
|
Nonce: 0,
|
||||||
To: env.from, // tx.to is an EOA with no code: delegate is never called
|
To: env.from, // tx.to is an EOA with no code: delegate is never called
|
||||||
Value: new(uint256.Int),
|
Value: new(uint256.Int),
|
||||||
Gas: 200_000,
|
Gas: 1_000_000,
|
||||||
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
||||||
GasTipCap: new(uint256.Int),
|
GasTipCap: new(uint256.Int),
|
||||||
AuthList: []types.SetCodeAuthorization{auth},
|
AuthList: []types.SetCodeAuthorization{auth},
|
||||||
|
|
@ -1131,7 +1141,7 @@ func (e *balTestEnv) newSetCodeTx(t *testing.T, nonce uint64, to common.Address,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
To: to,
|
To: to,
|
||||||
Value: new(uint256.Int),
|
Value: new(uint256.Int),
|
||||||
Gas: 400_000,
|
Gas: 1_000_000,
|
||||||
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
||||||
GasTipCap: new(uint256.Int),
|
GasTipCap: new(uint256.Int),
|
||||||
AuthList: auths,
|
AuthList: auths,
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
||||||
data := make([]byte, nbytes)
|
data := make([]byte, nbytes)
|
||||||
return func(i int, gen *BlockGen) {
|
return func(i int, gen *BlockGen) {
|
||||||
toaddr := common.Address{}
|
toaddr := common.Address{}
|
||||||
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
|
cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte)
|
||||||
signer := gen.Signer()
|
signer := gen.Signer()
|
||||||
gasPrice := big.NewInt(0)
|
gasPrice := big.NewInt(0)
|
||||||
if gen.header.BaseFee != nil {
|
if gen.header.BaseFee != nil {
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,12 @@ var (
|
||||||
func TestProcessUBT(t *testing.T) {
|
func TestProcessUBT(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
|
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||||
// will not contain that copied data.
|
// will not contain that copied data.
|
||||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
|
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||||
signer = types.LatestSigner(testUBTChainConfig)
|
signer = types.LatestSigner(testUBTChainConfig)
|
||||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||||
|
|
@ -201,7 +201,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
||||||
if isUBT {
|
if isUBT {
|
||||||
chainConfig = testUBTChainConfig
|
chainConfig = testUBTChainConfig
|
||||||
}
|
}
|
||||||
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
|
vmContext := NewEVMBlockContext(header, &BlockChain{chainConfig: chainConfig}, new(common.Address))
|
||||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
|
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
|
||||||
ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList())
|
ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1194,10 +1194,13 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash, isSnapV2 bool) error {
|
||||||
return fmt.Errorf("non existent state [%x..]", root[:4])
|
return fmt.Errorf("non existent state [%x..]", root[:4])
|
||||||
}
|
}
|
||||||
|
|
||||||
// The legacy snapshot tree needs to be wiped and rebuilt from the trie
|
// The legacy snapshot tree (hash scheme only) was persistently disabled
|
||||||
// after a snap/1 sync.
|
// before the sync, re-enables it explicitly.
|
||||||
if !isSnapV2 && bc.snaps != nil {
|
//
|
||||||
bc.snaps.Rebuild(root)
|
// For snap/2 the downloaded flat state is already complete and root-verified,
|
||||||
|
// so the background generation is unnecessary.
|
||||||
|
if bc.snaps != nil {
|
||||||
|
bc.snaps.Rebuild(root, !isSnapV2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all checks out, manually set the head block.
|
// If all checks out, manually set the head block.
|
||||||
|
|
@ -2316,6 +2319,14 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
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
|
||||||
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
||||||
|
|
||||||
|
// Attach the computed block access list so it gets persisted alongside the
|
||||||
|
// block. The validator has already verified the hash matches the header.
|
||||||
|
// BAL is only meaningful from Amsterdam onward; skip pre-Amsterdam blocks
|
||||||
|
// to avoid persisting and serving empty BALs over the network.
|
||||||
|
if res.Bal != nil && block.AccessList() == nil && bc.chainConfig.IsAmsterdam(block.Number(), block.Time()) {
|
||||||
|
block = block.WithAccessListUnsafe(res.Bal.ToEncodingObj())
|
||||||
|
}
|
||||||
|
|
||||||
// Write the block to the chain and get the status.
|
// Write the block to the chain and get the status.
|
||||||
var status WriteStatus
|
var status WriteStatus
|
||||||
if config.WriteState {
|
if config.WriteState {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -721,3 +722,40 @@ func TestRecoverSnapshotFromWipingCrash(t *testing.T) {
|
||||||
test.teardown()
|
test.teardown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSnapSyncCompleteRebuildsSnapshot verifies that completing a snap sync
|
||||||
|
// re-enables the legacy snapshot tree on the hash scheme for both syncer
|
||||||
|
// versions: SnapSyncStart persistently disables the tree, and only the
|
||||||
|
// rebuild on completion clears the marker again.
|
||||||
|
func TestSnapSyncCompleteRebuildsSnapshot(t *testing.T) {
|
||||||
|
for _, isSnapV2 := range []bool{false, true} {
|
||||||
|
_, _, chain, err := newCanonical(ethash.NewFaker(), 8, true, rawdb.HashScheme)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create chain: %v", err)
|
||||||
|
}
|
||||||
|
if err := chain.SnapSyncStart(); err != nil {
|
||||||
|
t.Fatalf("failed to start snap sync: %v", err)
|
||||||
|
}
|
||||||
|
if !rawdb.ReadSnapshotDisabled(chain.db) {
|
||||||
|
t.Fatal("snapshot should be disabled during snap sync")
|
||||||
|
}
|
||||||
|
head := chain.CurrentBlock()
|
||||||
|
if err := chain.SnapSyncComplete(head.Hash(), isSnapV2); err != nil {
|
||||||
|
t.Fatalf("failed to complete snap sync (v2=%v): %v", isSnapV2, err)
|
||||||
|
}
|
||||||
|
if rawdb.ReadSnapshotDisabled(chain.db) {
|
||||||
|
t.Fatalf("snapshot should be re-enabled after snap sync completion (v2=%v)", isSnapV2)
|
||||||
|
}
|
||||||
|
// snap/2 adopts the flat state without regeneration, so the snapshot
|
||||||
|
// must be immediately usable; snap/1 schedules a background rebuild
|
||||||
|
// instead (which may or may not have finished, no assertion there).
|
||||||
|
if isSnapV2 {
|
||||||
|
it, err := chain.snaps.AccountIterator(head.Root, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("adopted snapshot not immediately usable: %v", err)
|
||||||
|
}
|
||||||
|
it.Release()
|
||||||
|
}
|
||||||
|
chain.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
25
core/evm.go
25
core/evm.go
|
|
@ -68,18 +68,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||||
}
|
}
|
||||||
|
|
||||||
return vm.BlockContext{
|
return vm.BlockContext{
|
||||||
CanTransfer: CanTransfer,
|
CanTransfer: CanTransfer,
|
||||||
Transfer: Transfer,
|
Transfer: Transfer,
|
||||||
GetHash: GetHashFn(header, chain),
|
GetHash: GetHashFn(header, chain),
|
||||||
Coinbase: beneficiary,
|
Coinbase: beneficiary,
|
||||||
BlockNumber: new(big.Int).Set(header.Number),
|
BlockNumber: new(big.Int).Set(header.Number),
|
||||||
Time: header.Time,
|
Time: header.Time,
|
||||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||||
BaseFee: baseFee,
|
BaseFee: baseFee,
|
||||||
BlobBaseFee: blobBaseFee,
|
BlobBaseFee: blobBaseFee,
|
||||||
GasLimit: header.GasLimit,
|
GasLimit: header.GasLimit,
|
||||||
Random: random,
|
Random: random,
|
||||||
SlotNum: slotNum,
|
SlotNum: slotNum,
|
||||||
|
CostPerStateByte: params.CostPerStateByte,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ type GasPool struct {
|
||||||
remaining uint64
|
remaining uint64
|
||||||
initial uint64
|
initial uint64
|
||||||
cumulativeUsed uint64
|
cumulativeUsed uint64
|
||||||
|
|
||||||
|
// After 8037 Block gas used is max(cumulativeRegular, cumulativeState).
|
||||||
|
cumulativeRegular uint64
|
||||||
|
cumulativeState uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGasPool initializes the gasPool with the given amount.
|
// NewGasPool initializes the gasPool with the given amount.
|
||||||
|
|
@ -37,9 +41,9 @@ func NewGasPool(amount uint64) *GasPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubGas deducts the given amount from the pool if enough gas is
|
// CheckGasLegacy deducts the given amount from the pool if enough gas is
|
||||||
// available and returns an error otherwise.
|
// available and returns an error otherwise.
|
||||||
func (gp *GasPool) SubGas(amount uint64) error {
|
func (gp *GasPool) CheckGasLegacy(amount uint64) error {
|
||||||
if gp.remaining < amount {
|
if gp.remaining < amount {
|
||||||
return ErrGasLimitReached
|
return ErrGasLimitReached
|
||||||
}
|
}
|
||||||
|
|
@ -47,41 +51,73 @@ func (gp *GasPool) SubGas(amount uint64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReturnGas adds the refunded gas back to the pool and updates
|
// CheckGasAmsterdam performs the EIP-8037 per-tx 2D block-inclusion check:
|
||||||
|
// the worst-case regular contribution must fit in the regular dimension and
|
||||||
|
// the worst-case state contribution must fit in the state dimension
|
||||||
|
func (gp *GasPool) CheckGasAmsterdam(regularReservation, stateReservation uint64) error {
|
||||||
|
if gp.initial-gp.cumulativeRegular < regularReservation {
|
||||||
|
return ErrGasLimitReached
|
||||||
|
}
|
||||||
|
if gp.initial-gp.cumulativeState < stateReservation {
|
||||||
|
return ErrGasLimitReached
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeGasLegacy adds the refunded gas back to the pool and updates
|
||||||
// the cumulative gas usage accordingly.
|
// the cumulative gas usage accordingly.
|
||||||
func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
|
func (gp *GasPool) ChargeGasLegacy(returned uint64, gasUsed uint64) error {
|
||||||
if gp.remaining > math.MaxUint64-returned {
|
if gp.remaining > math.MaxUint64-returned {
|
||||||
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
|
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
|
||||||
}
|
}
|
||||||
// The returned gas calculation differs across forks.
|
// returned = purchased - remaining (refund included)
|
||||||
//
|
|
||||||
// - Pre-Amsterdam:
|
|
||||||
// returned = purchased - remaining (refund included)
|
|
||||||
//
|
|
||||||
// - Post-Amsterdam:
|
|
||||||
// returned = purchased - gasUsed (refund excluded)
|
|
||||||
gp.remaining += returned
|
gp.remaining += returned
|
||||||
|
|
||||||
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
|
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
|
||||||
// regardless of Amsterdam is activated or not.
|
|
||||||
gp.cumulativeUsed += gasUsed
|
gp.cumulativeUsed += gasUsed
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChargeGasAmsterdam calculates the new remaining gas in the pool after the
|
||||||
|
// execution of a message. Previously we subtracted and re-added gas to the
|
||||||
|
// gaspool. After Amsterdam we only check if we can include the transaction
|
||||||
|
// and charge the gaspool at the end.
|
||||||
|
func (gp *GasPool) ChargeGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error {
|
||||||
|
cumulativeRegular := gp.cumulativeRegular + txRegular
|
||||||
|
cumulativeState := gp.cumulativeState + txState
|
||||||
|
blockUsed := max(cumulativeRegular, cumulativeState)
|
||||||
|
if gp.initial < blockUsed {
|
||||||
|
return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)",
|
||||||
|
ErrGasLimitReached, gp.initial, blockUsed, cumulativeRegular, cumulativeState)
|
||||||
|
}
|
||||||
|
gp.cumulativeRegular = cumulativeRegular
|
||||||
|
gp.cumulativeState = cumulativeState
|
||||||
|
gp.cumulativeUsed += receiptGasUsed
|
||||||
|
// TODO(rjl, marius), the semantics of this counter is slightly different
|
||||||
|
// in the context of Amsterdam, the API Gas() should be reworked.
|
||||||
|
gp.remaining = gp.initial - gp.cumulativeRegular
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Gas returns the amount of gas remaining in the pool.
|
// Gas returns the amount of gas remaining in the pool.
|
||||||
func (gp *GasPool) Gas() uint64 {
|
func (gp *GasPool) Gas() uint64 {
|
||||||
return gp.remaining
|
return gp.remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
// CumulativeUsed returns the amount of cumulative consumed gas (refunded included).
|
// CumulativeUsed returns the cumulative gas consumed for receipt tracking.
|
||||||
func (gp *GasPool) CumulativeUsed() uint64 {
|
func (gp *GasPool) CumulativeUsed() uint64 {
|
||||||
return gp.cumulativeUsed
|
return gp.cumulativeUsed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used returns the amount of consumed gas.
|
// Used returns the amount of consumed gas.
|
||||||
func (gp *GasPool) Used() uint64 {
|
func (gp *GasPool) Used() uint64 {
|
||||||
|
// After 8037, return max(sum_regular, sum_state)
|
||||||
|
if gp.cumulativeRegular > 0 || gp.cumulativeState > 0 {
|
||||||
|
return max(gp.cumulativeRegular, gp.cumulativeState)
|
||||||
|
}
|
||||||
|
// Before 8037, return initial-remaining
|
||||||
if gp.initial < gp.remaining {
|
if gp.initial < gp.remaining {
|
||||||
panic("gas used underflow")
|
panic(fmt.Sprintf("gas used underflow: %v %v", gp.initial, gp.remaining))
|
||||||
}
|
}
|
||||||
return gp.initial - gp.remaining
|
return gp.initial - gp.remaining
|
||||||
}
|
}
|
||||||
|
|
@ -89,9 +125,11 @@ func (gp *GasPool) Used() uint64 {
|
||||||
// Snapshot returns the deep-copied object as the snapshot.
|
// Snapshot returns the deep-copied object as the snapshot.
|
||||||
func (gp *GasPool) Snapshot() *GasPool {
|
func (gp *GasPool) Snapshot() *GasPool {
|
||||||
return &GasPool{
|
return &GasPool{
|
||||||
initial: gp.initial,
|
initial: gp.initial,
|
||||||
remaining: gp.remaining,
|
remaining: gp.remaining,
|
||||||
cumulativeUsed: gp.cumulativeUsed,
|
cumulativeUsed: gp.cumulativeUsed,
|
||||||
|
cumulativeRegular: gp.cumulativeRegular,
|
||||||
|
cumulativeState: gp.cumulativeState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +138,8 @@ func (gp *GasPool) Set(other *GasPool) {
|
||||||
gp.initial = other.initial
|
gp.initial = other.initial
|
||||||
gp.remaining = other.remaining
|
gp.remaining = other.remaining
|
||||||
gp.cumulativeUsed = other.cumulativeUsed
|
gp.cumulativeUsed = other.cumulativeUsed
|
||||||
|
gp.cumulativeRegular = other.cumulativeRegular
|
||||||
|
gp.cumulativeState = other.cumulativeState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gp *GasPool) String() string {
|
func (gp *GasPool) String() string {
|
||||||
|
|
|
||||||
|
|
@ -209,48 +209,6 @@ func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadGenerateTriePartitionDone returns the raw subtree root blob for a
|
|
||||||
// partition that has previously completed.
|
|
||||||
func ReadGenerateTriePartitionDone(db ethdb.KeyValueReader, partition byte) ([]byte, bool) {
|
|
||||||
data, err := db.Get(generateTriePartitionDoneKey(partition))
|
|
||||||
if err != nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
switch data[0] {
|
|
||||||
case 0x00:
|
|
||||||
// Partition is done and it is empty.
|
|
||||||
return nil, true
|
|
||||||
case 0x01:
|
|
||||||
// Partition is done and the blob follows.
|
|
||||||
return data[1:], true
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteGenerateTriePartitionDone records a completed partition.
|
|
||||||
func WriteGenerateTriePartitionDone(db ethdb.KeyValueWriter, partition byte, blob []byte) {
|
|
||||||
var value []byte
|
|
||||||
if blob == nil {
|
|
||||||
value = []byte{0x00}
|
|
||||||
} else {
|
|
||||||
value = append([]byte{0x01}, blob...)
|
|
||||||
}
|
|
||||||
if err := db.Put(generateTriePartitionDoneKey(partition), value); err != nil {
|
|
||||||
log.Crit("Failed to store generate-trie done marker", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteGenerateTriePartitionDone removes a partition's done marker.
|
|
||||||
func DeleteGenerateTriePartitionDone(db ethdb.KeyValueWriter, partition byte) {
|
|
||||||
if err := db.Delete(generateTriePartitionDoneKey(partition)); err != nil {
|
|
||||||
log.Crit("Failed to remove generate-trie done marker", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSnapshotSyncStatus removes the serialized sync status from the database.
|
// DeleteSnapshotSyncStatus removes the serialized sync status from the database.
|
||||||
func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) {
|
func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) {
|
||||||
if err := db.Delete(snapshotSyncStatusKey); err != nil {
|
if err := db.Delete(snapshotSyncStatusKey); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -563,8 +563,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata keys
|
// Metadata keys
|
||||||
case bytes.HasPrefix(key, generateTriePartitionDonePrefix) && len(key) == len(generateTriePartitionDonePrefix)+1:
|
|
||||||
metadata.add(size)
|
|
||||||
case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }):
|
case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }):
|
||||||
metadata.add(size)
|
metadata.add(size)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,10 +104,6 @@ var (
|
||||||
// snapSyncStatusFlagKey flags that status of snap sync.
|
// snapSyncStatusFlagKey flags that status of snap sync.
|
||||||
snapSyncStatusFlagKey = []byte("SnapSyncStatus")
|
snapSyncStatusFlagKey = []byte("SnapSyncStatus")
|
||||||
|
|
||||||
// generateTriePartitionDonePrefix stores the subtree root hash of each
|
|
||||||
// triedb.GenerateTrie partition once it finishes.
|
|
||||||
generateTriePartitionDonePrefix = []byte("gtd") // generateTriePartitionDonePrefix + partition byte -> subtree root hash
|
|
||||||
|
|
||||||
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
|
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
|
||||||
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
|
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
|
||||||
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td (deprecated)
|
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td (deprecated)
|
||||||
|
|
@ -469,8 +465,3 @@ func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID
|
||||||
func transitionStateKey(hash common.Hash) []byte {
|
func transitionStateKey(hash common.Hash) []byte {
|
||||||
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
|
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateTriePartitionDoneKey = generateTriePartitionDonePrefix + partition (single byte).
|
|
||||||
func generateTriePartitionDoneKey(partition byte) []byte {
|
|
||||||
return append(generateTriePartitionDonePrefix, partition)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/fastcache"
|
||||||
"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"
|
||||||
|
|
@ -213,7 +214,7 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, roo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to load snapshot", "err", err)
|
log.Warn("Failed to load snapshot", "err", err)
|
||||||
if !config.NoBuild {
|
if !config.NoBuild {
|
||||||
snap.Rebuild(root)
|
snap.Rebuild(root, true)
|
||||||
return snap, nil
|
return snap, nil
|
||||||
}
|
}
|
||||||
return nil, err // Bail out the error, don't rebuild automatically.
|
return nil, err // Bail out the error, don't rebuild automatically.
|
||||||
|
|
@ -683,10 +684,12 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) {
|
||||||
return base, nil
|
return base, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild wipes all available snapshot data from the persistent database and
|
// Rebuild discards all caches and diff layers and re-enables the snapshot
|
||||||
// discard all caches and diff layers. Afterwards, it starts a new snapshot
|
// feature. With generate set, it starts a new snapshot generator with the
|
||||||
// generator with the given root hash.
|
// given root hash, wiping and regenerating the persistent flat state in the
|
||||||
func (t *Tree) Rebuild(root common.Hash) {
|
// background. Without it, the on-disk flat state is adopted as the fully
|
||||||
|
// generated disk layer directly.
|
||||||
|
func (t *Tree) Rebuild(root common.Hash, generate bool) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -715,6 +718,26 @@ func (t *Tree) Rebuild(root common.Hash) {
|
||||||
panic(fmt.Sprintf("unknown layer type: %T", layer))
|
panic(fmt.Sprintf("unknown layer type: %T", layer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Adopt the existing flat state as the generated disk layer if
|
||||||
|
// regeneration was not requested.
|
||||||
|
if !generate {
|
||||||
|
batch := t.diskdb.NewBatch()
|
||||||
|
rawdb.WriteSnapshotRoot(batch, root)
|
||||||
|
journalProgress(batch, nil, nil)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed to write snapshot completion marker", "err", err)
|
||||||
|
}
|
||||||
|
log.Info("Adopted state snapshot", "root", root)
|
||||||
|
t.layers = map[common.Hash]snapshot{
|
||||||
|
root: &diskLayer{
|
||||||
|
diskdb: t.diskdb,
|
||||||
|
triedb: t.triedb,
|
||||||
|
cache: fastcache.New(t.config.CacheSize * 1024 * 1024),
|
||||||
|
root: root,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
// Start generating a new snapshot from scratch on a background thread. The
|
// Start generating a new snapshot from scratch on a background thread. The
|
||||||
// generator will run a wiper first if there's not one running right now.
|
// generator will run a wiper first if there's not one running right now.
|
||||||
log.Info("Rebuilding state snapshot")
|
log.Info("Rebuilding state snapshot")
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,21 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
|
||||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
|
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// systemCallGasBudget returns the gas budget for system calls.
|
||||||
|
func systemCallGasBudget(evm *vm.EVM) (gasLimit uint64, gasBudget vm.GasBudget) {
|
||||||
|
if !evm.GetRules().IsAmsterdam {
|
||||||
|
gasLimit = 30_000_000
|
||||||
|
gasBudget = vm.NewGasBudget(gasLimit, 0)
|
||||||
|
} else {
|
||||||
|
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
|
||||||
|
// new storage slots a single system call is expected to write.
|
||||||
|
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
|
||||||
|
gasLimit = 30_000_000
|
||||||
|
gasBudget = vm.NewGasBudget(gasLimit, stateBudget)
|
||||||
|
}
|
||||||
|
return gasLimit, gasBudget
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||||
// contract. This method is exported to be used in tests.
|
// contract. This method is exported to be used in tests.
|
||||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) {
|
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) {
|
||||||
|
|
@ -280,9 +295,10 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
|
||||||
defer tracer.OnSystemCallEnd()
|
defer tracer.OnSystemCallEnd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gasLimit, gasBudget := systemCallGasBudget(evm)
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
From: params.SystemAddress,
|
From: params.SystemAddress,
|
||||||
GasLimit: 30_000_000,
|
GasLimit: gasLimit,
|
||||||
GasPrice: uint256.NewInt(0),
|
GasPrice: uint256.NewInt(0),
|
||||||
GasFeeCap: uint256.NewInt(0),
|
GasFeeCap: uint256.NewInt(0),
|
||||||
GasTipCap: uint256.NewInt(0),
|
GasTipCap: uint256.NewInt(0),
|
||||||
|
|
@ -293,7 +309,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
|
||||||
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
||||||
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
||||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, gasBudget, common.U2560)
|
||||||
if evm.StateDB.AccessEvents() != nil {
|
if evm.StateDB.AccessEvents() != nil {
|
||||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||||
}
|
}
|
||||||
|
|
@ -309,9 +325,10 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
|
||||||
defer tracer.OnSystemCallEnd()
|
defer tracer.OnSystemCallEnd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gasLimit, gasBudget := systemCallGasBudget(evm)
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
From: params.SystemAddress,
|
From: params.SystemAddress,
|
||||||
GasLimit: 30_000_000,
|
GasLimit: gasLimit,
|
||||||
GasPrice: uint256.NewInt(0),
|
GasPrice: uint256.NewInt(0),
|
||||||
GasFeeCap: uint256.NewInt(0),
|
GasFeeCap: uint256.NewInt(0),
|
||||||
GasTipCap: uint256.NewInt(0),
|
GasTipCap: uint256.NewInt(0),
|
||||||
|
|
@ -322,7 +339,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
|
||||||
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
||||||
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||||
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
|
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
|
||||||
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, gasBudget, common.U2560)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -351,9 +368,10 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
|
||||||
defer tracer.OnSystemCallEnd()
|
defer tracer.OnSystemCallEnd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gasLimit, gasBudget := systemCallGasBudget(evm)
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
From: params.SystemAddress,
|
From: params.SystemAddress,
|
||||||
GasLimit: 30_000_000,
|
GasLimit: gasLimit,
|
||||||
GasPrice: uint256.NewInt(0),
|
GasPrice: uint256.NewInt(0),
|
||||||
GasFeeCap: uint256.NewInt(0),
|
GasFeeCap: uint256.NewInt(0),
|
||||||
GasTipCap: uint256.NewInt(0),
|
GasTipCap: uint256.NewInt(0),
|
||||||
|
|
@ -363,7 +381,7 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
|
||||||
evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil)
|
evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil)
|
||||||
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
|
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, gasBudget, common.U2560)
|
||||||
if evm.StateDB.AccessEvents() != nil {
|
if evm.StateDB.AccessEvents() != nil {
|
||||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,27 @@ func (result *ExecutionResult) Revert() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) {
|
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
|
||||||
// Set the starting gas for the raw transaction
|
// Set the starting gas for the raw transaction
|
||||||
var gas uint64
|
var gas vm.GasCosts
|
||||||
if isContractCreation && isHomestead {
|
if isContractCreation && rules.IsHomestead {
|
||||||
gas = params.TxGasContractCreation
|
if rules.IsAmsterdam {
|
||||||
|
gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
|
||||||
|
gas.StateGas = params.AccountCreationSize * costPerStateByte
|
||||||
|
} else {
|
||||||
|
gas.RegularGas = params.TxGasContractCreation
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
gas = params.TxGas
|
gas.RegularGas = params.TxGas
|
||||||
|
}
|
||||||
|
// Add gas for authorizations
|
||||||
|
if authList != nil {
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
|
||||||
|
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
|
||||||
|
} else {
|
||||||
|
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataLen := uint64(len(data))
|
dataLen := uint64(len(data))
|
||||||
// Bump the required gas by the amount of transactional data
|
// Bump the required gas by the amount of transactional data
|
||||||
|
|
@ -85,59 +99,56 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
|
|
||||||
// Make sure we don't exceed uint64 for all data combinations
|
// Make sure we don't exceed uint64 for all data combinations
|
||||||
nonZeroGas := params.TxDataNonZeroGasFrontier
|
nonZeroGas := params.TxDataNonZeroGasFrontier
|
||||||
if isEIP2028 {
|
if rules.IsIstanbul {
|
||||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||||
}
|
}
|
||||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += nz * nonZeroGas
|
gas.RegularGas += nz * nonZeroGas
|
||||||
|
|
||||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += z * params.TxDataZeroGas
|
gas.RegularGas += z * params.TxDataZeroGas
|
||||||
|
|
||||||
if isContractCreation && isEIP3860 {
|
if isContractCreation && rules.IsShanghai {
|
||||||
lenWords := toWordSize(dataLen)
|
lenWords := toWordSize(dataLen)
|
||||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += lenWords * params.InitCodeWordGas
|
gas.RegularGas += lenWords * params.InitCodeWordGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if accessList != nil {
|
if accessList != nil {
|
||||||
addresses := uint64(len(accessList))
|
addresses := uint64(len(accessList))
|
||||||
storageKeys := uint64(accessList.StorageKeys())
|
storageKeys := uint64(accessList.StorageKeys())
|
||||||
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
|
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += addresses * params.TxAccessListAddressGas
|
gas.RegularGas += addresses * params.TxAccessListAddressGas
|
||||||
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
|
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += storageKeys * params.TxAccessListStorageKeyGas
|
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
|
||||||
|
|
||||||
// EIP-7981: access list data is charged in addition to the base charge.
|
// EIP-7981: access list data is charged in addition to the base charge.
|
||||||
if isAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
const (
|
const (
|
||||||
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||||
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||||
)
|
)
|
||||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += addresses * addressCost
|
gas.RegularGas += addresses * addressCost
|
||||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += storageKeys * storageKeyCost
|
gas.RegularGas += storageKeys * storageKeyCost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if authList != nil {
|
return gas, nil
|
||||||
gas += uint64(len(authList)) * params.CallNewAccountGas
|
|
||||||
}
|
|
||||||
return vm.GasCosts{RegularGas: gas}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||||
|
|
@ -338,12 +349,11 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err
|
||||||
// 5. Run Script section
|
// 5. Run Script section
|
||||||
// 6. Derive new state root
|
// 6. Derive new state root
|
||||||
type stateTransition struct {
|
type stateTransition struct {
|
||||||
gp *GasPool
|
gp *GasPool
|
||||||
msg *Message
|
msg *Message
|
||||||
initialBudget vm.GasBudget
|
gasRemaining vm.GasBudget
|
||||||
gasRemaining vm.GasBudget
|
state vm.StateDB
|
||||||
state vm.StateDB
|
evm *vm.EVM
|
||||||
evm *vm.EVM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStateTransition initialises and returns a new state transition object.
|
// newStateTransition initialises and returns a new state transition object.
|
||||||
|
|
@ -364,6 +374,24 @@ func (st *stateTransition) to() common.Address {
|
||||||
return *st.msg.To
|
return *st.msg.To
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buyGas pre-pays gas from the sender's balance and initializes the
|
||||||
|
// transaction's gas budget. It is invoked at the tail of preCheck.
|
||||||
|
//
|
||||||
|
// The balance requirement is the worst-case ETH the tx may need to lock
|
||||||
|
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
|
||||||
|
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
|
||||||
|
// returns ErrInsufficientFunds. After the check, the sender is actually
|
||||||
|
// debited `msg.GasLimit × msg.GasPrice` (plus `blobGas × blobBaseFee`
|
||||||
|
// under Cancun), the cap-vs-tip differential is settled at tx end.
|
||||||
|
//
|
||||||
|
// The gas budget is seeded into both `initialBudget` (frozen snapshot
|
||||||
|
// for tx-end accounting) and `gasRemaining` (live running balance):
|
||||||
|
//
|
||||||
|
// - Pre-Amsterdam: one-dimensional regular budget equal to
|
||||||
|
// `msg.GasLimit`; the state-gas reservoir is zero.
|
||||||
|
// - Amsterdam+ (EIP-8037): two-dimensional budget. Regular gas is
|
||||||
|
// capped at `MaxTxGas` (EIP-7825, 16_777_216); any excess from
|
||||||
|
// `msg.GasLimit` above that cap becomes the state-gas reservoir.
|
||||||
func (st *stateTransition) buyGas() error {
|
func (st *stateTransition) buyGas() error {
|
||||||
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
||||||
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
||||||
|
|
@ -416,22 +444,53 @@ func (st *stateTransition) buyGas() error {
|
||||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||||
}
|
}
|
||||||
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||||
|
|
||||||
|
// Reserve the gas budget in the block gas pool
|
||||||
|
var err error
|
||||||
|
if isAmsterdam {
|
||||||
|
err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit)
|
||||||
|
} else {
|
||||||
|
err = st.gp.CheckGasLegacy(st.msg.GasLimit)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.evm.Config.Tracer.HasGasHook() {
|
// After Amsterdam we limit the regular gas to 16M, the data gas to the transaction limit
|
||||||
empty := vm.GasBudget{}
|
limit := st.msg.GasLimit
|
||||||
initial := vm.NewGasBudget(st.msg.GasLimit)
|
if isAmsterdam {
|
||||||
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance)
|
limit = min(st.msg.GasLimit, params.MaxTxGas)
|
||||||
}
|
}
|
||||||
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
|
st.gasRemaining = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
|
||||||
st.initialBudget = st.gasRemaining.Copy()
|
|
||||||
|
|
||||||
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
|
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
|
||||||
|
}
|
||||||
|
// Deduct the gas cost from the sender's balance
|
||||||
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preCheck performs all pre-execution validation that does not require
|
||||||
|
// the EVM to run, then ends by calling buyGas to lock in the gas budget.
|
||||||
|
// It returns a consensus error if any of the following fail:
|
||||||
|
//
|
||||||
|
// - Sender nonce matches state and is not at 2^64-1 (EIP-2681).
|
||||||
|
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam
|
||||||
|
// (the cap also bounds the regular dimension after Amsterdam, but
|
||||||
|
// it is enforced there via the two-dimensional budget in buyGas).
|
||||||
|
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
|
||||||
|
// EIP-7702 delegation designator.
|
||||||
|
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
|
||||||
|
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
|
||||||
|
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
|
||||||
|
// - Blob fee-cap not below the current blob base fee (Cancun+).
|
||||||
|
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
|
||||||
|
// authorization list.
|
||||||
|
//
|
||||||
|
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
|
||||||
|
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
|
||||||
func (st *stateTransition) preCheck() error {
|
func (st *stateTransition) preCheck() error {
|
||||||
// Only check transactions that are not fake
|
// Only check transactions that are not fake
|
||||||
msg := st.msg
|
msg := st.msg
|
||||||
|
|
@ -449,11 +508,13 @@ func (st *stateTransition) preCheck() error {
|
||||||
msg.From.Hex(), stNonce)
|
msg.From.Hex(), stNonce)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
var (
|
||||||
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
isOsaka = st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||||
|
isAmsterdam = st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||||
|
)
|
||||||
if !msg.SkipTransactionChecks {
|
if !msg.SkipTransactionChecks {
|
||||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||||
if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas {
|
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||||
}
|
}
|
||||||
// Make sure the sender is an EOA
|
// Make sure the sender is an EOA
|
||||||
|
|
@ -527,40 +588,33 @@ func (st *stateTransition) preCheck() error {
|
||||||
return st.buyGas()
|
return st.buyGas()
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute will transition the state by applying the current message and
|
// execute transitions the state by applying the current message and
|
||||||
// returning the evm execution result with following fields.
|
// returns the EVM execution result with the following fields:
|
||||||
//
|
//
|
||||||
// - used gas: total gas used (including gas being refunded)
|
// - used gas: total gas used, including gas refunded
|
||||||
// - returndata: the returned data from evm
|
// - peak used gas: maximum gas used before applying refunds
|
||||||
// - concrete execution error: various EVM errors which abort the execution, e.g.
|
// - returndata: data returned by the EVM
|
||||||
// ErrOutOfGas, ErrExecutionReverted
|
// - execution error: EVM-level errors that abort execution, such as
|
||||||
|
// ErrOutOfGas or ErrExecutionReverted
|
||||||
//
|
//
|
||||||
// However if any consensus issue encountered, return the error directly with
|
// If a consensus error is encountered, it is returned directly with a
|
||||||
// nil evm execution result.
|
// nil EVM execution result.
|
||||||
func (st *stateTransition) execute() (*ExecutionResult, error) {
|
func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// First check this message satisfies all consensus rules before
|
// Validate the message and pre-pay gas.
|
||||||
// applying the message. The rules include these clauses
|
|
||||||
//
|
|
||||||
// 1. the nonce of the message caller is correct
|
|
||||||
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
|
|
||||||
// 3. the amount of gas required is available in the block
|
|
||||||
// 4. the purchased gas is enough to cover intrinsic usage
|
|
||||||
// 5. there is no overflow when calculating intrinsic gas
|
|
||||||
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
|
||||||
|
|
||||||
// Check clauses 1-3, buy gas if everything is correct
|
|
||||||
if err := st.preCheck(); err != nil {
|
if err := st.preCheck(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Charge intrinsic gas (with overflow detection inside IntrinsicGas).
|
||||||
|
// Under Amsterdam the cost is two-dimensional and Charge debits both
|
||||||
|
// regular and state in one step.
|
||||||
var (
|
var (
|
||||||
msg = st.msg
|
msg = st.msg
|
||||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
||||||
contractCreation = msg.To == nil
|
contractCreation = msg.To == nil
|
||||||
floorDataGas uint64
|
floorDataGas uint64
|
||||||
)
|
)
|
||||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
|
||||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -571,15 +625,24 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
if st.evm.Config.Tracer.HasGasHook() {
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||||
}
|
}
|
||||||
// Gas limit suffices for the floor data cost (EIP-7623)
|
|
||||||
|
// Validate the EIP-7623 calldata floor against the gas limit. The floor inflates
|
||||||
|
// the total gas usage at tx end, so the gas limit must be sufficient to cover that.
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Make sure the transaction has sufficient gas allowance to
|
||||||
|
// pay the floor cost.
|
||||||
if msg.GasLimit < floorDataGas {
|
if msg.GasLimit < floorDataGas {
|
||||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
||||||
}
|
}
|
||||||
|
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||||
|
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||||
|
if rules.IsAmsterdam && max(cost.RegularGas, floorDataGas) > params.MaxTxGas {
|
||||||
|
return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rules.IsEIP4762 {
|
if rules.IsEIP4762 {
|
||||||
|
|
@ -590,7 +653,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check clause 6
|
// Top-call affordability, the sender must still be able to cover the value
|
||||||
|
// transfer of the top frame after gas pre-pay.
|
||||||
value := msg.Value
|
value := msg.Value
|
||||||
if value == nil {
|
if value == nil {
|
||||||
value = new(uint256.Int)
|
value = new(uint256.Int)
|
||||||
|
|
@ -599,36 +663,43 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the init code size has been exceeded.
|
|
||||||
if contractCreation {
|
|
||||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the preparatory steps for state transition which includes:
|
// Execute the preparatory steps for state transition which includes:
|
||||||
// - prepare accessList(post-berlin)
|
// - prepare accessList(post-berlin)
|
||||||
// - reset transient storage(EIP-1153)
|
// - reset transient storage(EIP-1153)
|
||||||
// - enable block-level accessList construction (EIP-7928)
|
// - enable block-level accessList construction (EIP-7928)
|
||||||
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
||||||
|
|
||||||
|
// Execute the top-most frame
|
||||||
var (
|
var (
|
||||||
ret []byte
|
ret []byte
|
||||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||||
|
result vm.GasBudget
|
||||||
|
|
||||||
|
// Capture the forwarded regular-gas amount BEFORE ForwardAll consumes
|
||||||
|
// it, so Absorb can back out state-gas spillover from UsedRegularGas
|
||||||
|
// per EIP-8037.
|
||||||
|
forwarded = st.gasRemaining.RegularGas
|
||||||
)
|
)
|
||||||
if contractCreation {
|
if contractCreation {
|
||||||
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
|
// Check whether the init code size has been exceeded.
|
||||||
|
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Execute the transaction's creation.
|
||||||
|
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||||
|
st.gasRemaining.Absorb(result, forwarded)
|
||||||
|
|
||||||
|
// If the contract creation failed, refund the account-creation state
|
||||||
|
// gas pre-charged in IntrinsicGas.
|
||||||
|
if rules.IsAmsterdam && vmerr != nil {
|
||||||
|
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Increment the nonce for the next transaction.
|
// Increment the nonce for the next transaction.
|
||||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||||
|
|
||||||
// Apply EIP-7702 authorizations.
|
// Apply EIP-7702 authorizations.
|
||||||
if msg.SetCodeAuthorizations != nil {
|
st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
|
||||||
for _, auth := range msg.SetCodeAuthorizations {
|
|
||||||
// Note errors are ignored, we simply skip invalid authorizations here.
|
|
||||||
st.applyAuthorization(&auth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform convenience warming of sender's delegation target. Although the
|
// Perform convenience warming of sender's delegation target. Although the
|
||||||
// sender is already warmed in Prepare(..), it's possible a delegation to
|
// sender is already warmed in Prepare(..), it's possible a delegation to
|
||||||
|
|
@ -638,44 +709,18 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
||||||
st.state.AddAddressToAccessList(addr)
|
st.state.AddAddressToAccessList(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the transaction's call.
|
// Execute the transaction's call.
|
||||||
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
|
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||||
|
st.gasRemaining.Absorb(result, forwarded)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record the gas used excluding gas refunds. This value represents the actual
|
// Settle down the gas usage and refund the ETH back if any remaining
|
||||||
// gas allowance required to complete execution.
|
gasUsed, peakUsed, err := st.settleGas(rules, floorDataGas)
|
||||||
peakGasUsed := st.gasUsed()
|
|
||||||
|
|
||||||
// Compute refund counter, capped to a refund quotient.
|
|
||||||
st.gasRemaining.Refund(st.calcRefund())
|
|
||||||
|
|
||||||
if rules.IsPrague {
|
|
||||||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
|
||||||
if used := st.gasUsed(); used < floorDataGas {
|
|
||||||
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
|
|
||||||
if st.evm.Config.Tracer.HasGasHook() {
|
|
||||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if peakGasUsed < floorDataGas {
|
|
||||||
peakGasUsed = floorDataGas
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return gas to the user
|
|
||||||
st.returnGas()
|
|
||||||
|
|
||||||
// Return gas to the gas pool
|
|
||||||
if rules.IsAmsterdam {
|
|
||||||
// Refund is excluded for returning
|
|
||||||
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed())
|
|
||||||
} else {
|
|
||||||
// Refund is included for returning
|
|
||||||
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed())
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pay the effective transaction fee to the specific coinbase
|
||||||
effectiveTip := msg.GasPrice
|
effectiveTip := msg.GasPrice
|
||||||
if rules.IsLondon {
|
if rules.IsLondon {
|
||||||
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
|
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
|
||||||
|
|
@ -684,13 +729,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
}
|
}
|
||||||
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
|
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
// Skip fee payment when NoBaseFee is set and the fee fields
|
||||||
// are 0. This avoids a negative effectiveTip being applied to
|
// are 0. This avoids a negative effectiveTip being applied to
|
||||||
// the coinbase when simulating calls.
|
// the coinbase when simulating calls.
|
||||||
} else {
|
} else {
|
||||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
fee := new(uint256.Int).SetUint64(gasUsed)
|
||||||
fee.Mul(fee, effectiveTip)
|
fee.Mul(fee, effectiveTip)
|
||||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||||
|
|
||||||
|
|
@ -699,19 +743,105 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EIP-7708: Emit the ETH-burn logs
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
|
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
|
||||||
st.evm.StateDB.AddLog(log)
|
st.evm.StateDB.AddLog(log)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ExecutionResult{
|
return &ExecutionResult{
|
||||||
UsedGas: st.gasUsed(),
|
UsedGas: gasUsed,
|
||||||
MaxUsedGas: peakGasUsed,
|
MaxUsedGas: peakUsed,
|
||||||
Err: vmerr,
|
Err: vmerr,
|
||||||
ReturnData: ret,
|
ReturnData: ret,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// settleGas finalizes the per-tx gas accounting after EVM execution:
|
||||||
|
//
|
||||||
|
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
|
||||||
|
// tx_state_gas) before any refund or floor:
|
||||||
|
//
|
||||||
|
// tx_gas_used_before_refund = tx.gas - gas_left - state_gas_reservoir
|
||||||
|
// tx_state_gas = state_gas_used
|
||||||
|
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
||||||
|
//
|
||||||
|
// - Computes the receipt scalar tx_gas_used by applying the EIP-3529
|
||||||
|
// refund (capped at tx_gas_used_before_refund/5) and the EIP-7623
|
||||||
|
// calldata floor:
|
||||||
|
//
|
||||||
|
// tx_gas_used = max(tx_gas_used_before_refund - tx_gas_refund, calldata_floor)
|
||||||
|
//
|
||||||
|
// - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam).
|
||||||
|
//
|
||||||
|
// - Refunds the leftover gas to the sender as ETH.
|
||||||
|
//
|
||||||
|
// Returns the receipt-level tx_gas_used and the pre-refund peak (consumed
|
||||||
|
// by gas-estimation callers via ExecutionResult.MaxUsedGas). UsedStateGas
|
||||||
|
// should never become negative in the top-most frame, since state-gas
|
||||||
|
// refunds occur only when state creation is reverted within the same
|
||||||
|
// transaction and clearing pre-existing state is never refunded.
|
||||||
|
func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) {
|
||||||
|
if st.gasRemaining.UsedStateGas < 0 {
|
||||||
|
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
||||||
|
}
|
||||||
|
txStateGas := uint64(st.gasRemaining.UsedStateGas)
|
||||||
|
|
||||||
|
// EIP-8037:
|
||||||
|
// tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir
|
||||||
|
// tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used
|
||||||
|
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
||||||
|
gasLeft := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
|
||||||
|
gasUsedBeforeRefund := st.msg.GasLimit - gasLeft
|
||||||
|
|
||||||
|
if gasUsedBeforeRefund < txStateGas {
|
||||||
|
return 0, 0, fmt.Errorf("negative topmost frame regular gas usage, total: %d, state: %d", gasUsedBeforeRefund, txStateGas)
|
||||||
|
}
|
||||||
|
txRegularGas := gasUsedBeforeRefund - txStateGas
|
||||||
|
|
||||||
|
// EIP-3529: tx_gas_refund = min(tx_gas_used_before_refund/5, refund_counter).
|
||||||
|
refund := st.calcRefund(gasUsedBeforeRefund)
|
||||||
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
|
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: gasLeft}, tracing.Gas{Regular: gasLeft + refund}, tracing.GasChangeTxRefunds)
|
||||||
|
}
|
||||||
|
gasLeft += refund
|
||||||
|
gasUsed = gasUsedBeforeRefund - refund
|
||||||
|
|
||||||
|
// EIP-7623: tx_gas_used = max(tx_gas_used_after_refund, calldata_floor).
|
||||||
|
peakUsed = gasUsedBeforeRefund
|
||||||
|
if rules.IsPrague && gasUsed < floorDataGas {
|
||||||
|
diff := floorDataGas - gasUsed
|
||||||
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
|
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: gasLeft}, tracing.Gas{Regular: gasLeft - diff}, tracing.GasChangeTxDataFloor)
|
||||||
|
}
|
||||||
|
gasLeft -= diff
|
||||||
|
gasUsed = floorDataGas
|
||||||
|
peakUsed = max(peakUsed, floorDataGas)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = st.gp.ChargeGasLegacy(gasLeft, gasUsed); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refund leftover gas to the sender as ETH.
|
||||||
|
if gasLeft > 0 {
|
||||||
|
refund := new(uint256.Int).Mul(uint256.NewInt(gasLeft), st.msg.GasPrice)
|
||||||
|
st.state.AddBalance(st.msg.From, refund, tracing.BalanceIncreaseGasReturn)
|
||||||
|
|
||||||
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
|
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: gasLeft}, tracing.Gas{}, tracing.GasChangeTxLeftOverReturned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gasUsed, peakUsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateAuthorization validates an EIP-7702 authorization against the state.
|
// validateAuthorization validates an EIP-7702 authorization against the state.
|
||||||
func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) {
|
func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) {
|
||||||
// Verify chain ID is null or equal to current chain ID.
|
// Verify chain ID is null or equal to current chain ID.
|
||||||
|
|
@ -743,72 +873,93 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
||||||
return authority, nil
|
return authority, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyAuthorization applies an EIP-7702 code delegation to the state.
|
// applyAuthorization applies an EIP-7702 code delegation to the state and,
|
||||||
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error {
|
// under EIP-8037, reconciles the per-authorization intrinsic state-gas pre-
|
||||||
|
// charge so that, per authority:
|
||||||
|
//
|
||||||
|
// - the account portion (AccountCreationSize × CPSB) is charged at most
|
||||||
|
// once, and only when the account did not exist before the tx
|
||||||
|
//
|
||||||
|
// - the delegation-indicator portion (AuthorizationCreationSize × CPSB) is
|
||||||
|
// charged at most once, and only when the authority ends the tx delegated
|
||||||
|
// having started it undelegated.
|
||||||
|
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
|
||||||
authority, err := st.validateAuthorization(auth)
|
authority, err := st.validateAuthorization(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
prevDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||||
|
|
||||||
// If the account already exists in state, refund the new account cost
|
if !rules.IsAmsterdam {
|
||||||
// charged in the intrinsic calculation.
|
if st.state.Exist(authority) {
|
||||||
if st.state.Exist(authority) {
|
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
}
|
||||||
|
} else {
|
||||||
|
if st.state.Exist(authority) {
|
||||||
|
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
|
}
|
||||||
|
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||||
|
|
||||||
|
preDelegated, ok := delegates[authority]
|
||||||
|
if !ok {
|
||||||
|
preDelegated = curDelegated
|
||||||
|
delegates[authority] = preDelegated
|
||||||
|
}
|
||||||
|
if auth.Address == (common.Address{}) {
|
||||||
|
// Clearing writes no indicator, refill this auth's state charge.
|
||||||
|
st.gasRemaining.RefundState(authBase)
|
||||||
|
|
||||||
|
// The indicator was created by an earlier auth within the same
|
||||||
|
// transaction, refill the state charge as it's no longer justified.
|
||||||
|
if curDelegated && !preDelegated {
|
||||||
|
st.gasRemaining.RefundState(authBase)
|
||||||
|
}
|
||||||
|
} else if curDelegated || preDelegated {
|
||||||
|
// The 23-byte slot is already occupied, overwriting it writes no
|
||||||
|
// new bytes, refill the state charge.
|
||||||
|
st.gasRemaining.RefundState(authBase)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update nonce and account code.
|
// Update nonce and account code.
|
||||||
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
||||||
|
|
||||||
|
// Delegation to zero address means clear.
|
||||||
if auth.Address == (common.Address{}) {
|
if auth.Address == (common.Address{}) {
|
||||||
// Delegation to zero address means clear.
|
if curDelegated {
|
||||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Install delegation to auth.Address if the delegation changed
|
||||||
// Otherwise install delegation to auth.Address.
|
if !curDelegated || auth.Address != prevDelegation {
|
||||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcRefund computes refund counter, capped to a refund quotient.
|
// applyAuthorizations applies an EIP-7702 code delegation to the state.
|
||||||
func (st *stateTransition) calcRefund() vm.GasBudget {
|
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
|
||||||
var refund uint64
|
preDelegated := make(map[common.Address]bool)
|
||||||
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
for _, auth := range auths {
|
||||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
st.applyAuthorization(rules, &auth, preDelegated)
|
||||||
refund = st.gasUsed() / params.RefundQuotient
|
|
||||||
} else {
|
|
||||||
// After EIP-3529: refunds are capped to gasUsed / 5
|
|
||||||
refund = st.gasUsed() / params.RefundQuotientEIP3529
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcRefund computes the EIP-3529 refund cap against tx_gas_used_before_refund.
|
||||||
|
func (st *stateTransition) calcRefund(gasUsedBeforeRefund uint64) uint64 {
|
||||||
|
quotient := params.RefundQuotient
|
||||||
|
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||||
|
quotient = params.RefundQuotientEIP3529
|
||||||
|
}
|
||||||
|
refund := gasUsedBeforeRefund / quotient
|
||||||
if refund > st.state.GetRefund() {
|
if refund > st.state.GetRefund() {
|
||||||
refund = st.state.GetRefund()
|
refund = st.state.GetRefund()
|
||||||
}
|
}
|
||||||
if refund > 0 && st.evm.Config.Tracer.HasGasHook() {
|
return refund
|
||||||
after := st.gasRemaining
|
|
||||||
after.RegularGas += refund
|
|
||||||
|
|
||||||
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds)
|
|
||||||
}
|
|
||||||
return vm.NewGasBudget(refund)
|
|
||||||
}
|
|
||||||
|
|
||||||
// returnGas returns ETH for remaining gas,
|
|
||||||
// exchanged at the original rate.
|
|
||||||
func (st *stateTransition) returnGas() {
|
|
||||||
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
|
||||||
remaining.Mul(remaining, st.msg.GasPrice)
|
|
||||||
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
|
||||||
|
|
||||||
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
|
|
||||||
after := st.gasRemaining
|
|
||||||
after.RegularGas = 0
|
|
||||||
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// gasUsed returns the amount of gas used up by the state transition.
|
|
||||||
func (st *stateTransition) gasUsed() uint64 {
|
|
||||||
return st.gasRemaining.Used(st.initialBudget)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// blobGasUsed returns the amount of blob gas used by the message.
|
// blobGasUsed returns the amount of blob gas used by the message.
|
||||||
|
|
|
||||||
|
|
@ -155,50 +155,50 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028 bool
|
isEIP2028 bool
|
||||||
isEIP3860 bool
|
isEIP3860 bool
|
||||||
isAmsterdam bool
|
isAmsterdam bool
|
||||||
want uint64
|
want vm.GasCosts
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "frontier/empty-call",
|
name: "frontier/empty-call",
|
||||||
want: params.TxGas,
|
want: vm.GasCosts{RegularGas: params.TxGas},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "frontier/contract-creation-pre-homestead",
|
name: "frontier/contract-creation-pre-homestead",
|
||||||
creation: true,
|
creation: true,
|
||||||
isHomestead: false,
|
isHomestead: false,
|
||||||
// pre-homestead, contract creation still uses TxGas
|
// pre-homestead, contract creation still uses TxGas
|
||||||
want: params.TxGas,
|
want: vm.GasCosts{RegularGas: params.TxGas},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "homestead/contract-creation",
|
name: "homestead/contract-creation",
|
||||||
creation: true,
|
creation: true,
|
||||||
isHomestead: true,
|
isHomestead: true,
|
||||||
want: params.TxGasContractCreation,
|
want: vm.GasCosts{RegularGas: params.TxGasContractCreation},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "frontier/non-zero-data",
|
name: "frontier/non-zero-data",
|
||||||
data: bytes.Repeat([]byte{0xff}, 100),
|
data: bytes.Repeat([]byte{0xff}, 100),
|
||||||
// 100 nz bytes * 68 (frontier)
|
// 100 nz bytes * 68 (frontier)
|
||||||
want: params.TxGas + 100*params.TxDataNonZeroGasFrontier,
|
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasFrontier},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "istanbul/non-zero-data",
|
name: "istanbul/non-zero-data",
|
||||||
data: bytes.Repeat([]byte{0xff}, 100),
|
data: bytes.Repeat([]byte{0xff}, 100),
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
// 100 nz bytes * 16 (post-EIP2028)
|
// 100 nz bytes * 16 (post-EIP2028)
|
||||||
want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028,
|
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasEIP2028},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "istanbul/zero-data",
|
name: "istanbul/zero-data",
|
||||||
data: bytes.Repeat([]byte{0x00}, 100),
|
data: bytes.Repeat([]byte{0x00}, 100),
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
// 100 zero bytes * 4
|
// 100 zero bytes * 4
|
||||||
want: params.TxGas + 100*params.TxDataZeroGas,
|
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataZeroGas},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "istanbul/mixed-data",
|
name: "istanbul/mixed-data",
|
||||||
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
|
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028,
|
want: vm.GasCosts{RegularGas: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "shanghai/init-code-word-gas",
|
name: "shanghai/init-code-word-gas",
|
||||||
|
|
@ -208,7 +208,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isEIP3860: true,
|
isEIP3860: true,
|
||||||
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
|
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
|
||||||
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "shanghai/init-code-non-multiple-of-32",
|
name: "shanghai/init-code-non-multiple-of-32",
|
||||||
|
|
@ -217,7 +217,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isHomestead: true,
|
isHomestead: true,
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isEIP3860: true,
|
isEIP3860: true,
|
||||||
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "berlin/access-list",
|
name: "berlin/access-list",
|
||||||
|
|
@ -227,7 +227,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
},
|
},
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
// 2 addrs * 2400 + 3 keys * 1900
|
// 2 addrs * 2400 + 3 keys * 1900
|
||||||
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas,
|
want: vm.GasCosts{RegularGas: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/access-list-extra-cost",
|
name: "amsterdam/access-list-extra-cost",
|
||||||
|
|
@ -238,9 +238,9 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
// base access-list charge + EIP-7981 extra
|
// base access-list charge + EIP-7981 extra
|
||||||
want: params.TxGas +
|
want: vm.GasCosts{RegularGas: params.TxGas +
|
||||||
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
|
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
|
||||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost,
|
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prague/auth-list",
|
name: "prague/auth-list",
|
||||||
|
|
@ -250,8 +250,54 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
{Address: addr1},
|
{Address: addr1},
|
||||||
},
|
},
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
// 3 auths * 25000
|
// 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple)
|
||||||
want: params.TxGas + 3*params.CallNewAccountGas,
|
want: vm.GasCosts{RegularGas: params.TxGas + 3*params.CallNewAccountGas},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amsterdam/contract-creation-empty",
|
||||||
|
creation: true,
|
||||||
|
isHomestead: true,
|
||||||
|
isEIP2028: true,
|
||||||
|
isAmsterdam: true,
|
||||||
|
// EIP-8037: creation regular gas is TxGas + CreateGasAmsterdam (not TxGasContractCreation),
|
||||||
|
// and account-creation cost is moved to state gas.
|
||||||
|
want: vm.GasCosts{
|
||||||
|
RegularGas: params.TxGas + params.CreateGasAmsterdam,
|
||||||
|
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amsterdam/contract-creation-init-code",
|
||||||
|
data: bytes.Repeat([]byte{0x00}, 64), // 2 words of init code
|
||||||
|
creation: true,
|
||||||
|
isHomestead: true,
|
||||||
|
isEIP2028: true,
|
||||||
|
isEIP3860: true, // Shanghai gates init-code word gas
|
||||||
|
isAmsterdam: true,
|
||||||
|
want: vm.GasCosts{
|
||||||
|
RegularGas: params.TxGas + params.CreateGasAmsterdam +
|
||||||
|
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||||
|
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amsterdam/contract-creation-with-access-list",
|
||||||
|
data: bytes.Repeat([]byte{0xff}, 32), // 1 word of non-zero init code
|
||||||
|
accessList: types.AccessList{
|
||||||
|
{Address: addr1, StorageKeys: []common.Hash{key1}},
|
||||||
|
},
|
||||||
|
creation: true,
|
||||||
|
isHomestead: true,
|
||||||
|
isEIP2028: true,
|
||||||
|
isEIP3860: true,
|
||||||
|
isAmsterdam: true,
|
||||||
|
want: vm.GasCosts{
|
||||||
|
RegularGas: params.TxGas + params.CreateGasAmsterdam +
|
||||||
|
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
||||||
|
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||||
|
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
||||||
|
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/combined",
|
name: "amsterdam/combined",
|
||||||
|
|
@ -264,23 +310,34 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
},
|
},
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
want: params.TxGas +
|
// EIP-8037 splits the auth-tuple charge into regular + state gas:
|
||||||
100*params.TxDataNonZeroGasEIP2028 +
|
// regular: TxAuthTupleRegularGas (7500) per auth
|
||||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
|
||||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
want: vm.GasCosts{
|
||||||
1*params.CallNewAccountGas,
|
RegularGas: params.TxGas +
|
||||||
|
100*params.TxDataNonZeroGasEIP2028 +
|
||||||
|
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||||
|
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||||
|
1*params.TxAuthTupleRegularGas,
|
||||||
|
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rules := params.Rules{
|
||||||
|
IsHomestead: tt.isHomestead,
|
||||||
|
IsIstanbul: tt.isEIP2028,
|
||||||
|
IsShanghai: tt.isEIP3860,
|
||||||
|
IsAmsterdam: tt.isAmsterdam,
|
||||||
|
}
|
||||||
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
||||||
tt.creation, tt.isHomestead, tt.isEIP2028, tt.isEIP3860, tt.isAmsterdam)
|
tt.creation, rules, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
want := vm.GasCosts{RegularGas: tt.want}
|
if got != tt.want {
|
||||||
if got != want {
|
t.Fatalf("gas mismatch: got %+v, want %+v", got, tt.want)
|
||||||
t.Fatalf("gas mismatch: got %+v, want %+v", got, want)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,22 @@ func _() {
|
||||||
_ = x[GasChangeWitnessCodeChunk-17]
|
_ = x[GasChangeWitnessCodeChunk-17]
|
||||||
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
||||||
_ = x[GasChangeTxDataFloor-19]
|
_ = x[GasChangeTxDataFloor-19]
|
||||||
|
_ = x[GasChangeAccountCreation-20]
|
||||||
_ = x[GasChangeIgnored-255]
|
_ = x[GasChangeIgnored-255]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloor"
|
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorAccountCreation"
|
||||||
_GasChangeReason_name_1 = "Ignored"
|
_GasChangeReason_name_1 = "Ignored"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353}
|
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 368}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i GasChangeReason) String() string {
|
func (i GasChangeReason) String() string {
|
||||||
switch {
|
switch {
|
||||||
case i <= 19:
|
case i <= 20:
|
||||||
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
|
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
|
||||||
case i == 255:
|
case i == 255:
|
||||||
return _GasChangeReason_name_1
|
return _GasChangeReason_name_1
|
||||||
|
|
|
||||||
|
|
@ -472,6 +472,10 @@ const (
|
||||||
// transaction data. This change will always be a negative change.
|
// transaction data. This change will always be a negative change.
|
||||||
GasChangeTxDataFloor GasChangeReason = 19
|
GasChangeTxDataFloor GasChangeReason = 19
|
||||||
|
|
||||||
|
// GasChangeAccountCreation represents the state gas charging for account
|
||||||
|
// creation inside the call/create frame.
|
||||||
|
GasChangeAccountCreation GasChangeReason = 20
|
||||||
|
|
||||||
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
||||||
// it will be "manually" tracked by a direct emit of the gas change event.
|
// it will be "manually" tracked by a direct emit of the gas change event.
|
||||||
GasChangeIgnored GasChangeReason = 0xFF
|
GasChangeIgnored GasChangeReason = 0xFF
|
||||||
|
|
|
||||||
|
|
@ -279,10 +279,9 @@ func encodeForNetwork(storedRLP []byte, version uint) ([]byte, error) {
|
||||||
|
|
||||||
// 3. Find the version of sidecar.
|
// 3. Find the version of sidecar.
|
||||||
sidecarVersion, _, err := rlp.SplitUint64(sidecarElems[0])
|
sidecarVersion, _, err := rlp.SplitUint64(sidecarElems[0])
|
||||||
if err != nil || sidecarVersion > 255 {
|
if err != nil || sidecarVersion > 255 || sidecarVersion == 0 {
|
||||||
return nil, fmt.Errorf("invalid version: %w", err)
|
return nil, fmt.Errorf("invalid version: %w", err)
|
||||||
}
|
}
|
||||||
sidecarVersionByte := byte(sidecarVersion)
|
|
||||||
|
|
||||||
// 4. Extract sidecar elements.
|
// 4. Extract sidecar elements.
|
||||||
commitmentsRLP := sidecarElems[2]
|
commitmentsRLP := sidecarElems[2]
|
||||||
|
|
@ -315,12 +314,7 @@ func encodeForNetwork(storedRLP []byte, version uint) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Reconstruct into the network format.
|
// 6. Reconstruct into the network format.
|
||||||
var outer [][]byte
|
outer := [][]byte{txRLP, sidecarElems[0], blobsField, commitmentsRLP, proofsRLP}
|
||||||
if sidecarVersionByte == types.BlobSidecarVersion0 {
|
|
||||||
outer = [][]byte{txRLP, blobsField, commitmentsRLP, proofsRLP}
|
|
||||||
} else {
|
|
||||||
outer = [][]byte{txRLP, sidecarElems[0], blobsField, commitmentsRLP, proofsRLP}
|
|
||||||
}
|
|
||||||
body, err := rlp.MergeListValues(outer)
|
body, err := rlp.MergeListValues(outer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -634,7 +628,7 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
||||||
p.state = state
|
p.state = state
|
||||||
|
|
||||||
// Create new slotter for pre-Osaka blob configuration.
|
// Create new slotter for pre-Osaka blob configuration.
|
||||||
slotter := newSlotter(params.BlobTxMaxBlobs)
|
slotter := newSlotterEIP7594(params.BlobTxMaxBlobs)
|
||||||
|
|
||||||
// See if we need to migrate the queue blob store after fusaka
|
// See if we need to migrate the queue blob store after fusaka
|
||||||
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
|
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ import (
|
||||||
var (
|
var (
|
||||||
testBlobs []*kzg4844.Blob
|
testBlobs []*kzg4844.Blob
|
||||||
testBlobCommits []kzg4844.Commitment
|
testBlobCommits []kzg4844.Commitment
|
||||||
testBlobProofs []kzg4844.Proof
|
|
||||||
testBlobCellProofs [][]kzg4844.Proof
|
testBlobCellProofs [][]kzg4844.Proof
|
||||||
testBlobVHashes [][32]byte
|
testBlobVHashes [][32]byte
|
||||||
testBlobIndices = make(map[[32]byte]int)
|
testBlobIndices = make(map[[32]byte]int)
|
||||||
|
|
@ -69,9 +68,6 @@ func init() {
|
||||||
testBlobCommit, _ := kzg4844.BlobToCommitment(testBlob)
|
testBlobCommit, _ := kzg4844.BlobToCommitment(testBlob)
|
||||||
testBlobCommits = append(testBlobCommits, testBlobCommit)
|
testBlobCommits = append(testBlobCommits, testBlobCommit)
|
||||||
|
|
||||||
testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit)
|
|
||||||
testBlobProofs = append(testBlobProofs, testBlobProof)
|
|
||||||
|
|
||||||
testBlobCellProof, _ := kzg4844.ComputeCellProofs(testBlob)
|
testBlobCellProof, _ := kzg4844.ComputeCellProofs(testBlob)
|
||||||
testBlobCellProofs = append(testBlobCellProofs, testBlobCellProof)
|
testBlobCellProofs = append(testBlobCellProofs, testBlobCellProof)
|
||||||
|
|
||||||
|
|
@ -244,7 +240,7 @@ func encodeForPool(tx *types.Transaction) []byte {
|
||||||
|
|
||||||
// makeMultiBlobTx is a utility method to construct a random blob tx with
|
// makeMultiBlobTx is a utility method to construct a random blob tx with
|
||||||
// certain number of blobs in its sidecar.
|
// certain number of blobs in its sidecar.
|
||||||
func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey, version byte) *types.Transaction {
|
func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||||
var (
|
var (
|
||||||
blobs []kzg4844.Blob
|
blobs []kzg4844.Blob
|
||||||
blobHashes []common.Hash
|
blobHashes []common.Hash
|
||||||
|
|
@ -254,12 +250,8 @@ func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCa
|
||||||
for i := 0; i < blobCount; i++ {
|
for i := 0; i < blobCount; i++ {
|
||||||
blobs = append(blobs, *testBlobs[blobOffset+i])
|
blobs = append(blobs, *testBlobs[blobOffset+i])
|
||||||
commitments = append(commitments, testBlobCommits[blobOffset+i])
|
commitments = append(commitments, testBlobCommits[blobOffset+i])
|
||||||
if version == types.BlobSidecarVersion0 {
|
cellProofs, _ := kzg4844.ComputeCellProofs(testBlobs[blobOffset+i])
|
||||||
proofs = append(proofs, testBlobProofs[blobOffset+i])
|
proofs = append(proofs, cellProofs...)
|
||||||
} else {
|
|
||||||
cellProofs, _ := kzg4844.ComputeCellProofs(testBlobs[blobOffset+i])
|
|
||||||
proofs = append(proofs, cellProofs...)
|
|
||||||
}
|
|
||||||
blobHashes = append(blobHashes, testBlobVHashes[blobOffset+i])
|
blobHashes = append(blobHashes, testBlobVHashes[blobOffset+i])
|
||||||
}
|
}
|
||||||
blobtx := &types.BlobTx{
|
blobtx := &types.BlobTx{
|
||||||
|
|
@ -271,7 +263,7 @@ func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCa
|
||||||
BlobFeeCap: uint256.NewInt(blobFeeCap),
|
BlobFeeCap: uint256.NewInt(blobFeeCap),
|
||||||
BlobHashes: blobHashes,
|
BlobHashes: blobHashes,
|
||||||
Value: uint256.NewInt(100),
|
Value: uint256.NewInt(100),
|
||||||
Sidecar: types.NewBlobTxSidecar(version, blobs, commitments, proofs),
|
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, blobs, commitments, proofs),
|
||||||
}
|
}
|
||||||
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
|
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +295,7 @@ func makeUnsignedTxWithTestBlob(nonce uint64, gasTipCap uint64, gasFeeCap uint64
|
||||||
BlobFeeCap: uint256.NewInt(blobFeeCap),
|
BlobFeeCap: uint256.NewInt(blobFeeCap),
|
||||||
BlobHashes: []common.Hash{testBlobVHashes[blobIdx]},
|
BlobHashes: []common.Hash{testBlobVHashes[blobIdx]},
|
||||||
Value: uint256.NewInt(100),
|
Value: uint256.NewInt(100),
|
||||||
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, []kzg4844.Blob{*testBlobs[blobIdx]}, []kzg4844.Commitment{testBlobCommits[blobIdx]}, []kzg4844.Proof{testBlobProofs[blobIdx]}),
|
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, []kzg4844.Blob{*testBlobs[blobIdx]}, []kzg4844.Commitment{testBlobCommits[blobIdx]}, testBlobCellProofs[blobIdx]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,36 +442,18 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
||||||
hashes = append(hashes, tx.vhashes...)
|
hashes = append(hashes, tx.vhashes...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blobs1, _, proofs1, err := pool.getBlobs(hashes, types.BlobSidecarVersion0)
|
blobs, _, proofs, err := pool.getBlobs(hashes, types.BlobSidecarVersion1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
blobs2, _, proofs2, err := pool.getBlobs(hashes, types.BlobSidecarVersion1)
|
if len(blobs) != len(hashes) || len(proofs) != len(hashes) {
|
||||||
if err != nil {
|
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want blobs %d, want proofs: %d", len(blobs), len(proofs), len(hashes), len(hashes))
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Cross validate what we received vs what we wanted
|
|
||||||
if len(blobs1) != len(hashes) || len(proofs1) != len(hashes) {
|
|
||||||
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs1), len(proofs1), len(hashes))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(blobs2) != len(hashes) || len(proofs2) != len(hashes) {
|
|
||||||
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want blobs %d, want proofs: %d", len(blobs2), len(proofs2), len(hashes), len(hashes))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i, hash := range hashes {
|
for i, hash := range hashes {
|
||||||
// If an item is missing from both, but shouldn't, error
|
|
||||||
if (blobs1[i] == nil || proofs1[i] == nil) && (blobs2[i] == nil || proofs2[i] == nil) {
|
|
||||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Item retrieved, make sure it matches the expectation
|
// Item retrieved, make sure it matches the expectation
|
||||||
index := testBlobIndices[hash]
|
index := testBlobIndices[hash]
|
||||||
if blobs1[i] != nil && (*blobs1[i] != *testBlobs[index] || proofs1[i][0] != testBlobProofs[index]) {
|
if blobs[i] != nil && (*blobs[i] != *testBlobs[index] || !slices.Equal(proofs[i], testBlobCellProofs[index])) {
|
||||||
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if blobs2[i] != nil && (*blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index])) {
|
|
||||||
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
|
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -513,7 +487,7 @@ func TestOpenDrops(t *testing.T) {
|
||||||
storage := t.TempDir()
|
storage := t.TempDir()
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(testMaxBlobsPerBlock), nil)
|
||||||
|
|
||||||
// Insert a malformed transaction to verify that decoding errors (or format
|
// Insert a malformed transaction to verify that decoding errors (or format
|
||||||
// changes) are handled gracefully (case 1)
|
// changes) are handled gracefully (case 1)
|
||||||
|
|
@ -841,7 +815,7 @@ func TestOpenIndex(t *testing.T) {
|
||||||
storage := t.TempDir()
|
storage := t.TempDir()
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(testMaxBlobsPerBlock), nil)
|
||||||
|
|
||||||
// Insert a sequence of transactions with varying price points to check that
|
// Insert a sequence of transactions with varying price points to check that
|
||||||
// the cumulative minimum will be maintained.
|
// the cumulative minimum will be maintained.
|
||||||
|
|
@ -929,7 +903,7 @@ func TestOpenHeap(t *testing.T) {
|
||||||
storage := t.TempDir()
|
storage := t.TempDir()
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(testMaxBlobsPerBlock), nil)
|
||||||
|
|
||||||
// Insert a few transactions from a few accounts. To remove randomness from
|
// Insert a few transactions from a few accounts. To remove randomness from
|
||||||
// the heap initialization, use a deterministic account/tx/priority ordering.
|
// the heap initialization, use a deterministic account/tx/priority ordering.
|
||||||
|
|
@ -1107,7 +1081,7 @@ func TestChangingSlotterSize(t *testing.T) {
|
||||||
storage := t.TempDir()
|
storage := t.TempDir()
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(6), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(6), nil)
|
||||||
|
|
||||||
// Create transactions from a few accounts.
|
// Create transactions from a few accounts.
|
||||||
var (
|
var (
|
||||||
|
|
@ -1119,9 +1093,9 @@ func TestChangingSlotterSize(t *testing.T) {
|
||||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
||||||
|
|
||||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
|
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1)
|
||||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
|
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2)
|
||||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
|
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3)
|
||||||
|
|
||||||
blob1 = encodeForPool(tx1)
|
blob1 = encodeForPool(tx1)
|
||||||
blob2 = encodeForPool(tx2)
|
blob2 = encodeForPool(tx2)
|
||||||
|
|
@ -1222,9 +1196,9 @@ func TestBillyMigration(t *testing.T) {
|
||||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
||||||
|
|
||||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
|
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1)
|
||||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
|
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2)
|
||||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
|
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3)
|
||||||
|
|
||||||
blob1 = encodeForPool(tx1)
|
blob1 = encodeForPool(tx1)
|
||||||
blob2 = encodeForPool(tx2)
|
blob2 = encodeForPool(tx2)
|
||||||
|
|
@ -1318,7 +1292,7 @@ func TestLegacyTxConversion(t *testing.T) {
|
||||||
// Initialize the pending store with two blob transactions encoded in the
|
// Initialize the pending store with two blob transactions encoded in the
|
||||||
// legacy format.
|
// legacy format.
|
||||||
queuedir := filepath.Join(storage, pendingTransactionStore)
|
queuedir := filepath.Join(storage, pendingTransactionStore)
|
||||||
store, err := billy.Open(billy.Options{Path: queuedir}, newSlotter(testMaxBlobsPerBlock), nil)
|
store, err := billy.Open(billy.Options{Path: queuedir}, newSlotterEIP7594(testMaxBlobsPerBlock), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open billy: %v", err)
|
t.Fatalf("failed to open billy: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1328,8 +1302,8 @@ func TestLegacyTxConversion(t *testing.T) {
|
||||||
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
|
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
addr2 := crypto.PubkeyToAddress(key2.PublicKey)
|
addr2 := crypto.PubkeyToAddress(key2.PublicKey)
|
||||||
|
|
||||||
tx1 := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key1, types.BlobSidecarVersion0)
|
tx1 := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key1)
|
||||||
tx2 := makeMultiBlobTx(0, 1, 1000, 100, 2, 2, key2, types.BlobSidecarVersion0)
|
tx2 := makeMultiBlobTx(0, 1, 1000, 100, 2, 2, key2)
|
||||||
|
|
||||||
for _, tx := range []*types.Transaction{tx1, tx2} {
|
for _, tx := range []*types.Transaction{tx1, tx2} {
|
||||||
legacy, err := rlp.EncodeToBytes(tx)
|
legacy, err := rlp.EncodeToBytes(tx)
|
||||||
|
|
@ -1427,8 +1401,8 @@ func TestBlobCountLimit(t *testing.T) {
|
||||||
|
|
||||||
// Attempt to add transactions.
|
// Attempt to add transactions.
|
||||||
var (
|
var (
|
||||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
|
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1)
|
||||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 7, 0, key2, types.BlobSidecarVersion0)
|
tx2 = makeMultiBlobTx(0, 1, 800, 70, 7, 0, key2)
|
||||||
)
|
)
|
||||||
errs := pool.Add([]*types.Transaction{tx1, tx2}, true)
|
errs := pool.Add([]*types.Transaction{tx1, tx2}, true)
|
||||||
|
|
||||||
|
|
@ -1830,7 +1804,7 @@ func TestAdd(t *testing.T) {
|
||||||
storage := filepath.Join(t.TempDir(), fmt.Sprintf("test-%d", i))
|
storage := filepath.Join(t.TempDir(), fmt.Sprintf("test-%d", i))
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(testMaxBlobsPerBlock), nil)
|
||||||
|
|
||||||
// Insert the seed transactions for the pool startup
|
// Insert the seed transactions for the pool startup
|
||||||
var (
|
var (
|
||||||
|
|
@ -1941,7 +1915,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
storage := t.TempDir()
|
storage := t.TempDir()
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(params.BlobTxMaxBlobs), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(params.BlobTxMaxBlobs), nil)
|
||||||
|
|
||||||
// Create transactions from a few accounts.
|
// Create transactions from a few accounts.
|
||||||
var (
|
var (
|
||||||
|
|
@ -1953,9 +1927,9 @@ func TestGetBlobs(t *testing.T) {
|
||||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
||||||
|
|
||||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0) // [0, 6)
|
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1) // [0, 6)
|
||||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion1) // [6, 12)
|
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2) // [6, 12)
|
||||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3, types.BlobSidecarVersion0) // [12, 18)
|
tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3) // [12, 18)
|
||||||
|
|
||||||
blob1 = encodeForPool(tx1)
|
blob1 = encodeForPool(tx1)
|
||||||
blob2 = encodeForPool(tx2)
|
blob2 = encodeForPool(tx2)
|
||||||
|
|
@ -2023,7 +1997,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
start: 0, limit: 6,
|
start: 0, limit: 6,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 6,
|
start: 0, limit: 6,
|
||||||
|
|
@ -2031,7 +2005,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 6, fillRandom: true,
|
start: 0, limit: 6, fillRandom: true,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 6, fillRandom: true,
|
start: 0, limit: 6, fillRandom: true,
|
||||||
|
|
@ -2039,7 +2013,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 9,
|
start: 3, limit: 9,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 9,
|
start: 3, limit: 9,
|
||||||
|
|
@ -2047,7 +2021,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 9, fillRandom: true,
|
start: 3, limit: 9, fillRandom: true,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 9, fillRandom: true,
|
start: 3, limit: 9, fillRandom: true,
|
||||||
|
|
@ -2055,7 +2029,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 15,
|
start: 3, limit: 15,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 15,
|
start: 3, limit: 15,
|
||||||
|
|
@ -2063,7 +2037,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 15, fillRandom: true,
|
start: 3, limit: 15, fillRandom: true,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 3, limit: 15, fillRandom: true,
|
start: 3, limit: 15, fillRandom: true,
|
||||||
|
|
@ -2071,7 +2045,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 18,
|
start: 0, limit: 18,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 18,
|
start: 0, limit: 18,
|
||||||
|
|
@ -2079,7 +2053,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 18, fillRandom: true,
|
start: 0, limit: 18, fillRandom: true,
|
||||||
version: types.BlobSidecarVersion0,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: 0, limit: 18, fillRandom: true,
|
start: 0, limit: 18, fillRandom: true,
|
||||||
|
|
@ -2133,7 +2107,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
if blobs[j] == nil || proofs[j] == nil {
|
if blobs[j] == nil || proofs[j] == nil {
|
||||||
// This is only an error if there was no version mismatch
|
// This is only an error if there was no version mismatch
|
||||||
if (c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
|
if (c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
|
||||||
(c.version == types.BlobSidecarVersion0 && (testBlobIndex < 6 || 12 <= testBlobIndex)) {
|
(c.version == types.BlobSidecarVersion1 && (testBlobIndex < 6 || 12 <= testBlobIndex)) {
|
||||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
|
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
@ -2144,15 +2118,9 @@ func TestGetBlobs(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Item retrieved, make sure the proof matches the expectation
|
// Item retrieved, make sure the proof matches the expectation
|
||||||
if c.version == types.BlobSidecarVersion0 {
|
want, _ := kzg4844.ComputeCellProofs(blobs[j])
|
||||||
if proofs[j][0] != testBlobProofs[testBlobIndex] {
|
if !reflect.DeepEqual(want, proofs[j]) {
|
||||||
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
|
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
|
||||||
}
|
|
||||||
} else {
|
|
||||||
want, _ := kzg4844.ComputeCellProofs(blobs[j])
|
|
||||||
if !reflect.DeepEqual(want, proofs[j]) {
|
|
||||||
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2165,25 +2133,22 @@ func TestGetBlobs(t *testing.T) {
|
||||||
// - eth/72: blob payload omitted, output matches removeBlobs(tx)
|
// - eth/72: blob payload omitted, output matches removeBlobs(tx)
|
||||||
func TestEncodeForNetwork(t *testing.T) {
|
func TestEncodeForNetwork(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
sidecarVer byte
|
ethVer uint
|
||||||
ethVer uint
|
|
||||||
}{
|
}{
|
||||||
{"v0/eth70", types.BlobSidecarVersion0, 70},
|
{"eth70", 70},
|
||||||
{"v1/eth70", types.BlobSidecarVersion1, 70},
|
{"eth72", 72},
|
||||||
{"v0/eth72", types.BlobSidecarVersion0, 72},
|
|
||||||
{"v1/eth72", types.BlobSidecarVersion1, 72},
|
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testEncodeForNetwork(t, tc.sidecarVer, tc.ethVer)
|
testEncodeForNetwork(t, tc.ethVer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEncodeForNetwork(t *testing.T, sidecarVer byte, ethVer uint) {
|
func testEncodeForNetwork(t *testing.T, ethVer uint) {
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
tx := makeMultiBlobTx(0, 1, 1, 1, 1, 0, key, sidecarVer)
|
tx := makeMultiBlobTx(0, 1, 1, 1, 1, 0, key)
|
||||||
|
|
||||||
wantTx := tx
|
wantTx := tx
|
||||||
if ethVer >= 72 {
|
if ethVer >= 72 {
|
||||||
|
|
@ -2293,14 +2258,14 @@ func TestGetCells(t *testing.T) {
|
||||||
storage := t.TempDir()
|
storage := t.TempDir()
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(params.BlobTxMaxBlobs), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(params.BlobTxMaxBlobs), nil)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
key1, _ = crypto.GenerateKey()
|
key1, _ = crypto.GenerateKey()
|
||||||
|
|
||||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
|
||||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 3, 0, key1, types.BlobSidecarVersion1) // blobs [0, 3)
|
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 3, 0, key1) // blobs [0, 3)
|
||||||
|
|
||||||
pooledTx1, _ = newBlobTxForPool(tx1)
|
pooledTx1, _ = newBlobTxForPool(tx1)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
// (simulating what ETH/72 peers send).
|
// (simulating what ETH/72 peers send).
|
||||||
func makeV1Tx(t *testing.T, nonce uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey) *types.Transaction {
|
func makeV1Tx(t *testing.T, nonce uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
tx := makeMultiBlobTx(nonce, 1, 1, 1, blobCount, blobOffset, key, types.BlobSidecarVersion1)
|
tx := makeMultiBlobTx(nonce, 1, 1, 1, blobCount, blobOffset, key)
|
||||||
return removeBlobs(tx)
|
return removeBlobs(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func newTestCache(t *testing.T, txConfig []txSpec) *testCache {
|
||||||
if err := os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700); err != nil {
|
if err := os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700); err != nil {
|
||||||
t.Fatalf("mkdir: %v", err)
|
t.Fatalf("mkdir: %v", err)
|
||||||
}
|
}
|
||||||
store, err := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(params.BlobTxMaxBlobs), nil)
|
store, err := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(params.BlobTxMaxBlobs), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("billy open: %v", err)
|
t.Fatalf("billy open: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +70,7 @@ func newTestCache(t *testing.T, txConfig []txSpec) *testCache {
|
||||||
)
|
)
|
||||||
for _, s := range txConfig {
|
for _, s := range txConfig {
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
tx := makeMultiBlobTx(0, s.tip, 1_000_000, 1_000_000, s.blobs, offset, key, types.BlobSidecarVersion1)
|
tx := makeMultiBlobTx(0, s.tip, 1_000_000, 1_000_000, s.blobs, offset, key)
|
||||||
if _, err := store.Put(encodeForPool(tx)); err != nil {
|
if _, err := store.Put(encodeForPool(tx)); err != nil {
|
||||||
t.Fatalf("store put: %v", err)
|
t.Fatalf("store put: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +143,7 @@ func newTestCache(t *testing.T, txConfig []txSpec) *testCache {
|
||||||
func (tc *testCache) inject(t *testing.T, spec txSpec) []common.Hash {
|
func (tc *testCache) inject(t *testing.T, spec txSpec) []common.Hash {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
tx := makeMultiBlobTx(0, spec.tip, 1_000_000, 1_000_000, spec.blobs, tc.offset, key, types.BlobSidecarVersion1)
|
tx := makeMultiBlobTx(0, spec.tip, 1_000_000, 1_000_000, spec.blobs, tc.offset, key)
|
||||||
tc.offset += spec.blobs
|
tc.offset += spec.blobs
|
||||||
|
|
||||||
ptx, err := newBlobTxForPool(tx)
|
ptx, err := newBlobTxForPool(tx)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func newLimbo(config *params.ChainConfig, datadir string) (*limbo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new slotter for pre-Osaka blob configuration.
|
// Create new slotter for pre-Osaka blob configuration.
|
||||||
slotter := newSlotter(params.BlobTxMaxBlobs)
|
slotter := newSlotterEIP7594(params.BlobTxMaxBlobs)
|
||||||
|
|
||||||
// See if we need to migrate the limbo after fusaka.
|
// See if we need to migrate the limbo after fusaka.
|
||||||
slotter, err := tryMigrate(config, slotter, datadir)
|
slotter, err := tryMigrate(config, slotter, datadir)
|
||||||
|
|
|
||||||
|
|
@ -58,27 +58,6 @@ func tryMigrate(config *params.ChainConfig, slotter billy.SlotSizeFn, datadir st
|
||||||
return slotter, nil
|
return slotter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSlotter creates a helper method for the Billy datastore that returns the
|
|
||||||
// individual shelf sizes used to store transactions in.
|
|
||||||
//
|
|
||||||
// The slotter will create shelves for each possible blob count + some tx metadata
|
|
||||||
// wiggle room, up to the max permitted limits.
|
|
||||||
//
|
|
||||||
// The slotter also creates a shelf for 0-blob transactions. Whilst those are not
|
|
||||||
// allowed in the current protocol, having an empty shelf is not a relevant use
|
|
||||||
// of resources, but it makes stress testing with junk transactions simpler.
|
|
||||||
func newSlotter(maxBlobsPerTransaction int) billy.SlotSizeFn {
|
|
||||||
slotsize := uint32(txAvgSize)
|
|
||||||
slotsize -= uint32(blobSize) // underflows, it's ok, will overflow back in the first return
|
|
||||||
|
|
||||||
return func() (size uint32, done bool) {
|
|
||||||
slotsize += blobSize
|
|
||||||
finished := slotsize > uint32(maxBlobsPerTransaction)*blobSize+txMaxSize
|
|
||||||
|
|
||||||
return slotsize, finished
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSlotterEIP7594 creates a different slotter for EIP-7594 transactions.
|
// newSlotterEIP7594 creates a different slotter for EIP-7594 transactions.
|
||||||
// EIP-7594 (PeerDAS) changes the average transaction size which means the current
|
// EIP-7594 (PeerDAS) changes the average transaction size which means the current
|
||||||
// static 4KB average size is not enough anymore.
|
// static 4KB average size is not enough anymore.
|
||||||
|
|
|
||||||
|
|
@ -20,47 +20,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests that the slotter creates the expected database shelves.
|
|
||||||
func TestNewSlotter(t *testing.T) {
|
|
||||||
// Generate the database shelve sizes
|
|
||||||
slotter := newSlotter(6)
|
|
||||||
|
|
||||||
var shelves []uint32
|
|
||||||
for {
|
|
||||||
shelf, done := slotter()
|
|
||||||
shelves = append(shelves, shelf)
|
|
||||||
if done {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Compare the database shelves to the expected ones
|
|
||||||
want := []uint32{
|
|
||||||
0*blobSize + txAvgSize, // 0 blob + some expected tx infos
|
|
||||||
1*blobSize + txAvgSize, // 1 blob + some expected tx infos
|
|
||||||
2*blobSize + txAvgSize, // 2 blob + some expected tx infos (could be fewer blobs and more tx data)
|
|
||||||
3*blobSize + txAvgSize, // 3 blob + some expected tx infos (could be fewer blobs and more tx data)
|
|
||||||
4*blobSize + txAvgSize, // 4 blob + some expected tx infos (could be fewer blobs and more tx data)
|
|
||||||
5*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
6*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
7*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
8*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
9*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
10*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
11*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
12*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
13*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
|
|
||||||
14*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos >= 4 blobs + max tx metadata size
|
|
||||||
}
|
|
||||||
if len(shelves) != len(want) {
|
|
||||||
t.Errorf("shelves count mismatch: have %d, want %d", len(shelves), len(want))
|
|
||||||
}
|
|
||||||
for i := 0; i < len(shelves) && i < len(want); i++ {
|
|
||||||
if shelves[i] != want[i] {
|
|
||||||
t.Errorf("shelf %d mismatch: have %d, want %d", i, shelves[i], want[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that the slotter creates the expected database shelves.
|
// Tests that the slotter creates the expected database shelves.
|
||||||
func TestNewSlotterEIP7594(t *testing.T) {
|
func TestNewSlotterEIP7594(t *testing.T) {
|
||||||
// Generate the database shelve sizes
|
// Generate the database shelve sizes
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
}
|
}
|
||||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||||
// the transaction metadata
|
// the transaction metadata
|
||||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -135,22 +135,29 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Make sure the transaction has sufficient gas allowance to
|
||||||
|
// pay the floor cost.
|
||||||
if tx.Gas() < floorDataGas {
|
if tx.Gas() < floorDataGas {
|
||||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
|
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
|
||||||
}
|
}
|
||||||
|
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||||
|
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||||
|
if rules.IsAmsterdam && max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas {
|
||||||
|
return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
||||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
||||||
}
|
}
|
||||||
|
if tx.Type() == types.BlobTxType {
|
||||||
|
return validateBlobSidecar(tx, head, opts)
|
||||||
|
}
|
||||||
if tx.Type() == types.SetCodeTxType {
|
if tx.Type() == types.SetCodeTxType {
|
||||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||||
return errors.New("set code tx must have at least one authorization tuple")
|
return errors.New("set code tx must have at least one authorization tuple")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tx.Type() == types.BlobTxType {
|
|
||||||
return validateBlobSidecar(tx, head, opts)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,25 +179,11 @@ func validateBlobSidecar(tx *types.Transaction, head *types.Header, opts *Valida
|
||||||
if len(hashes) > opts.MaxBlobCount {
|
if len(hashes) > opts.MaxBlobCount {
|
||||||
return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, len(hashes), opts.MaxBlobCount)
|
return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, len(hashes), opts.MaxBlobCount)
|
||||||
}
|
}
|
||||||
|
if sidecar.Version != types.BlobSidecarVersion1 {
|
||||||
expected := types.BlobSidecarVersion0
|
return fmt.Errorf("%w: unexpected sidecar version, want: %d, got: %d", ErrSidecarFormatError, types.BlobSidecarVersion1, sidecar.Version)
|
||||||
if opts.Config.IsOsaka(head.Number, head.Time) {
|
|
||||||
expected = types.BlobSidecarVersion1
|
|
||||||
}
|
}
|
||||||
if sidecar.Version != expected {
|
if len(sidecar.Proofs) != len(sidecar.Commitments)*kzg4844.CellProofsPerBlob {
|
||||||
return fmt.Errorf("%w: unexpected sidecar version, want: %d, got: %d", ErrSidecarFormatError, expected, sidecar.Version)
|
return fmt.Errorf("%w: invalid number of %d blob proofs expected %d", ErrSidecarFormatError, len(sidecar.Proofs), len(sidecar.Commitments)*kzg4844.CellProofsPerBlob)
|
||||||
}
|
|
||||||
|
|
||||||
//todo: can we drop the support for v0 blob txs in blobpool?
|
|
||||||
switch sidecar.Version {
|
|
||||||
case types.BlobSidecarVersion0:
|
|
||||||
if len(sidecar.Proofs) != len(sidecar.Commitments) {
|
|
||||||
return fmt.Errorf("%w: invalid number of %d blob proofs expected %d", ErrSidecarFormatError, len(sidecar.Proofs), len(sidecar.Commitments))
|
|
||||||
}
|
|
||||||
case types.BlobSidecarVersion1:
|
|
||||||
if len(sidecar.Proofs) != len(sidecar.Commitments)*kzg4844.CellProofsPerBlob {
|
|
||||||
return fmt.Errorf("%w: invalid number of %d blob proofs expected %d", ErrSidecarFormatError, len(sidecar.Proofs), len(sidecar.Commitments)*kzg4844.CellProofsPerBlob)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -210,24 +203,10 @@ func ValidateCells(sidecar *types.BlobTxCellSidecar) error {
|
||||||
if blobCount != len(sidecar.Commitments) {
|
if blobCount != len(sidecar.Commitments) {
|
||||||
return fmt.Errorf("invalid number of %d blobs compared to %d commitments", blobCount, len(sidecar.Commitments))
|
return fmt.Errorf("invalid number of %d blobs compared to %d commitments", blobCount, len(sidecar.Commitments))
|
||||||
}
|
}
|
||||||
// Fork-specific sidecar checks, including proof verification.
|
if sidecar.Version != types.BlobSidecarVersion1 {
|
||||||
if sidecar.Version == types.BlobSidecarVersion1 {
|
return fmt.Errorf("unexpected sidecar version, want: %d, got: %d", types.BlobSidecarVersion1, sidecar.Version)
|
||||||
return validateCellsOsaka(sidecar)
|
|
||||||
}
|
}
|
||||||
return validateCellsLegacy(sidecar)
|
return validateCellsOsaka(sidecar)
|
||||||
}
|
|
||||||
|
|
||||||
func validateCellsLegacy(sidecar *types.BlobTxCellSidecar) error {
|
|
||||||
blobs, err := kzg4844.RecoverBlobs(sidecar.Cells, sidecar.Custody.Indices())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %v", ErrKZGVerificationError, err)
|
|
||||||
}
|
|
||||||
for i := range blobs {
|
|
||||||
if err := kzg4844.VerifyBlobProof(&blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
|
|
||||||
return fmt.Errorf("%w: invalid blob %d: %v", ErrKZGVerificationError, i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCellsOsaka(sidecar *types.BlobTxCellSidecar) error {
|
func validateCellsOsaka(sidecar *types.BlobTxCellSidecar) error {
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,5 @@ func toWordSize(size uint64) uint64 {
|
||||||
if size > math.MaxUint64-31 {
|
if size > math.MaxUint64-31 {
|
||||||
return math.MaxUint64/32 + 1
|
return math.MaxUint64/32 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return (size + 31) / 32
|
return (size + 31) / 32
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ type Contract struct {
|
||||||
IsDeployment bool
|
IsDeployment bool
|
||||||
IsSystemCall bool
|
IsSystemCall bool
|
||||||
|
|
||||||
|
// Gas carries the unified gas state for this frame: running balance,
|
||||||
|
// reservoir, and per-frame usage accumulators. See GasBudget.
|
||||||
Gas GasBudget
|
Gas GasBudget
|
||||||
value *uint256.Int
|
value *uint256.Int
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +115,6 @@ func (c *Contract) GetOp(n uint64) OpCode {
|
||||||
if n < uint64(len(c.Code)) {
|
if n < uint64(len(c.Code)) {
|
||||||
return OpCode(c.Code[n])
|
return OpCode(c.Code[n])
|
||||||
}
|
}
|
||||||
|
|
||||||
return STOP
|
return STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,9 +126,10 @@ func (c *Contract) Caller() common.Address {
|
||||||
return c.caller
|
return c.caller
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseGas attempts the use gas and subtracts it and returns true on success
|
// chargeRegular deducts regular gas only, with tracer integration.
|
||||||
func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) {
|
// Returns false on OOG. Delegates the arithmetic to GasBudget.ChargeRegular.
|
||||||
prior, ok := c.Gas.Charge(cost)
|
func (c *Contract) chargeRegular(r uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) bool {
|
||||||
|
prior, ok := c.Gas.ChargeRegular(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -137,15 +139,44 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefundGas refunds the leftover gas budget back to the contract.
|
// chargeState deducts state gas (spilling into regular when the reservoir is
|
||||||
func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) {
|
// exhausted), with tracer integration. Returns false on OOG.
|
||||||
prior, changed := c.Gas.Refund(refund)
|
func (c *Contract) chargeState(s uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) bool {
|
||||||
if !changed {
|
prior, ok := c.Gas.ChargeState(s)
|
||||||
return
|
if !ok {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||||
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// refundGas absorbs a sub-call's leftover GasBudget into this contract's gas state.
|
||||||
|
func (c *Contract) refundGas(child GasBudget, forwarded uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
|
||||||
|
prior := c.Gas
|
||||||
|
c.Gas.Absorb(child, forwarded)
|
||||||
|
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||||
|
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forwardGas drains `regular` regular gas and the entire state reservoir
|
||||||
|
// from this contract's running budget and returns the initial GasBudget for
|
||||||
|
// a child frame. The caller's UsedRegularGas is bumped by the forwarded
|
||||||
|
// amount so that the absorb-on-return path correctly reclaims the unused
|
||||||
|
// portion. Thin wrapper around GasBudget.Forward with tracer integration.
|
||||||
|
//
|
||||||
|
// Caller must ensure `regular` is no larger than the running balance (the
|
||||||
|
// opcode's dynamic gas table is expected to validate that before invoking
|
||||||
|
// the opcode handler).
|
||||||
|
func (c *Contract) forwardGas(regular uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) GasBudget {
|
||||||
|
prior := c.Gas
|
||||||
|
child := c.Gas.Forward(regular)
|
||||||
|
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||||
|
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||||
|
}
|
||||||
|
return child
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns the contracts address
|
// Address returns the contracts address
|
||||||
|
|
|
||||||
|
|
@ -264,9 +264,8 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
|
||||||
// - any error that occurred
|
// - any error that occurred
|
||||||
func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks, rules params.Rules) (ret []byte, remaining GasBudget, err error) {
|
func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks, rules params.Rules) (ret []byte, remaining GasBudget, err error) {
|
||||||
gasCost := p.RequiredGas(input)
|
gasCost := p.RequiredGas(input)
|
||||||
prior, ok := gas.Charge(GasCosts{RegularGas: gasCost})
|
prior, ok := gas.ChargeRegular(gasCost)
|
||||||
if !ok {
|
if !ok {
|
||||||
gas.Exhaust()
|
|
||||||
return nil, gas, ErrOutOfGas
|
return nil, gas, ErrOutOfGas
|
||||||
}
|
}
|
||||||
if logger.HasGasHook() {
|
if logger.HasGasHook() {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inWant := string(input)
|
inWant := string(input)
|
||||||
RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas), nil, params.Rules{})
|
RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas, 0), nil, params.Rules{})
|
||||||
if inHave := string(input); inWant != inHave {
|
if inHave := string(input); inWant != inHave {
|
||||||
t.Errorf("Precompiled %v modified input data", a)
|
t.Errorf("Precompiled %v modified input data", a)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
||||||
in := common.Hex2Bytes(test.Input)
|
in := common.Hex2Bytes(test.Input)
|
||||||
gas := p.RequiredGas(in)
|
gas := p.RequiredGas(in)
|
||||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||||
if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}); err != nil {
|
if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{}); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else if common.Bytes2Hex(res) != test.Expected {
|
} else if common.Bytes2Hex(res) != test.Expected {
|
||||||
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
||||||
|
|
@ -122,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
||||||
gas := test.Gas - 1
|
gas := test.Gas - 1
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||||
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{})
|
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
|
||||||
if err.Error() != "out of gas" {
|
if err.Error() != "out of gas" {
|
||||||
t.Errorf("Expected error [out of gas], got [%v]", err)
|
t.Errorf("Expected error [out of gas], got [%v]", err)
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
|
||||||
in := common.Hex2Bytes(test.Input)
|
in := common.Hex2Bytes(test.Input)
|
||||||
gas := p.RequiredGas(in)
|
gas := p.RequiredGas(in)
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{})
|
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
|
||||||
if err.Error() != test.ExpectedError {
|
if err.Error() != test.ExpectedError {
|
||||||
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for bench.Loop() {
|
for bench.Loop() {
|
||||||
copy(data, in)
|
copy(data, in)
|
||||||
res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas), nil, params.Rules{})
|
res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas, 0), nil, params.Rules{})
|
||||||
}
|
}
|
||||||
elapsed := uint64(time.Since(start))
|
elapsed := uint64(time.Since(start))
|
||||||
if elapsed < 1 {
|
if elapsed < 1 {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ var activators = map[int]func(*JumpTable){
|
||||||
7939: enable7939,
|
7939: enable7939,
|
||||||
8024: enable8024,
|
8024: enable8024,
|
||||||
7843: enable7843,
|
7843: enable7843,
|
||||||
|
8037: enable8037,
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableEIP enables the given EIP on the config.
|
// EnableEIP enables the given EIP on the config.
|
||||||
|
|
@ -211,8 +212,7 @@ func opTstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.readOnly {
|
if evm.readOnly {
|
||||||
return nil, ErrWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
loc := scope.Stack.pop()
|
loc, val := scope.Stack.pop2()
|
||||||
val := scope.Stack.pop()
|
|
||||||
evm.StateDB.SetTransientState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32())
|
evm.StateDB.SetTransientState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -262,11 +262,7 @@ func enable5656(jt *JumpTable) {
|
||||||
|
|
||||||
// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656)
|
// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656)
|
||||||
func opMcopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opMcopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
dst, src, length := scope.Stack.pop3()
|
||||||
dst = scope.Stack.pop()
|
|
||||||
src = scope.Stack.pop()
|
|
||||||
length = scope.Stack.pop()
|
|
||||||
)
|
|
||||||
// These values are checked for overflow during memory expansion calculation
|
// These values are checked for overflow during memory expansion calculation
|
||||||
// (the memorySize function on the opcode).
|
// (the memorySize function on the opcode).
|
||||||
scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64())
|
scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64())
|
||||||
|
|
@ -363,11 +359,8 @@ func enable8024(jt *JumpTable) {
|
||||||
|
|
||||||
func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
stack = scope.Stack
|
stack = scope.Stack
|
||||||
a = stack.pop()
|
a, memOffset, codeOffset, length = stack.pop4()
|
||||||
memOffset = stack.pop()
|
|
||||||
codeOffset = stack.pop()
|
|
||||||
length = stack.pop()
|
|
||||||
)
|
)
|
||||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
if overflow {
|
if overflow {
|
||||||
|
|
@ -377,7 +370,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er
|
||||||
code := evm.StateDB.GetCode(addr)
|
code := evm.StateDB.GetCode(addr)
|
||||||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
||||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas.RegularGas)
|
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas.RegularGas)
|
||||||
scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
scope.Contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||||
if consumed < wanted {
|
if consumed < wanted {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +396,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// advanced past this boundary.
|
// advanced past this boundary.
|
||||||
contractAddr := scope.Contract.Address()
|
contractAddr := scope.Contract.Address()
|
||||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
|
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
|
||||||
scope.Contract.UseGas(GasCosts{RegularGas: wanted}, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
scope.Contract.chargeRegular(wanted, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||||
if consumed < wanted {
|
if consumed < wanted {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
@ -430,7 +423,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
|
||||||
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
|
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
|
||||||
contractAddr := scope.Contract.Address()
|
contractAddr := scope.Contract.Address()
|
||||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
|
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
|
||||||
scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
scope.Contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||||
if consumed < wanted {
|
if consumed < wanted {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
@ -590,3 +583,11 @@ func enable7843(jt *JumpTable) {
|
||||||
maxStack: maxStack(0, 1),
|
maxStack: maxStack(0, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enable8037 enables the multidimensional-metering as specified in EIP-8037.
|
||||||
|
func enable8037(jt *JumpTable) {
|
||||||
|
jt[CREATE].constantGas = params.CreateGasAmsterdam
|
||||||
|
jt[CREATE2].constantGas = params.CreateGasAmsterdam
|
||||||
|
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037
|
||||||
|
jt[SSTORE].dynamicGas = gasSStore8037
|
||||||
|
}
|
||||||
|
|
|
||||||
255
core/vm/evm.go
255
core/vm/evm.go
|
|
@ -67,6 +67,8 @@ type BlockContext struct {
|
||||||
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
|
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
|
||||||
Random *common.Hash // Provides information for PREVRANDAO
|
Random *common.Hash // Provides information for PREVRANDAO
|
||||||
SlotNum uint64 // Provides information for SLOTNUM
|
SlotNum uint64 // Provides information for SLOTNUM
|
||||||
|
|
||||||
|
CostPerStateByte uint64 // CostPerByte for new state after EIP-8037
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxContext provides the EVM with information about a transaction.
|
// TxContext provides the EVM with information about a transaction.
|
||||||
|
|
@ -245,13 +247,13 @@ func isSystemCall(caller common.Address) bool {
|
||||||
// parameters. It also handles any necessary value transfer required and takse
|
// parameters. It also handles any necessary value transfer required and takse
|
||||||
// the necessary steps to create accounts and reverses the state in case of an
|
// the necessary steps to create accounts and reverses the state in case of an
|
||||||
// execution error or failed value transfer.
|
// execution error or failed value transfer.
|
||||||
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) {
|
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
|
||||||
// Capture the tracer start/end events in debug mode
|
// Capture the tracer start/end events in debug mode
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas.RegularGas, value.ToBig())
|
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig())
|
||||||
defer func(startGas uint64) {
|
defer func(startGas GasBudget) {
|
||||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||||
}(gas.RegularGas)
|
}(gas)
|
||||||
}
|
}
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
|
|
@ -263,7 +265,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
snapshot := evm.StateDB.Snapshot()
|
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||||
p, isPrecompile := evm.precompile(addr)
|
p, isPrecompile := evm.precompile(addr)
|
||||||
if !evm.StateDB.Exist(addr) {
|
if !evm.StateDB.Exist(addr) {
|
||||||
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
|
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
|
||||||
|
|
@ -275,10 +277,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
// hash leaf to the access list, then account creation will proceed unimpaired.
|
// hash leaf to the access list, then account creation will proceed unimpaired.
|
||||||
// Thus, only pay for the creation of the code hash leaf here.
|
// Thus, only pay for the creation of the code hash leaf here.
|
||||||
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
|
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
|
||||||
if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok {
|
if _, ok := gas.ChargeRegular(wgas); !ok {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
gas.Exhaust()
|
return nil, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||||
return nil, gas, ErrOutOfGas
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,6 +289,16 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
}
|
}
|
||||||
evm.StateDB.CreateAccount(addr)
|
evm.StateDB.CreateAccount(addr)
|
||||||
}
|
}
|
||||||
|
if evm.chainRules.IsAmsterdam && !value.IsZero() && evm.StateDB.Empty(addr) {
|
||||||
|
prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte)
|
||||||
|
if !ok {
|
||||||
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
return nil, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||||
|
}
|
||||||
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
|
evm.Config.Tracer.EmitGasChange(prev.AsTracing(), gas.AsTracing(), tracing.GasChangeAccountCreation)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Perform the value transfer only in non-syscall mode.
|
// Perform the value transfer only in non-syscall mode.
|
||||||
// Calling this is required even for zero-value transfers,
|
// Calling this is required even for zero-value transfers,
|
||||||
// to ensure the state clearing mechanism is applied.
|
// to ensure the state clearing mechanism is applied.
|
||||||
|
|
@ -311,22 +322,19 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally,
|
// Calculate the remaining gas at the end of frame
|
||||||
// when we're in homestead this also counts for code storage gas errors.
|
exitGas := gas.Exit(err, reservoir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
gas.Exhaust()
|
|
||||||
}
|
}
|
||||||
// TODO: consider clearing up unused snapshots:
|
|
||||||
//} else {
|
|
||||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, exitGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallCode executes the contract associated with the addr with the given input
|
// CallCode executes the contract associated with the addr with the given input
|
||||||
|
|
@ -336,26 +344,23 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
//
|
//
|
||||||
// CallCode differs from Call in the sense that it executes the given address'
|
// CallCode differs from Call in the sense that it executes the given address'
|
||||||
// code with the caller as context.
|
// code with the caller as context.
|
||||||
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) {
|
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
|
||||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas.RegularGas, value.ToBig())
|
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig())
|
||||||
defer func(startGas uint64) {
|
defer func(startGas GasBudget) {
|
||||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||||
}(gas.RegularGas)
|
}(gas)
|
||||||
}
|
}
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
// Fail if we're trying to transfer more than the available balance
|
// Fail if we're trying to transfer more than the available balance
|
||||||
// Note although it's noop to transfer X ether to caller itself. But
|
|
||||||
// if caller doesn't have enough balance, it would be an error to allow
|
|
||||||
// over-charging itself. So the check here is necessary.
|
|
||||||
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||||
|
|
||||||
// It is allowed to call precompiles, even via delegatecall
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
|
|
@ -368,16 +373,19 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
||||||
ret, err = evm.Run(contract, input, false)
|
ret, err = evm.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the remaining gas at the end of frame
|
||||||
|
exitGas := gas.Exit(err, reservoir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
gas.Exhaust()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, exitGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelegateCall executes the contract associated with the addr with the given input
|
// DelegateCall executes the contract associated with the addr with the given input
|
||||||
|
|
@ -385,56 +393,56 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
||||||
//
|
//
|
||||||
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
||||||
// code with the caller as context and the caller is set to the caller of the caller.
|
// code with the caller as context and the caller is set to the caller of the caller.
|
||||||
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) {
|
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
|
||||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
// DELEGATECALL inherits value from parent call
|
// DELEGATECALL inherits value from parent call
|
||||||
evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas.RegularGas, value.ToBig())
|
evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig())
|
||||||
defer func(startGas uint64) {
|
defer func(startGas GasBudget) {
|
||||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||||
}(gas.RegularGas)
|
}(gas)
|
||||||
}
|
}
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||||
|
|
||||||
// It is allowed to call precompiles, even via delegatecall
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
||||||
} else {
|
} else {
|
||||||
// Initialise a new contract and make initialise the delegate values
|
|
||||||
//
|
|
||||||
// Note: The value refers to the original value from the parent call.
|
|
||||||
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
|
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
|
||||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||||
ret, err = evm.Run(contract, input, false)
|
ret, err = evm.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the remaining gas at the end of frame
|
||||||
|
exitGas := gas.Exit(err, reservoir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
gas.Exhaust()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, exitGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticCall executes the contract associated with the addr with the given input
|
// StaticCall executes the contract associated with the addr with the given input
|
||||||
// as parameters while disallowing any modifications to the state during the call.
|
// as parameters while disallowing any modifications to the state during the call.
|
||||||
// Opcodes that attempt to perform such modifications will result in exceptions
|
// Opcodes that attempt to perform such modifications will result in exceptions
|
||||||
// instead of performing the modifications.
|
// instead of performing the modifications.
|
||||||
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, err error) {
|
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, result GasBudget, err error) {
|
||||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas.RegularGas, nil)
|
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil)
|
||||||
defer func(startGas uint64) {
|
defer func(startGas GasBudget) {
|
||||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||||
}(gas.RegularGas)
|
}(gas)
|
||||||
}
|
}
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
|
|
@ -445,7 +453,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
||||||
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
||||||
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
||||||
// We could change this, but for now it's left for legacy reasons
|
// We could change this, but for now it's left for legacy reasons
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||||
|
|
||||||
// We do an AddBalance of zero here, just in order to trigger a touch.
|
// We do an AddBalance of zero here, just in order to trigger a touch.
|
||||||
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
||||||
|
|
@ -456,58 +464,59 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
||||||
} else {
|
} else {
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
|
||||||
// The contract is a scoped environment for this execution context only.
|
|
||||||
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
|
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
|
||||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||||
|
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
|
||||||
// when we're in Homestead this also counts for code storage gas errors.
|
|
||||||
ret, err = evm.Run(contract, input, true)
|
ret, err = evm.Run(contract, input, true)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the remaining gas at the end of frame
|
||||||
|
exitGas := gas.Exit(err, reservoir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
gas.Exhaust()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, exitGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create creates a new contract using code as deployment code.
|
// create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasBudget, err error) {
|
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, err error) {
|
||||||
if evm.Config.Tracer != nil {
|
|
||||||
evm.captureBegin(evm.depth, typ, caller, address, code, gas.RegularGas, value.ToBig())
|
|
||||||
defer func(startGas uint64) {
|
|
||||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
|
||||||
}(gas.RegularGas)
|
|
||||||
}
|
|
||||||
// Depth check execution. Fail if we're trying to execute above the
|
// Depth check execution. Fail if we're trying to execute above the
|
||||||
// limit.
|
// limit.
|
||||||
|
var nonce uint64
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, common.Address{}, gas, ErrDepth
|
err = ErrDepth
|
||||||
|
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||||
|
err = ErrInsufficientBalance
|
||||||
|
} else {
|
||||||
|
nonce = evm.StateDB.GetNonce(caller)
|
||||||
|
if nonce+1 < nonce {
|
||||||
|
err = ErrNonceUintOverflow
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
if evm.Config.Tracer != nil {
|
||||||
return nil, common.Address{}, gas, ErrInsufficientBalance
|
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
||||||
|
defer func(startGas GasBudget) {
|
||||||
|
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||||
|
}(gas)
|
||||||
}
|
}
|
||||||
nonce := evm.StateDB.GetNonce(caller)
|
if err != nil {
|
||||||
if nonce+1 < nonce {
|
return nil, common.Address{}, gas, err
|
||||||
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
|
||||||
}
|
}
|
||||||
|
// Increment the caller's nonce after passing all validations
|
||||||
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
||||||
|
reservoir := gas.StateGas
|
||||||
|
|
||||||
// Charge the contract creation init gas in verkle mode
|
// Charge the contract creation init gas in verkle mode
|
||||||
if evm.chainRules.IsEIP4762 {
|
if evm.chainRules.IsEIP4762 {
|
||||||
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
|
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
|
||||||
prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas})
|
prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas})
|
||||||
if !ok {
|
if !ok {
|
||||||
gas.Exhaust()
|
return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||||
return nil, common.Address{}, gas, ErrOutOfGas
|
|
||||||
}
|
}
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
|
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
|
||||||
|
|
@ -528,11 +537,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
if evm.StateDB.GetNonce(address) != 0 ||
|
if evm.StateDB.GetNonce(address) != 0 ||
|
||||||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
||||||
isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) {
|
isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) {
|
||||||
|
halt := gas.ExitHalt(reservoir)
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), halt.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
gas.Exhaust()
|
// EIP-8037 collision rule: the state reservoir is fully preserved on
|
||||||
return nil, common.Address{}, gas, ErrContractAddressCollision
|
// address collision while regular gas is burnt.
|
||||||
|
return nil, common.Address{}, halt, ErrContractAddressCollision
|
||||||
}
|
}
|
||||||
// Create a new account on the state only if the object was not present.
|
// Create a new account on the state only if the object was not present.
|
||||||
// It might be possible the contract code is deployed to a pre-existent
|
// It might be possible the contract code is deployed to a pre-existent
|
||||||
|
|
@ -540,6 +551,18 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
snapshot := evm.StateDB.Snapshot()
|
snapshot := evm.StateDB.Snapshot()
|
||||||
if !evm.StateDB.Exist(address) {
|
if !evm.StateDB.Exist(address) {
|
||||||
evm.StateDB.CreateAccount(address)
|
evm.StateDB.CreateAccount(address)
|
||||||
|
|
||||||
|
if evm.chainRules.IsAmsterdam && evm.depth > 0 {
|
||||||
|
// Only charge state gas if we are not doing a create transaction.
|
||||||
|
// Prevents double charging with IntrinsicGas.
|
||||||
|
prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte)
|
||||||
|
if !ok {
|
||||||
|
return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||||
|
}
|
||||||
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
|
evm.Config.Tracer.EmitGasChange(prev.AsTracing(), gas.AsTracing(), tracing.GasChangeAccountCreation)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// CreateContract means that regardless of whether the account previously existed
|
// CreateContract means that regardless of whether the account previously existed
|
||||||
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
||||||
|
|
@ -554,8 +577,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
if evm.chainRules.IsEIP4762 {
|
if evm.chainRules.IsEIP4762 {
|
||||||
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
|
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
|
||||||
if consumed < wanted {
|
if consumed < wanted {
|
||||||
gas.Exhaust()
|
return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||||
return nil, common.Address{}, gas, ErrOutOfGas
|
|
||||||
}
|
}
|
||||||
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
|
|
@ -574,13 +596,23 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
contract.IsDeployment = true
|
contract.IsDeployment = true
|
||||||
|
|
||||||
ret, err = evm.initNewContract(contract, address)
|
ret, err = evm.initNewContract(contract, address)
|
||||||
|
|
||||||
|
// Special case: ErrCodeStoreOutOfGas pre-Homestead does NOT roll back
|
||||||
|
// state and gas is preserved (i.e., treated as success).
|
||||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
|
exit := contract.Gas.Exit(err, reservoir)
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
|
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return ret, address, exit, err
|
||||||
}
|
}
|
||||||
return ret, address, contract.Gas, err
|
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
|
||||||
|
// Both packaged as a success-form GasBudget.
|
||||||
|
return ret, address, contract.Gas.ExitSuccess(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// initNewContract runs a new contract's creation code, performs checks on the
|
// initNewContract runs a new contract's creation code, performs checks on the
|
||||||
|
|
@ -590,30 +622,45 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
// Check prefix before gas calculation.
|
||||||
// Check whether the max code size has been exceeded, assign err if the case.
|
|
||||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
||||||
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
|
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
|
||||||
return ret, ErrInvalidCode
|
return ret, ErrInvalidCode
|
||||||
}
|
}
|
||||||
|
if evm.chainRules.IsEIP4762 {
|
||||||
if !evm.chainRules.IsEIP4762 {
|
|
||||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
|
||||||
if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
|
||||||
return ret, ErrCodeStoreOutOfGas
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
|
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
|
||||||
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||||
if len(ret) > 0 && (consumed < wanted) {
|
if len(ret) > 0 && (consumed < wanted) {
|
||||||
return ret, ErrCodeStoreOutOfGas
|
return ret, ErrCodeStoreOutOfGas
|
||||||
}
|
}
|
||||||
|
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
} else if evm.chainRules.IsAmsterdam {
|
||||||
|
// Check max code size BEFORE charging gas so over-max code
|
||||||
|
// does not consume state gas (which would inflate tx_state).
|
||||||
|
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
// Charge regular gas (hash cost) before state gas.
|
||||||
|
regularCost := toWordSize(uint64(len(ret))) * params.Keccak256WordGas
|
||||||
|
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
|
return ret, ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
// Charge state gas (code-deposit) afterwards.
|
||||||
|
stateCost := uint64(len(ret)) * evm.Context.CostPerStateByte
|
||||||
|
if !contract.chargeState(stateCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
|
return ret, ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createDataCost := uint64(len(ret)) * params.CreateDataGas
|
||||||
|
if !contract.chargeRegular(createDataCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
|
return ret, ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ret) > 0 {
|
if len(ret) > 0 {
|
||||||
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
||||||
}
|
}
|
||||||
|
|
@ -621,7 +668,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new contract using code as deployment code.
|
// Create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) {
|
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
|
||||||
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
|
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
|
||||||
return evm.create(caller, code, gas, value, contractAddr, CREATE)
|
return evm.create(caller, code, gas, value, contractAddr, CREATE)
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +677,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
//
|
//
|
||||||
// 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 GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) {
|
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
|
||||||
inithash := crypto.Keccak256Hash(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)
|
||||||
|
|
@ -668,22 +715,20 @@ func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash {
|
||||||
// ChainConfig returns the environment's chain configuration
|
// ChainConfig returns the environment's chain configuration
|
||||||
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
||||||
|
|
||||||
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) {
|
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas GasBudget, value *big.Int) {
|
||||||
tracer := evm.Config.Tracer
|
tracer := evm.Config.Tracer
|
||||||
if tracer.OnEnter != nil {
|
if tracer.OnEnter != nil {
|
||||||
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value)
|
tracer.OnEnter(depth, byte(typ), from, to, input, startGas.RegularGas, value)
|
||||||
}
|
}
|
||||||
if tracer.HasGasHook() {
|
if tracer.HasGasHook() {
|
||||||
initial := NewGasBudget(startGas)
|
tracer.EmitGasChange(tracing.Gas{}, startGas.AsTracing(), tracing.GasChangeCallInitialBalance)
|
||||||
tracer.EmitGasChange(tracing.Gas{}, initial.AsTracing(), tracing.GasChangeCallInitialBalance)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) {
|
func (evm *EVM) captureEnd(depth int, startGas GasBudget, leftOverGas GasBudget, ret []byte, err error) {
|
||||||
tracer := evm.Config.Tracer
|
tracer := evm.Config.Tracer
|
||||||
if leftOverGas != 0 && tracer.HasGasHook() {
|
if !leftOverGas.IsZero() && tracer.HasGasHook() {
|
||||||
leftover := NewGasBudget(leftOverGas)
|
tracer.EmitGasChange(leftOverGas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
|
||||||
tracer.EmitGasChange(leftover.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
|
|
||||||
}
|
}
|
||||||
var reverted bool
|
var reverted bool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -693,7 +738,7 @@ func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret [
|
||||||
reverted = false
|
reverted = false
|
||||||
}
|
}
|
||||||
if tracer.OnExit != nil {
|
if tracer.OnExit != nil {
|
||||||
tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
|
tracer.OnExit(depth, ret, startGas.RegularGas-leftOverGas.RegularGas, VMErrorFromErr(err), reverted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -291,10 +291,19 @@ var (
|
||||||
gasMLoad = pureMemoryGascost
|
gasMLoad = pureMemoryGascost
|
||||||
gasMStore8 = pureMemoryGascost
|
gasMStore8 = pureMemoryGascost
|
||||||
gasMStore = pureMemoryGascost
|
gasMStore = pureMemoryGascost
|
||||||
gasCreate = pureMemoryGascost
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return GasCosts{}, ErrWriteProtection
|
||||||
|
}
|
||||||
|
return pureMemoryGascost(evm, contract, stack, mem, memorySize)
|
||||||
|
}
|
||||||
|
|
||||||
func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return GasCosts{}, ErrWriteProtection
|
||||||
|
}
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return GasCosts{}, err
|
||||||
|
|
@ -313,6 +322,9 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return GasCosts{}, ErrWriteProtection
|
||||||
|
}
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return GasCosts{}, err
|
||||||
|
|
@ -331,7 +343,11 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
||||||
}
|
}
|
||||||
return GasCosts{RegularGas: gas}, nil
|
return GasCosts{RegularGas: gas}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return GasCosts{}, ErrWriteProtection
|
||||||
|
}
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return GasCosts{}, err
|
||||||
|
|
@ -384,17 +400,17 @@ var (
|
||||||
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
|
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
func makeCallVariantGasCost(intrinsicFunc intrinsicGasFunc) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return GasCosts{}, err
|
||||||
}
|
}
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.back(0))
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic, stack.back(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return GasCosts{}, err
|
||||||
}
|
}
|
||||||
gas, overflow := math.SafeAdd(intrinsic.RegularGas, evm.callGasTemp)
|
gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp)
|
||||||
if overflow {
|
if overflow {
|
||||||
return GasCosts{}, ErrGasUintOverflow
|
return GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
@ -402,19 +418,19 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
gas uint64
|
gas uint64
|
||||||
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 {
|
if evm.readOnly && transfersValue {
|
||||||
return GasCosts{}, ErrWriteProtection
|
return 0, ErrWriteProtection
|
||||||
}
|
}
|
||||||
// Stateless check
|
// Stateless check
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return 0, err
|
||||||
}
|
}
|
||||||
var transferGas uint64
|
var transferGas uint64
|
||||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||||
|
|
@ -422,12 +438,12 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
||||||
}
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
||||||
return GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
// it can effectively prevent accessing the states in the following steps.
|
// it can effectively prevent accessing the states in the following steps.
|
||||||
if contract.Gas.RegularGas < gas {
|
if contract.Gas.RegularGas < gas {
|
||||||
return GasCosts{}, ErrOutOfGas
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
// Stateful check
|
// Stateful check
|
||||||
var stateGas uint64
|
var stateGas uint64
|
||||||
|
|
@ -439,15 +455,15 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
||||||
stateGas += params.CallNewAccountGas
|
stateGas += params.CallNewAccountGas
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
|
||||||
return GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return GasCosts{RegularGas: gas}, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return 0, err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
gas uint64
|
gas uint64
|
||||||
|
|
@ -457,38 +473,36 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
||||||
gas += params.CallValueTransferGas
|
gas += params.CallValueTransferGas
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return GasCosts{RegularGas: gas}, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return GasCosts{RegularGas: gas}, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return GasCosts{RegularGas: gas}, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
if evm.readOnly {
|
if evm.readOnly {
|
||||||
return GasCosts{}, ErrWriteProtection
|
return GasCosts{}, 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 {
|
||||||
gas = params.SelfdestructGasEIP150
|
gas = params.SelfdestructGasEIP150
|
||||||
var address = common.Address(stack.back(0).Bytes20())
|
var address = common.Address(stack.back(0).Bytes20())
|
||||||
|
|
||||||
if evm.chainRules.IsEIP158 {
|
if evm.chainRules.IsEIP158 {
|
||||||
// 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 {
|
||||||
|
|
@ -504,3 +518,104 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||||
}
|
}
|
||||||
return GasCosts{RegularGas: gas}, nil
|
return GasCosts{RegularGas: gas}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return GasCosts{}, ErrWriteProtection
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
gas GasCosts
|
||||||
|
address = common.Address(stack.peek().Bytes20())
|
||||||
|
)
|
||||||
|
if !evm.StateDB.AddressInAccessList(address) {
|
||||||
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
|
evm.StateDB.AddAddressToAccessList(address)
|
||||||
|
gas.RegularGas = params.ColdAccountAccessCostEIP2929
|
||||||
|
}
|
||||||
|
// Check we have enough regular gas before we add the address to the BAL
|
||||||
|
if contract.Gas.RegularGas < gas.RegularGas {
|
||||||
|
return gas, ErrOutOfGas
|
||||||
|
}
|
||||||
|
// Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist
|
||||||
|
// in the current state yet still be considered non-existent by EIP-161 if its
|
||||||
|
// nonce, balance, and code are all zero. Such accounts can appear temporarily
|
||||||
|
// during execution (e.g. via SELFDESTRUCT) and are removed at tx end.
|
||||||
|
//
|
||||||
|
// Funding such an account makes it permanent state growth and must be charged.
|
||||||
|
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||||
|
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
if evm.readOnly {
|
||||||
|
return GasCosts{}, ErrWriteProtection
|
||||||
|
}
|
||||||
|
// If we fail the minimum gas availability invariant, fail (0)
|
||||||
|
if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 {
|
||||||
|
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
||||||
|
}
|
||||||
|
// Gas sentry honoured, do the actual gas calculation based on the stored value
|
||||||
|
var (
|
||||||
|
y, x = stack.back(1), stack.peek()
|
||||||
|
slot = common.Hash(x.Bytes32())
|
||||||
|
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||||
|
cost GasCosts
|
||||||
|
)
|
||||||
|
// Check slot presence in the access list
|
||||||
|
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||||
|
cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929}
|
||||||
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
|
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||||
|
}
|
||||||
|
value := common.Hash(y.Bytes32())
|
||||||
|
|
||||||
|
if current == value { // noop (1)
|
||||||
|
// EIP 2200 original clause:
|
||||||
|
// return params.SloadGasEIP2200, nil
|
||||||
|
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
|
||||||
|
}
|
||||||
|
if original == current {
|
||||||
|
if original == (common.Hash{}) { // create slot (2.1.1)
|
||||||
|
return GasCosts{
|
||||||
|
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
|
||||||
|
StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if value == (common.Hash{}) { // delete slot (2.1.2b)
|
||||||
|
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||||
|
}
|
||||||
|
// EIP-2200 original clause:
|
||||||
|
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
|
||||||
|
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2)
|
||||||
|
}
|
||||||
|
if original != (common.Hash{}) {
|
||||||
|
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
|
||||||
|
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||||
|
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
|
||||||
|
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if original == value {
|
||||||
|
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
||||||
|
// EIP-8037 point (2): refund state gas directly to the reservoir
|
||||||
|
// at the SSTORE restoration point (0→x→0 in same tx); not to the
|
||||||
|
// refund counter, which is capped at gas_used/5.
|
||||||
|
contract.Gas.RefundState(params.StorageCreationSize * evm.Context.CostPerStateByte)
|
||||||
|
|
||||||
|
// Regular portion of the refund still goes through the refund counter.
|
||||||
|
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
|
||||||
|
} else { // reset to original existing slot (2.2.2.2)
|
||||||
|
// EIP 2200 Original clause:
|
||||||
|
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
|
||||||
|
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
|
||||||
|
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
|
||||||
|
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
|
||||||
|
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// EIP-2200 original clause:
|
||||||
|
//return params.SloadGasEIP2200, nil // dirty update (2.2)
|
||||||
|
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,12 +97,12 @@ func TestEIP2200(t *testing.T) {
|
||||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||||
}
|
}
|
||||||
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
||||||
initialGas := NewGasBudget(tt.gaspool)
|
initialGas := NewGasBudget(tt.gaspool, 0)
|
||||||
_, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
|
_, result, err := evm.Call(common.Address{}, address, nil, initialGas, new(uint256.Int))
|
||||||
if !errors.Is(err, tt.failure) {
|
if !errors.Is(err, tt.failure) {
|
||||||
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
|
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
|
||||||
}
|
}
|
||||||
if used := leftOver.Used(initialGas); used != tt.used {
|
if used := result.Used(initialGas); used != tt.used {
|
||||||
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used)
|
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used)
|
||||||
}
|
}
|
||||||
if refund := evm.StateDB.GetRefund(); refund != tt.refund {
|
if refund := evm.StateDB.GetRefund(); refund != tt.refund {
|
||||||
|
|
@ -157,12 +157,12 @@ func TestCreateGas(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
evm := NewEVM(vmctx, statedb, chainConfig, config)
|
evm := NewEVM(vmctx, statedb, chainConfig, config)
|
||||||
initialGas := NewGasBudget(uint64(testGas))
|
initialGas := NewGasBudget(uint64(testGas), 0)
|
||||||
ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
|
ret, result, err := evm.Call(common.Address{}, address, nil, initialGas, new(uint256.Int))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
gasUsed = leftOver.Used(initialGas)
|
gasUsed = result.Used(initialGas)
|
||||||
if len(ret) != 32 {
|
if len(ret) != 32 {
|
||||||
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
|
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GasCosts denotes a vector of gas costs in the
|
// GasCosts denotes a vector of gas costs in the multidimensional metering
|
||||||
// multidimensional metering paradigm. It represents the cost
|
// paradigm. It represents the cost charged by an individual operation.
|
||||||
// charged by an individual operation.
|
|
||||||
type GasCosts struct {
|
type GasCosts struct {
|
||||||
RegularGas uint64
|
RegularGas uint64
|
||||||
StateGas uint64
|
StateGas uint64
|
||||||
|
|
@ -40,67 +40,259 @@ func (g GasCosts) String() string {
|
||||||
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas)
|
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GasBudget denotes a vector of remaining gas allowances available
|
// GasBudget is the unified gas-state structure used throughout the EVM.
|
||||||
// for EVM execution in the multidimensional metering paradigm.
|
// It carries two pairs of fields:
|
||||||
// Unlike GasCosts which represents the price of an operation,
|
//
|
||||||
// GasBudget tracks how much gas is left to spend.
|
// - RegularGas / StateGas: the running balance during execution, or the
|
||||||
|
// leftover balance the caller must absorb after a sub-call.
|
||||||
|
// - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross
|
||||||
|
// consumption. UsedStateGas is signed so it can be decremented by inline
|
||||||
|
// state-gas refunds (e.g., SSTORE 0->A->0).
|
||||||
|
//
|
||||||
|
// The same struct serves three roles:
|
||||||
|
//
|
||||||
|
// - During execution: Charge / ChargeRegular / ChargeState / RefundState
|
||||||
|
// and RefundRegular mutate the running balance and the usage accumulators
|
||||||
|
// in lockstep.
|
||||||
|
//
|
||||||
|
// - At frame exit: ExitSuccess / ExitRevert / ExitHalt produce a new
|
||||||
|
// GasBudget in "leftover" form that packages the result for the caller.
|
||||||
|
//
|
||||||
|
// - At absorption: the caller's Absorb method merges the child's leftover
|
||||||
|
// budget into its own running budget.
|
||||||
type GasBudget struct {
|
type GasBudget struct {
|
||||||
RegularGas uint64 // The leftover gas for execution and state gas usage
|
RegularGas uint64 // remaining regular-gas balance (or leftover for caller to absorb)
|
||||||
StateGas uint64 // The state gas reservoir
|
StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
|
||||||
|
UsedRegularGas uint64 // gross regular gas consumed in this frame
|
||||||
|
UsedStateGas int64 // signed net state-gas consumed in this frame
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGasBudget creates a GasBudget with the given initial regular gas allowance.
|
// NewGasBudget initializes a fresh GasBudget for execution / forwarding,
|
||||||
func NewGasBudget(gas uint64) GasBudget {
|
// with both usage accumulators set to zero.
|
||||||
return GasBudget{RegularGas: gas}
|
func NewGasBudget(regular, state uint64) GasBudget {
|
||||||
|
return GasBudget{RegularGas: regular, StateGas: state}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used returns the amount of regular gas consumed so far.
|
// Used returns the total scalar gas consumed relative to an initial budget
|
||||||
|
// (= (initial.regular + initial.state) − (current.regular + current.state)).
|
||||||
|
// This is the payment scalar (EIP-8037's tx_gas_used_before_refund).
|
||||||
func (g GasBudget) Used(initial GasBudget) uint64 {
|
func (g GasBudget) Used(initial GasBudget) uint64 {
|
||||||
return initial.RegularGas - g.RegularGas
|
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exhaust sets all remaining gas to zero, preserving the initial amount
|
// String returns a visual representation of the budget.
|
||||||
// for usage tracking.
|
|
||||||
func (g *GasBudget) Exhaust() {
|
|
||||||
g.RegularGas = 0
|
|
||||||
g.StateGas = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GasBudget) Copy() GasBudget {
|
|
||||||
return GasBudget{RegularGas: g.RegularGas, StateGas: g.StateGas}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a visual representation of the gas budget vector.
|
|
||||||
func (g GasBudget) String() string {
|
func (g GasBudget) String() string {
|
||||||
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas)
|
return fmt.Sprintf("<%v,%v,used=<%v,%v>>", g.RegularGas, g.StateGas, g.UsedRegularGas, g.UsedStateGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanAfford reports whether the budget has sufficient gas to cover the cost.
|
// Charge deducts a combined regular+state cost from the running balance and
|
||||||
func (g GasBudget) CanAfford(cost GasCosts) bool {
|
// updates the usage accumulators. State-gas in excess of the reservoir spills
|
||||||
return g.RegularGas >= cost.RegularGas
|
// into regular_gas.
|
||||||
}
|
|
||||||
|
|
||||||
// Charge deducts the given gas cost from the budget. It returns the
|
|
||||||
// pre-charge budget and false if the budget does not have sufficient
|
|
||||||
// gas to cover the cost.
|
|
||||||
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
||||||
prior := *g
|
prior := *g
|
||||||
if g.RegularGas < cost.RegularGas {
|
ok := g.charge(cost)
|
||||||
return prior, false
|
return prior, ok
|
||||||
}
|
|
||||||
g.RegularGas -= cost.RegularGas
|
|
||||||
return prior, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refund adds the given gas budget back. It returns the pre-refund budget
|
// chargeRegularOnly deducts a regular-only cost.
|
||||||
// and whether the budget was actually changed.
|
func (g *GasBudget) chargeRegularOnly(r uint64) bool {
|
||||||
func (g *GasBudget) Refund(other GasBudget) (GasBudget, bool) {
|
if g.RegularGas < r {
|
||||||
prior := *g
|
return false
|
||||||
g.RegularGas += other.RegularGas
|
}
|
||||||
return prior, g.RegularGas != prior.RegularGas
|
g.RegularGas -= r
|
||||||
|
g.UsedRegularGas += r
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// charge deducts both the state and regular cost.
|
||||||
|
func (g *GasBudget) charge(cost GasCosts) bool {
|
||||||
|
if g.RegularGas < cost.RegularGas {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
regular := g.RegularGas - cost.RegularGas
|
||||||
|
state := g.StateGas
|
||||||
|
|
||||||
|
if cost.StateGas > state {
|
||||||
|
spillover := cost.StateGas - state
|
||||||
|
if spillover > regular {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
regular -= spillover
|
||||||
|
state = 0
|
||||||
|
} else {
|
||||||
|
state -= cost.StateGas
|
||||||
|
}
|
||||||
|
g.RegularGas = regular
|
||||||
|
g.StateGas = state
|
||||||
|
g.UsedRegularGas += cost.RegularGas
|
||||||
|
g.UsedStateGas += int64(cost.StateGas)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsTracing converts the GasBudget into the tracing-facing Gas vector.
|
// AsTracing converts the GasBudget into the tracing-facing Gas vector.
|
||||||
func (g GasBudget) AsTracing() tracing.Gas {
|
func (g GasBudget) AsTracing() tracing.Gas {
|
||||||
return tracing.Gas{Regular: g.RegularGas, State: g.StateGas}
|
return tracing.Gas{Regular: g.RegularGas, State: g.StateGas}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChargeRegular is a convenience that deducts a regular-only cost.
|
||||||
|
func (g *GasBudget) ChargeRegular(r uint64) (GasBudget, bool) {
|
||||||
|
return g.Charge(GasCosts{RegularGas: r})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeState is a convenience that deducts a state-only cost (spills to
|
||||||
|
// regular when the reservoir is exhausted). Returns false on OOG.
|
||||||
|
func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) {
|
||||||
|
return g.Charge(GasCosts{StateGas: s})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns an indicator if the gas budget has been exhausted.
|
||||||
|
func (g *GasBudget) IsZero() bool {
|
||||||
|
return g.RegularGas == 0 && g.StateGas == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0).
|
||||||
|
// The reservoir is credited and the signed usage counter is decremented
|
||||||
|
// in lockstep, preserving the per-frame invariant:
|
||||||
|
//
|
||||||
|
// StateGas + UsedStateGas == initialStateGas + spillover_so_far
|
||||||
|
//
|
||||||
|
// which the revert path relies on for the correct gross refund.
|
||||||
|
func (g *GasBudget) RefundState(s uint64) {
|
||||||
|
g.StateGas += s
|
||||||
|
g.UsedStateGas -= int64(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward drains `regular` regular gas and the entire state reservoir from
|
||||||
|
// the parent's running budget and returns the initial GasBudget for a child
|
||||||
|
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
|
||||||
|
// that the absorb-on-return path correctly reclaims the unused portion.
|
||||||
|
//
|
||||||
|
// Used by frame boundaries where the regular forward has NOT been pre-
|
||||||
|
// deducted: tx-level dispatch (state_transition) and CREATE / CREATE2. The
|
||||||
|
// CALL family pre-deducts the forward via the dynamic gas table for tracer-
|
||||||
|
// reporting reasons and therefore constructs its child budget directly.
|
||||||
|
//
|
||||||
|
// Caller must ensure `regular` does not exceed the running balance and
|
||||||
|
// apply any EIP-150 1/64 retention before calling Forward.
|
||||||
|
func (g *GasBudget) Forward(regular uint64) GasBudget {
|
||||||
|
g.RegularGas -= regular
|
||||||
|
g.UsedRegularGas += regular
|
||||||
|
|
||||||
|
child := GasBudget{
|
||||||
|
RegularGas: regular,
|
||||||
|
StateGas: g.StateGas,
|
||||||
|
}
|
||||||
|
g.StateGas = 0
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardAll forwards the parent's full remaining budget (both regular and
|
||||||
|
// state) to a child frame. Equivalent to Forward(g.RegularGas) — used at
|
||||||
|
// the tx boundary where there is no 1/64 retention.
|
||||||
|
func (g *GasBudget) ForwardAll() GasBudget {
|
||||||
|
return g.Forward(g.RegularGas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Exit-form constructors. These take a post-execution running budget and
|
||||||
|
// produce a new GasBudget in "leftover form", the value the caller should
|
||||||
|
// absorb to update its own state.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ExitSuccess produces the leftover form for a successful frame. Inline
|
||||||
|
// state-gas refunds have already been folded into StateGas / UsedStateGas
|
||||||
|
// during execution; the running budget IS the exit budget on success.
|
||||||
|
func (g GasBudget) ExitSuccess() GasBudget {
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitRevert produces the leftover for a REVERT exit. Per EIP-8037, all state
|
||||||
|
// gas charged by the reverted frame is refunded to the caller's reservoir:
|
||||||
|
//
|
||||||
|
// leftover.StateGas = StateGas + UsedStateGas
|
||||||
|
//
|
||||||
|
// UsedStateGas is reset since the frame's state changes are discarded.
|
||||||
|
func (g GasBudget) ExitRevert() GasBudget {
|
||||||
|
reservoir := int64(g.StateGas) + g.UsedStateGas
|
||||||
|
if reservoir < 0 {
|
||||||
|
// Reservoir should never be negative. By construction it equals
|
||||||
|
// the initial state-gas allocation plus any spillover to regular
|
||||||
|
// gas.
|
||||||
|
reservoir = 0
|
||||||
|
log.Warn("Negative reservoir at revert", "remaining", g.StateGas, "used", g.UsedStateGas)
|
||||||
|
}
|
||||||
|
return GasBudget{
|
||||||
|
RegularGas: g.RegularGas,
|
||||||
|
StateGas: uint64(reservoir),
|
||||||
|
UsedRegularGas: g.UsedRegularGas,
|
||||||
|
UsedStateGas: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitHalt produces the leftover for an exceptional halt.
|
||||||
|
//
|
||||||
|
// - state_gas_reservoir is reset back to its value at the start of the child frame
|
||||||
|
// - the gas_left initially given to the child is consumed (set to zero)
|
||||||
|
func (g GasBudget) ExitHalt(initStateReservoir uint64) GasBudget {
|
||||||
|
reservoir := int64(g.StateGas) + g.UsedStateGas
|
||||||
|
if reservoir < 0 {
|
||||||
|
// Reservoir should never be negative. By construction it equals
|
||||||
|
// the initial state-gas allocation plus any spillover to regular
|
||||||
|
// gas.
|
||||||
|
reservoir = 0
|
||||||
|
log.Warn("Negative reservoir at halt", "remaining", g.StateGas, "used", g.UsedStateGas)
|
||||||
|
}
|
||||||
|
// The portion of state gas charged from regular gas is also burned
|
||||||
|
// together with the regular gas, rather than being returned to the
|
||||||
|
// parent's state-gas reservoir.
|
||||||
|
var spilled uint64
|
||||||
|
if uint64(reservoir) > initStateReservoir {
|
||||||
|
spilled = uint64(reservoir) - initStateReservoir
|
||||||
|
}
|
||||||
|
return GasBudget{
|
||||||
|
RegularGas: 0,
|
||||||
|
StateGas: initStateReservoir,
|
||||||
|
UsedRegularGas: g.UsedRegularGas + g.RegularGas + spilled,
|
||||||
|
UsedStateGas: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit dispatches on err to the appropriate exit-form constructor
|
||||||
|
// for the post-evm.Run path:
|
||||||
|
//
|
||||||
|
// - err == nil → ExitSuccess
|
||||||
|
// - err == ErrExecutionReverted → ExitRevert
|
||||||
|
// - any other err → ExitHalt
|
||||||
|
//
|
||||||
|
// Soft validation failures (occurring BEFORE evm.Run) should call Preserved
|
||||||
|
// directly instead of going through this dispatcher.
|
||||||
|
func (g GasBudget) Exit(err error, initStateReservoir uint64) GasBudget {
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return g.ExitSuccess()
|
||||||
|
case err == ErrExecutionReverted:
|
||||||
|
return g.ExitRevert()
|
||||||
|
default:
|
||||||
|
return g.ExitHalt(initStateReservoir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absorb merges a sub-call's leftover GasBudget into this (caller's) running
|
||||||
|
// budget. Additionally, it does an EIP-8037 spillover correction:
|
||||||
|
// state-gas that spilled into the regular pool inside the child frame is
|
||||||
|
// excluded from the UsedRegularGas.
|
||||||
|
//
|
||||||
|
// spillover = forwarded - child.RegularGas - child.UsedRegularGas
|
||||||
|
//
|
||||||
|
// forwarded is the regular-gas amount that was passed to the child at call
|
||||||
|
// entry (i.e., the regular initial of the child's GasBudget).
|
||||||
|
func (g *GasBudget) Absorb(child GasBudget, forwarded uint64) {
|
||||||
|
spillover := forwarded - child.RegularGas - child.UsedRegularGas
|
||||||
|
|
||||||
|
g.UsedRegularGas -= child.RegularGas
|
||||||
|
g.RegularGas += child.RegularGas
|
||||||
|
g.StateGas = child.StateGas
|
||||||
|
g.UsedStateGas += child.UsedStateGas
|
||||||
|
|
||||||
|
g.UsedRegularGas -= spillover
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,56 +27,56 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func opAdd(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opAdd(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Add(&x, y)
|
y.Add(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSub(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSub(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Sub(&x, y)
|
y.Sub(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opMul(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opMul(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Mul(&x, y)
|
y.Mul(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opDiv(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opDiv(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Div(&x, y)
|
y.Div(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSdiv(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSdiv(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.SDiv(&x, y)
|
y.SDiv(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opMod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opMod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Mod(&x, y)
|
y.Mod(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.SMod(&x, y)
|
y.SMod(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opExp(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opExp(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
base, exponent := scope.Stack.pop(), scope.Stack.peek()
|
base, exponent := scope.Stack.pop1Peek1()
|
||||||
exponent.Exp(&base, exponent)
|
exponent.Exp(base, exponent)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSignExtend(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSignExtend(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
back, num := scope.Stack.pop(), scope.Stack.peek()
|
back, num := scope.Stack.pop1Peek1()
|
||||||
num.ExtendSign(num, &back)
|
num.ExtendSign(num, back)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ func opNot(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opLt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opLt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
if x.Lt(y) {
|
if x.Lt(y) {
|
||||||
y.SetOne()
|
y.SetOne()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -97,7 +97,7 @@ func opLt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opGt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opGt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
if x.Gt(y) {
|
if x.Gt(y) {
|
||||||
y.SetOne()
|
y.SetOne()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -107,7 +107,7 @@ func opGt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSlt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSlt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
if x.Slt(y) {
|
if x.Slt(y) {
|
||||||
y.SetOne()
|
y.SetOne()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -117,7 +117,7 @@ func opSlt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSgt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSgt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
if x.Sgt(y) {
|
if x.Sgt(y) {
|
||||||
y.SetOne()
|
y.SetOne()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -127,7 +127,7 @@ func opSgt(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opEq(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opEq(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
if x.Eq(y) {
|
if x.Eq(y) {
|
||||||
y.SetOne()
|
y.SetOne()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -147,38 +147,38 @@ func opIszero(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opAnd(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opAnd(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.And(&x, y)
|
y.And(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opOr(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opOr(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Or(&x, y)
|
y.Or(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opXor(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opXor(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y := scope.Stack.pop(), scope.Stack.peek()
|
x, y := scope.Stack.pop1Peek1()
|
||||||
y.Xor(&x, y)
|
y.Xor(x, y)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opByte(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opByte(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
th, val := scope.Stack.pop(), scope.Stack.peek()
|
th, val := scope.Stack.pop1Peek1()
|
||||||
val.Byte(&th)
|
val.Byte(th)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opAddmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opAddmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
|
x, y, z := scope.Stack.pop2Peek1()
|
||||||
z.AddMod(&x, &y, z)
|
z.AddMod(x, y, z)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opMulmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opMulmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
|
x, y, z := scope.Stack.pop2Peek1()
|
||||||
z.MulMod(&x, &y, z)
|
z.MulMod(x, y, z)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ func opMulmod(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// and pushes on the stack arg2 shifted to the left by arg1 number of bits.
|
// and pushes on the stack arg2 shifted to the left by arg1 number of bits.
|
||||||
func opSHL(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSHL(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
|
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
|
||||||
shift, value := scope.Stack.pop(), scope.Stack.peek()
|
shift, value := scope.Stack.pop1Peek1()
|
||||||
if shift.LtUint64(256) {
|
if shift.LtUint64(256) {
|
||||||
value.Lsh(value, uint(shift.Uint64()))
|
value.Lsh(value, uint(shift.Uint64()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -201,7 +201,7 @@ func opSHL(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill.
|
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill.
|
||||||
func opSHR(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSHR(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
|
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
|
||||||
shift, value := scope.Stack.pop(), scope.Stack.peek()
|
shift, value := scope.Stack.pop1Peek1()
|
||||||
if shift.LtUint64(256) {
|
if shift.LtUint64(256) {
|
||||||
value.Rsh(value, uint(shift.Uint64()))
|
value.Rsh(value, uint(shift.Uint64()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -214,7 +214,7 @@ func opSHR(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2,
|
// The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2,
|
||||||
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension.
|
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension.
|
||||||
func opSAR(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opSAR(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
shift, value := scope.Stack.pop(), scope.Stack.peek()
|
shift, value := scope.Stack.pop1Peek1()
|
||||||
if shift.GtUint64(256) {
|
if shift.GtUint64(256) {
|
||||||
if value.Sign() >= 0 {
|
if value.Sign() >= 0 {
|
||||||
value.Clear()
|
value.Clear()
|
||||||
|
|
@ -230,7 +230,7 @@ func opSAR(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opKeccak256(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opKeccak256(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
offset, size := scope.Stack.pop(), scope.Stack.peek()
|
offset, size := scope.Stack.pop1Peek1()
|
||||||
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
|
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
|
||||||
|
|
||||||
hash := crypto.Keccak256Hash(data)
|
hash := crypto.Keccak256Hash(data)
|
||||||
|
|
@ -286,11 +286,7 @@ func opCallDataSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opCallDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opCallDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
memOffset, dataOffset, length := scope.Stack.pop3()
|
||||||
memOffset = scope.Stack.pop()
|
|
||||||
dataOffset = scope.Stack.pop()
|
|
||||||
length = scope.Stack.pop()
|
|
||||||
)
|
|
||||||
dataOffset64, overflow := dataOffset.Uint64WithOverflow()
|
dataOffset64, overflow := dataOffset.Uint64WithOverflow()
|
||||||
if overflow {
|
if overflow {
|
||||||
dataOffset64 = math.MaxUint64
|
dataOffset64 = math.MaxUint64
|
||||||
|
|
@ -309,11 +305,7 @@ func opReturnDataSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func opReturnDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opReturnDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
memOffset, dataOffset, length := scope.Stack.pop3()
|
||||||
memOffset = scope.Stack.pop()
|
|
||||||
dataOffset = scope.Stack.pop()
|
|
||||||
length = scope.Stack.pop()
|
|
||||||
)
|
|
||||||
|
|
||||||
offset64, overflow := dataOffset.Uint64WithOverflow()
|
offset64, overflow := dataOffset.Uint64WithOverflow()
|
||||||
if overflow {
|
if overflow {
|
||||||
|
|
@ -321,7 +313,7 @@ func opReturnDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error)
|
||||||
}
|
}
|
||||||
// we can reuse dataOffset now (aliasing it for clarity)
|
// we can reuse dataOffset now (aliasing it for clarity)
|
||||||
var end = dataOffset
|
var end = dataOffset
|
||||||
end.Add(&dataOffset, &length)
|
end.Add(dataOffset, length)
|
||||||
end64, overflow := end.Uint64WithOverflow()
|
end64, overflow := end.Uint64WithOverflow()
|
||||||
if overflow || uint64(len(evm.returnData)) < end64 {
|
if overflow || uint64(len(evm.returnData)) < end64 {
|
||||||
return nil, ErrReturnDataOutOfBounds
|
return nil, ErrReturnDataOutOfBounds
|
||||||
|
|
@ -342,11 +334,7 @@ func opCodeSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opCodeCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opCodeCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
memOffset, codeOffset, length := scope.Stack.pop3()
|
||||||
memOffset = scope.Stack.pop()
|
|
||||||
codeOffset = scope.Stack.pop()
|
|
||||||
length = scope.Stack.pop()
|
|
||||||
)
|
|
||||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
if overflow {
|
if overflow {
|
||||||
uint64CodeOffset = math.MaxUint64
|
uint64CodeOffset = math.MaxUint64
|
||||||
|
|
@ -359,11 +347,8 @@ func opCodeCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
|
|
||||||
func opExtCodeCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opExtCodeCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
stack = scope.Stack
|
stack = scope.Stack
|
||||||
a = stack.pop()
|
a, memOffset, codeOffset, length = stack.pop4()
|
||||||
memOffset = stack.pop()
|
|
||||||
codeOffset = stack.pop()
|
|
||||||
length = stack.pop()
|
|
||||||
)
|
)
|
||||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
if overflow {
|
if overflow {
|
||||||
|
|
@ -480,7 +465,7 @@ func opGasLimit(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opPop(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opPop(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
scope.Stack.pop()
|
scope.Stack.drop()
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -492,13 +477,13 @@ func opMload(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opMstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opMstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
mStart, val := scope.Stack.pop(), scope.Stack.pop()
|
mStart, val := scope.Stack.pop2()
|
||||||
scope.Memory.Set32(mStart.Uint64(), &val)
|
scope.Memory.Set32(mStart.Uint64(), val)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opMstore8(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opMstore8(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
off, val := scope.Stack.pop(), scope.Stack.pop()
|
off, val := scope.Stack.pop2()
|
||||||
scope.Memory.store[off.Uint64()] = byte(val.Uint64())
|
scope.Memory.store[off.Uint64()] = byte(val.Uint64())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -515,8 +500,7 @@ func opSstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.readOnly {
|
if evm.readOnly {
|
||||||
return nil, ErrWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
loc := scope.Stack.pop()
|
loc, val := scope.Stack.pop2()
|
||||||
val := scope.Stack.pop()
|
|
||||||
evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32())
|
evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -525,8 +509,8 @@ func opJump(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.abort.Load() {
|
if evm.abort.Load() {
|
||||||
return nil, errStopToken
|
return nil, errStopToken
|
||||||
}
|
}
|
||||||
pos := scope.Stack.pop()
|
pos := scope.Stack.pop1()
|
||||||
if !scope.Contract.validJumpdest(&pos) {
|
if !scope.Contract.validJumpdest(pos) {
|
||||||
return nil, ErrInvalidJump
|
return nil, ErrInvalidJump
|
||||||
}
|
}
|
||||||
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
||||||
|
|
@ -537,9 +521,9 @@ func opJumpi(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.abort.Load() {
|
if evm.abort.Load() {
|
||||||
return nil, errStopToken
|
return nil, errStopToken
|
||||||
}
|
}
|
||||||
pos, cond := scope.Stack.pop(), scope.Stack.pop()
|
pos, cond := scope.Stack.pop2()
|
||||||
if !cond.IsZero() {
|
if !cond.IsZero() {
|
||||||
if !scope.Contract.validJumpdest(&pos) {
|
if !scope.Contract.validJumpdest(pos) {
|
||||||
return nil, ErrInvalidJump
|
return nil, ErrInvalidJump
|
||||||
}
|
}
|
||||||
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
||||||
|
|
@ -647,25 +631,22 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.readOnly {
|
|
||||||
return nil, ErrWriteProtection
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
value = scope.Stack.pop()
|
value = scope.Stack.pop()
|
||||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||||
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||||
gas = scope.Contract.Gas.RegularGas
|
|
||||||
)
|
)
|
||||||
|
// Apply EIP-150 to the regular gas left after the state charge.
|
||||||
|
forward := scope.Contract.Gas.RegularGas
|
||||||
if evm.chainRules.IsEIP150 {
|
if evm.chainRules.IsEIP150 {
|
||||||
gas -= gas / 64
|
forward -= forward / 64
|
||||||
}
|
}
|
||||||
|
|
||||||
// reuse size int for stackvalue
|
// reuse size int for stackvalue
|
||||||
stackvalue := size
|
stackvalue := size
|
||||||
|
|
||||||
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
||||||
|
res, addr, result, suberr := evm.Create(scope.Contract.Address(), input, child, &value)
|
||||||
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas), &value)
|
|
||||||
// Push item on the stack based on the returned error. If the ruleset is
|
// Push item on the stack based on the returned error. If the ruleset is
|
||||||
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
||||||
// rule) and treat as an error, if the ruleset is frontier we must
|
// rule) and treat as an error, if the ruleset is frontier we must
|
||||||
|
|
@ -679,7 +660,8 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
scope.Stack.push(&stackvalue)
|
scope.Stack.push(&stackvalue)
|
||||||
|
|
||||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
// Refund the leftover gas back to current frame
|
||||||
|
scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
if suberr == ErrExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
evm.returnData = res // set REVERT data to return data buffer
|
evm.returnData = res // set REVERT data to return data buffer
|
||||||
|
|
@ -690,24 +672,20 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if evm.readOnly {
|
|
||||||
return nil, ErrWriteProtection
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
endowment = scope.Stack.pop()
|
endowment = scope.Stack.pop()
|
||||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||||
salt = scope.Stack.pop()
|
salt = scope.Stack.pop()
|
||||||
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||||
gas = scope.Contract.Gas.RegularGas
|
|
||||||
)
|
)
|
||||||
|
// Apply EIP-150 to the regular gas left after the state charge.
|
||||||
|
forward := scope.Contract.Gas.RegularGas
|
||||||
|
forward -= forward / 64
|
||||||
|
|
||||||
// Apply EIP150
|
|
||||||
gas -= gas / 64
|
|
||||||
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
|
||||||
// reuse size int for stackvalue
|
// reuse size int for stackvalue
|
||||||
stackvalue := size
|
stackvalue := size
|
||||||
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas),
|
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||||
&endowment, &salt)
|
res, addr, result, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt)
|
||||||
// Push item on the stack based on the returned error.
|
// Push item on the stack based on the returned error.
|
||||||
if suberr != nil {
|
if suberr != nil {
|
||||||
stackvalue.Clear()
|
stackvalue.Clear()
|
||||||
|
|
@ -715,7 +693,9 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
stackvalue.SetBytes(addr.Bytes())
|
stackvalue.SetBytes(addr.Bytes())
|
||||||
}
|
}
|
||||||
scope.Stack.push(&stackvalue)
|
scope.Stack.push(&stackvalue)
|
||||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
|
||||||
|
// Refund the leftover gas back to current frame
|
||||||
|
scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
if suberr == ErrExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
evm.returnData = res // set REVERT data to return data buffer
|
evm.returnData = res // set REVERT data to return data buffer
|
||||||
|
|
@ -743,7 +723,12 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if !value.IsZero() {
|
if !value.IsZero() {
|
||||||
gas += params.CallStipend
|
gas += params.CallStipend
|
||||||
}
|
}
|
||||||
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value)
|
|
||||||
|
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||||
|
// gas table (see makeCallVariantGasCallEIP*); only the state reservoir
|
||||||
|
// needs to be handed off to the child here.
|
||||||
|
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||||
|
ret, result, err := evm.Call(scope.Contract.Address(), toAddr, args, childBudget, &value)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
temp.Clear()
|
temp.Clear()
|
||||||
|
|
@ -751,11 +736,11 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
temp.SetOne()
|
temp.SetOne()
|
||||||
}
|
}
|
||||||
stack.push(&temp)
|
stack.push(&temp)
|
||||||
|
|
||||||
if err == nil || err == ErrExecutionReverted {
|
if err == nil || err == ErrExecutionReverted {
|
||||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
|
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
|
||||||
|
|
||||||
evm.returnData = ret
|
evm.returnData = ret
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|
@ -776,8 +761,11 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if !value.IsZero() {
|
if !value.IsZero() {
|
||||||
gas += params.CallStipend
|
gas += params.CallStipend
|
||||||
}
|
}
|
||||||
|
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||||
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value)
|
// gas table, only the state reservoir needs to be handed off to the
|
||||||
|
// child here.
|
||||||
|
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||||
|
ret, result, err := evm.CallCode(scope.Contract.Address(), toAddr, args, childBudget, &value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
temp.Clear()
|
temp.Clear()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -788,7 +776,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
evm.returnData = ret
|
evm.returnData = ret
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|
@ -806,7 +794,11 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// Get arguments from the memory.
|
// Get arguments from the memory.
|
||||||
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
||||||
|
|
||||||
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas), scope.Contract.value)
|
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||||
|
// gas table, only the state reservoir needs to be handed off to the
|
||||||
|
// child here.
|
||||||
|
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||||
|
ret, result, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, childBudget, scope.Contract.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
temp.Clear()
|
temp.Clear()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -816,8 +808,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
if err == nil || err == ErrExecutionReverted {
|
if err == nil || err == ErrExecutionReverted {
|
||||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
|
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
|
||||||
|
|
||||||
evm.returnData = ret
|
evm.returnData = ret
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|
@ -835,7 +826,11 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// Get arguments from the memory.
|
// Get arguments from the memory.
|
||||||
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
||||||
|
|
||||||
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas))
|
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||||
|
// gas table, only the state reservoir needs to be handed off to the
|
||||||
|
// child here.
|
||||||
|
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||||
|
ret, result, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, childBudget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
temp.Clear()
|
temp.Clear()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -846,21 +841,21 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
evm.returnData = ret
|
evm.returnData = ret
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func opReturn(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opReturn(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
offset, size := scope.Stack.pop2()
|
||||||
ret := scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
ret := scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||||
|
|
||||||
return ret, errStopToken
|
return ret, errStopToken
|
||||||
}
|
}
|
||||||
|
|
||||||
func opRevert(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
func opRevert(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
offset, size := scope.Stack.pop2()
|
||||||
ret := scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
ret := scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||||
|
|
||||||
evm.returnData = ret
|
evm.returnData = ret
|
||||||
|
|
@ -882,7 +877,7 @@ func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
this = scope.Contract.Address()
|
this = scope.Contract.Address()
|
||||||
balance = evm.StateDB.GetBalance(this)
|
balance = evm.StateDB.GetBalance(this)
|
||||||
top = scope.Stack.pop()
|
top = scope.Stack.pop1()
|
||||||
beneficiary = common.Address(top.Bytes20())
|
beneficiary = common.Address(top.Bytes20())
|
||||||
)
|
)
|
||||||
// The funds are burned immediately if the beneficiary is the caller itself,
|
// The funds are burned immediately if the beneficiary is the caller itself,
|
||||||
|
|
@ -912,7 +907,7 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
||||||
var (
|
var (
|
||||||
this = scope.Contract.Address()
|
this = scope.Contract.Address()
|
||||||
balance = evm.StateDB.GetBalance(this)
|
balance = evm.StateDB.GetBalance(this)
|
||||||
top = scope.Stack.pop()
|
top = scope.Stack.pop1()
|
||||||
beneficiary = common.Address(top.Bytes20())
|
beneficiary = common.Address(top.Bytes20())
|
||||||
newContract = evm.StateDB.IsNewContract(this)
|
newContract = evm.StateDB.IsNewContract(this)
|
||||||
)
|
)
|
||||||
|
|
@ -1081,9 +1076,9 @@ func makeLog(size int) executionFunc {
|
||||||
}
|
}
|
||||||
topics := make([]common.Hash, size)
|
topics := make([]common.Hash, size)
|
||||||
stack := scope.Stack
|
stack := scope.Stack
|
||||||
mStart, mSize := stack.pop(), stack.pop()
|
mStart, mSize := stack.pop2()
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
addr := stack.pop()
|
addr := stack.pop1()
|
||||||
topics[i] = addr.Bytes32()
|
topics[i] = addr.Bytes32()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -724,7 +724,7 @@ func TestRandom(t *testing.T) {
|
||||||
)
|
)
|
||||||
opRandom(&pc, evm, &ScopeContext{nil, stack, nil})
|
opRandom(&pc, evm, &ScopeContext{nil, stack, nil})
|
||||||
if have, want := stack.len(), 1; have != want {
|
if have, want := stack.len(), 1; have != want {
|
||||||
t.Errorf("test '%v': want %d item(s) on stack, have %d: ", tt.name, have, want)
|
t.Errorf("test '%v': want %d item(s) on stack, have %d: ", tt.name, want, have)
|
||||||
}
|
}
|
||||||
actual := stack.pop()
|
actual := stack.pop()
|
||||||
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
|
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
||||||
// associated costs.
|
// associated costs.
|
||||||
contractAddr := contract.Address()
|
contractAddr := contract.Address()
|
||||||
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
|
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
|
||||||
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||||
if consumed < wanted {
|
if consumed < wanted {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
@ -192,10 +192,8 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
||||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||||
}
|
}
|
||||||
// for tracing: this gas consumption event is emitted below in the debug section.
|
// for tracing: this gas consumption event is emitted below in the debug section.
|
||||||
if contract.Gas.RegularGas < cost {
|
if !contract.Gas.chargeRegularOnly(cost) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
} else {
|
|
||||||
contract.Gas.RegularGas -= cost
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||||
|
|
@ -224,11 +222,12 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
||||||
}
|
}
|
||||||
// for tracing: this gas consumption event is emitted below in the debug section.
|
if dynamicCost.StateGas == 0 {
|
||||||
if contract.Gas.RegularGas < dynamicCost.RegularGas {
|
if !contract.Gas.chargeRegularOnly(dynamicCost.RegularGas) {
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
} else if !contract.Gas.charge(dynamicCost) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
} else {
|
|
||||||
contract.Gas.RegularGas -= dynamicCost.RegularGas
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
|
||||||
timeout := make(chan bool)
|
timeout := make(chan bool)
|
||||||
|
|
||||||
go func(evm *EVM) {
|
go func(evm *EVM) {
|
||||||
_, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64), new(uint256.Int))
|
_, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64, 0), new(uint256.Int))
|
||||||
errChannel <- err
|
errChannel <- err
|
||||||
}(evm)
|
}(evm)
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ func BenchmarkInterpreter(b *testing.B) {
|
||||||
value = uint256.NewInt(0)
|
value = uint256.NewInt(0)
|
||||||
stack = newStackForTesting()
|
stack = newStackForTesting()
|
||||||
mem = NewMemory()
|
mem = NewMemory()
|
||||||
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil)
|
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas, 0), nil)
|
||||||
)
|
)
|
||||||
stack.push(uint256.NewInt(123))
|
stack.push(uint256.NewInt(123))
|
||||||
stack.push(uint256.NewInt(123))
|
stack.push(uint256.NewInt(123))
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error)
|
executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error)
|
||||||
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64
|
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64
|
||||||
|
intrinsicGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
|
||||||
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64
|
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64
|
||||||
memorySizeFunc func(*Stack) (size uint64, overflow bool)
|
memorySizeFunc func(*Stack) (size uint64, overflow bool)
|
||||||
)
|
)
|
||||||
|
|
@ -97,6 +98,7 @@ func newAmsterdamInstructionSet() JumpTable {
|
||||||
instructionSet := newOsakaInstructionSet()
|
instructionSet := newOsakaInstructionSet()
|
||||||
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
|
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
|
||||||
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
|
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
|
||||||
|
enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase)
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
// Charge the remaining difference here already, to correctly calculate available
|
// Charge the remaining difference here already, to correctly calculate available
|
||||||
// gas for call
|
// gas for call
|
||||||
if !contract.UseGas(GasCosts{RegularGas: coldCost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
if !contract.chargeRegular(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return GasCosts{}, ErrOutOfGas
|
return GasCosts{}, ErrOutOfGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +276,7 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
||||||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
var (
|
var (
|
||||||
eip2929Cost uint64
|
eip2929Cost uint64
|
||||||
|
|
@ -295,7 +295,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
|
|
||||||
// Charge the remaining difference here already, to correctly calculate
|
// Charge the remaining difference here already, to correctly calculate
|
||||||
// available gas for call
|
// available gas for call
|
||||||
if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return GasCosts{}, ErrOutOfGas
|
return GasCosts{}, ErrOutOfGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +312,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
// it can effectively prevent accessing the states in the following steps.
|
// it can effectively prevent accessing the states in the following steps.
|
||||||
// It's an essential safeguard before any stateful check.
|
// It's an essential safeguard before any stateful check.
|
||||||
if contract.Gas.RegularGas < intrinsicCost.RegularGas {
|
if contract.Gas.RegularGas < intrinsicCost {
|
||||||
return GasCosts{}, ErrOutOfGas
|
return GasCosts{}, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,13 +324,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
evm.StateDB.AddAddressToAccessList(target)
|
evm.StateDB.AddAddressToAccessList(target)
|
||||||
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||||
}
|
}
|
||||||
if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return GasCosts{}, ErrOutOfGas
|
return GasCosts{}, ErrOutOfGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Calculate the gas budget for the nested call. The costs defined by
|
// Calculate the gas budget for the nested call. The costs defined by
|
||||||
// EIP-2929 and EIP-7702 have already been applied.
|
// EIP-2929 and EIP-7702 have already been applied.
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.back(0))
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.back(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GasCosts{}, err
|
return GasCosts{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -339,6 +339,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
// part of the dynamic gas. This will ensure it is correctly reported to
|
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||||
// tracers.
|
// tracers.
|
||||||
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
|
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
|
||||||
|
// Undo the RegularGasUsed increments from the direct UseGas charges,
|
||||||
|
// since this gas will be re-charged via the returned cost.
|
||||||
|
contract.Gas.UsedRegularGas -= eip2929Cost
|
||||||
|
contract.Gas.UsedRegularGas -= eip7702Cost
|
||||||
|
|
||||||
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
||||||
// the CALL opcode itself, and the cost incurred by nested calls.
|
// the CALL opcode itself, and the cost incurred by nested calls.
|
||||||
|
|
@ -349,7 +353,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
|
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
|
||||||
return GasCosts{}, ErrGasUintOverflow
|
return GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost.RegularGas); overflow {
|
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
|
||||||
return GasCosts{}, ErrGasUintOverflow
|
return GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
|
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ 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/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,17 +30,18 @@ func NewEnv(cfg *Config) *vm.EVM {
|
||||||
BlobHashes: cfg.BlobHashes,
|
BlobHashes: cfg.BlobHashes,
|
||||||
}
|
}
|
||||||
blockContext := vm.BlockContext{
|
blockContext := vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
GetHash: cfg.GetHashFn,
|
GetHash: cfg.GetHashFn,
|
||||||
Coinbase: cfg.Coinbase,
|
Coinbase: cfg.Coinbase,
|
||||||
BlockNumber: cfg.BlockNumber,
|
BlockNumber: cfg.BlockNumber,
|
||||||
Time: cfg.Time,
|
Time: cfg.Time,
|
||||||
Difficulty: cfg.Difficulty,
|
Difficulty: cfg.Difficulty,
|
||||||
GasLimit: cfg.GasLimit,
|
GasLimit: cfg.GasLimit,
|
||||||
BaseFee: cfg.BaseFee,
|
BaseFee: cfg.BaseFee,
|
||||||
BlobBaseFee: cfg.BlobBaseFee,
|
BlobBaseFee: cfg.BlobBaseFee,
|
||||||
Random: cfg.Random,
|
Random: cfg.Random,
|
||||||
|
CostPerStateByte: params.CostPerStateByte,
|
||||||
}
|
}
|
||||||
|
|
||||||
evm := vm.NewEVM(blockContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig)
|
evm := vm.NewEVM(blockContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig)
|
||||||
|
|
|
||||||
|
|
@ -144,15 +144,15 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||||
// set the receiver's (the executing contract) code for execution.
|
// set the receiver's (the executing contract) code for execution.
|
||||||
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
|
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
|
||||||
// Call the code with the given configuration.
|
// Call the code with the given configuration.
|
||||||
ret, leftOverGas, err := vmenv.Call(
|
ret, result, err := vmenv.Call(
|
||||||
cfg.Origin,
|
cfg.Origin,
|
||||||
common.BytesToAddress([]byte("contract")),
|
common.BytesToAddress([]byte("contract")),
|
||||||
input,
|
input,
|
||||||
vm.NewGasBudget(cfg.GasLimit),
|
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||||
uint256.MustFromBig(cfg.Value),
|
uint256.MustFromBig(cfg.Value),
|
||||||
)
|
)
|
||||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err)
|
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
|
||||||
}
|
}
|
||||||
return ret, cfg.State, err
|
return ret, cfg.State, err
|
||||||
}
|
}
|
||||||
|
|
@ -179,16 +179,16 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
||||||
// - reset transient storage(eip 1153)
|
// - reset transient storage(eip 1153)
|
||||||
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
|
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
|
||||||
// Call the code with the given configuration.
|
// Call the code with the given configuration.
|
||||||
code, address, leftOverGas, err := vmenv.Create(
|
code, address, result, err := vmenv.Create(
|
||||||
cfg.Origin,
|
cfg.Origin,
|
||||||
input,
|
input,
|
||||||
vm.NewGasBudget(cfg.GasLimit),
|
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||||
uint256.MustFromBig(cfg.Value),
|
uint256.MustFromBig(cfg.Value),
|
||||||
)
|
)
|
||||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err)
|
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
|
||||||
}
|
}
|
||||||
return code, address, leftOverGas.RegularGas, err
|
return code, address, result.RegularGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call executes the code given by the contract's address. It will return the
|
// Call executes the code given by the contract's address. It will return the
|
||||||
|
|
@ -213,15 +213,15 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
||||||
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
|
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
|
||||||
|
|
||||||
// Call the code with the given configuration.
|
// Call the code with the given configuration.
|
||||||
ret, leftOverGas, err := vmenv.Call(
|
ret, result, err := vmenv.Call(
|
||||||
cfg.Origin,
|
cfg.Origin,
|
||||||
address,
|
address,
|
||||||
input,
|
input,
|
||||||
vm.NewGasBudget(cfg.GasLimit),
|
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||||
uint256.MustFromBig(cfg.Value),
|
uint256.MustFromBig(cfg.Value),
|
||||||
)
|
)
|
||||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err)
|
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
|
||||||
}
|
}
|
||||||
return ret, leftOverGas.RegularGas, err
|
return ret, result.RegularGas, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,62 @@ func (s *Stack) len() int {
|
||||||
return s.size
|
return s.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drop removes the top element without reading it.
|
||||||
|
func (s *Stack) drop() {
|
||||||
|
s.inner.top--
|
||||||
|
s.size--
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop1 removes the top element and returns a pointer to it. The pointer
|
||||||
|
// stays valid only until the next push or sub call.
|
||||||
|
func (s *Stack) pop1() *uint256.Int {
|
||||||
|
s.inner.top--
|
||||||
|
s.size--
|
||||||
|
return &s.inner.data[s.inner.top]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop2 removes the top two elements and returns pointers to them. The
|
||||||
|
// pointers stay valid only until the next push or sub call.
|
||||||
|
func (s *Stack) pop2() (top, second *uint256.Int) {
|
||||||
|
s.inner.top -= 2
|
||||||
|
s.size -= 2
|
||||||
|
return &s.inner.data[s.inner.top+1], &s.inner.data[s.inner.top]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop3 removes the top three elements and returns pointers to them. The
|
||||||
|
// pointers stay valid only until the next push or sub call.
|
||||||
|
func (s *Stack) pop3() (top, second, third *uint256.Int) {
|
||||||
|
s.inner.top -= 3
|
||||||
|
s.size -= 3
|
||||||
|
return &s.inner.data[s.inner.top+2], &s.inner.data[s.inner.top+1], &s.inner.data[s.inner.top]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop4 removes the top four elements and returns pointers to them. The
|
||||||
|
// pointers stay valid only until the next push or sub call.
|
||||||
|
func (s *Stack) pop4() (top, second, third, fourth *uint256.Int) {
|
||||||
|
s.inner.top -= 4
|
||||||
|
s.size -= 4
|
||||||
|
return &s.inner.data[s.inner.top+3], &s.inner.data[s.inner.top+2], &s.inner.data[s.inner.top+1], &s.inner.data[s.inner.top]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop1Peek1 removes the top element and returns pointers to it and to the new
|
||||||
|
// top, the usual operand and write target of a binary operation. The first
|
||||||
|
// pointer stays valid only until the next push or sub call.
|
||||||
|
func (s *Stack) pop1Peek1() (top, rest *uint256.Int) {
|
||||||
|
s.inner.top--
|
||||||
|
s.size--
|
||||||
|
return &s.inner.data[s.inner.top], &s.inner.data[s.inner.top-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop2Peek1 removes the top two elements and returns pointers to them and to
|
||||||
|
// the new top, for three operand operations. The first two pointers stay
|
||||||
|
// valid only until the next push or sub call.
|
||||||
|
func (s *Stack) pop2Peek1() (top, second, rest *uint256.Int) {
|
||||||
|
s.inner.top -= 2
|
||||||
|
s.size -= 2
|
||||||
|
return &s.inner.data[s.inner.top+1], &s.inner.data[s.inner.top], &s.inner.data[s.inner.top-1]
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Stack) swap1() {
|
func (s *Stack) swap1() {
|
||||||
s.inner.data[s.bottom+s.size-2], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-2]
|
s.inner.data[s.bottom+s.size-2], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-2]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1936,111 +1936,6 @@ func newGetBlobEnv(t testing.TB, version byte, custody types.CustodyBitmap) (*no
|
||||||
api := newConsensusAPIWithoutHeartbeat(ethServ)
|
api := newConsensusAPIWithoutHeartbeat(ethServ)
|
||||||
return n, api
|
return n, api
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBlobsV1(t *testing.T) {
|
|
||||||
n, api := newGetBlobEnv(t, 0, types.CustodyBitmapAll)
|
|
||||||
defer n.Close()
|
|
||||||
|
|
||||||
suites := []struct {
|
|
||||||
start int
|
|
||||||
limit int
|
|
||||||
fillRandom bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
start: 0, limit: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 0, limit: 1, fillRandom: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 0, limit: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 0, limit: 2, fillRandom: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 1, limit: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 1, limit: 3, fillRandom: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 0, limit: 6,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 0, limit: 6, fillRandom: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 1, limit: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: 1, limit: 5, fillRandom: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, suite := range suites {
|
|
||||||
// Fill the request for retrieving blobs
|
|
||||||
var (
|
|
||||||
vhashes []common.Hash
|
|
||||||
expect engine.BlobAndProofListV1
|
|
||||||
)
|
|
||||||
// fill missing blob at the beginning
|
|
||||||
if suite.fillRandom {
|
|
||||||
vhashes = append(vhashes, testrand.Hash())
|
|
||||||
expect = append(expect, nil)
|
|
||||||
}
|
|
||||||
for j := suite.start; j < suite.limit; j++ {
|
|
||||||
vhashes = append(vhashes, testBlobVHashes[j])
|
|
||||||
expect = append(expect, &engine.BlobAndProofV1{
|
|
||||||
Blob: testBlobs[j][:],
|
|
||||||
Proof: testBlobProofs[j][:],
|
|
||||||
})
|
|
||||||
|
|
||||||
// fill missing blobs in the middle
|
|
||||||
if suite.fillRandom && rand.Intn(2) == 0 {
|
|
||||||
vhashes = append(vhashes, testrand.Hash())
|
|
||||||
expect = append(expect, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fill missing blobs at the end
|
|
||||||
if suite.fillRandom {
|
|
||||||
vhashes = append(vhashes, testrand.Hash())
|
|
||||||
expect = append(expect, nil)
|
|
||||||
}
|
|
||||||
result, err := api.GetBlobsV1(context.Background(), vhashes)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error for case %d, %v", i, err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(result, expect) {
|
|
||||||
t.Fatalf("Unexpected result for case %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBlobsV1AfterOsakaFork(t *testing.T) {
|
|
||||||
genesis := &core.Genesis{
|
|
||||||
Config: params.MergedTestChainConfig,
|
|
||||||
Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}},
|
|
||||||
Difficulty: common.Big0,
|
|
||||||
Timestamp: 1, // Timestamp > 0 to ensure Osaka fork is active
|
|
||||||
}
|
|
||||||
n, ethServ := startEthService(t, genesis, nil)
|
|
||||||
defer n.Close()
|
|
||||||
|
|
||||||
var engineErr *engine.EngineAPIError
|
|
||||||
api := newConsensusAPIWithoutHeartbeat(ethServ)
|
|
||||||
_, err := api.GetBlobsV1(context.Background(), []common.Hash{testrand.Hash()})
|
|
||||||
if !errors.As(err, &engineErr) {
|
|
||||||
t.Fatalf("Unexpected error: %T", err)
|
|
||||||
} else {
|
|
||||||
if engineErr.ErrorCode() != -38005 {
|
|
||||||
t.Fatalf("Expected error code -38005, got %d", engineErr.ErrorCode())
|
|
||||||
}
|
|
||||||
if engineErr.Error() != "Unsupported fork" {
|
|
||||||
t.Fatalf("Expected error message 'Unsupported fork', got '%s'", engineErr.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBlobsV2And3(t *testing.T) {
|
func TestGetBlobsV2And3(t *testing.T) {
|
||||||
n, api := newGetBlobEnv(t, 1, types.CustodyBitmapAll)
|
n, api := newGetBlobEnv(t, 1, types.CustodyBitmapAll)
|
||||||
defer n.Close()
|
defer n.Close()
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package catalyst
|
package catalyst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
|
|
@ -47,6 +48,31 @@ func (api *testingAPI) BuildBlockV1(parentHash common.Hash, payloadAttributes en
|
||||||
if api.eth.BlockChain().CurrentBlock().Hash() != parentHash {
|
if api.eth.BlockChain().CurrentBlock().Hash() != parentHash {
|
||||||
return nil, errors.New("parentHash is not current head")
|
return nil, errors.New("parentHash is not current head")
|
||||||
}
|
}
|
||||||
|
_, block, err := api.buildTestingBlock(payloadAttributes, transactions, extraData)
|
||||||
|
return block, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitBlockV1 builds a block from the supplied attributes and transactions, inserts
|
||||||
|
// it into the chain, and sets it as the new canonical head. It is the equivalent of
|
||||||
|
// BuildBlockV1 followed by engine_newPayload + engine_forkchoiceUpdated, but skips the
|
||||||
|
// serialize/deserialize round-trip through ExecutableData. The block is built on top of
|
||||||
|
// the current head.
|
||||||
|
func (api *testingAPI) CommitBlockV1(ctx context.Context, payloadAttributes engine.PayloadAttributes, transactions *[]hexutil.Bytes, extraData *hexutil.Bytes) (common.Hash, error) {
|
||||||
|
block, _, err := api.buildTestingBlock(payloadAttributes, transactions, extraData)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
if _, err := api.eth.BlockChain().InsertBlockWithoutSetHead(ctx, block, false); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
if _, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
return block.Hash(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *testingAPI) buildTestingBlock(payloadAttributes engine.PayloadAttributes, transactions *[]hexutil.Bytes, extraData *hexutil.Bytes) (*types.Block, *engine.ExecutionPayloadEnvelope, error) {
|
||||||
|
parentHash := api.eth.BlockChain().CurrentBlock().Hash()
|
||||||
// If transactions is empty but not nil, build an empty block
|
// If transactions is empty but not nil, build an empty block
|
||||||
// If the transactions is nil, build a block with the current transactions from the txpool
|
// If the transactions is nil, build a block with the current transactions from the txpool
|
||||||
// If the transactions is not nil and not empty, build a block with the transactions
|
// If the transactions is not nil and not empty, build a block with the transactions
|
||||||
|
|
@ -60,7 +86,7 @@ func (api *testingAPI) BuildBlockV1(parentHash common.Hash, payloadAttributes en
|
||||||
var err error
|
var err error
|
||||||
txs, err = engine.DecodeTransactions(dec)
|
txs, err = engine.DecodeTransactions(dec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extra := make([]byte, 0)
|
extra := make([]byte, 0)
|
||||||
|
|
|
||||||
|
|
@ -119,3 +119,90 @@ func TestBuildBlockV1(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommitBlockV1(t *testing.T) {
|
||||||
|
genesis, blocks := generateMergeChain(5, true)
|
||||||
|
n, ethservice := startEthService(t, genesis, blocks)
|
||||||
|
defer n.Close()
|
||||||
|
|
||||||
|
api := &testingAPI{eth: ethservice}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
nextAttrs := func() engine.PayloadAttributes {
|
||||||
|
head := ethservice.BlockChain().CurrentBlock()
|
||||||
|
return engine.PayloadAttributes{
|
||||||
|
Timestamp: head.Time + 1,
|
||||||
|
Random: crypto.Keccak256Hash([]byte("commit-test")),
|
||||||
|
SuggestedFeeRecipient: head.Coinbase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("commitEmptyBlock", func(t *testing.T) {
|
||||||
|
parent := ethservice.BlockChain().CurrentBlock()
|
||||||
|
emptyTxs := []hexutil.Bytes{}
|
||||||
|
hash, err := api.CommitBlockV1(ctx, nextAttrs(), &emptyTxs, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CommitBlockV1 failed: %v", err)
|
||||||
|
}
|
||||||
|
head := ethservice.BlockChain().CurrentBlock()
|
||||||
|
if head.Hash() != hash {
|
||||||
|
t.Errorf("head hash mismatch: got %x want %x", head.Hash(), hash)
|
||||||
|
}
|
||||||
|
if head.Number.Uint64() != parent.Number.Uint64()+1 {
|
||||||
|
t.Errorf("head number mismatch: got %d want %d", head.Number.Uint64(), parent.Number.Uint64()+1)
|
||||||
|
}
|
||||||
|
block := ethservice.BlockChain().GetBlockByHash(hash)
|
||||||
|
if block == nil {
|
||||||
|
t.Fatal("committed block not found in chain")
|
||||||
|
}
|
||||||
|
if len(block.Transactions()) != 0 {
|
||||||
|
t.Errorf("expected empty block, got %d transactions", len(block.Transactions()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("commitBlockWithTransactions", func(t *testing.T) {
|
||||||
|
parent := ethservice.BlockChain().CurrentBlock()
|
||||||
|
nonce, _ := ethservice.APIBackend.GetPoolNonce(ctx, testAddr)
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(nonce, testAddr, big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||||
|
enc, _ := tx.MarshalBinary()
|
||||||
|
txs := []hexutil.Bytes{enc}
|
||||||
|
|
||||||
|
hash, err := api.CommitBlockV1(ctx, nextAttrs(), &txs, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CommitBlockV1 failed: %v", err)
|
||||||
|
}
|
||||||
|
head := ethservice.BlockChain().CurrentBlock()
|
||||||
|
if head.Hash() != hash {
|
||||||
|
t.Errorf("head hash mismatch: got %x want %x", head.Hash(), hash)
|
||||||
|
}
|
||||||
|
if head.Number.Uint64() != parent.Number.Uint64()+1 {
|
||||||
|
t.Errorf("head number mismatch: got %d want %d", head.Number.Uint64(), parent.Number.Uint64()+1)
|
||||||
|
}
|
||||||
|
block := ethservice.BlockChain().GetBlockByHash(hash)
|
||||||
|
if block == nil {
|
||||||
|
t.Fatal("committed block not found in chain")
|
||||||
|
}
|
||||||
|
if len(block.Transactions()) != 1 {
|
||||||
|
t.Fatalf("expected 1 transaction, got %d", len(block.Transactions()))
|
||||||
|
}
|
||||||
|
if block.Transactions()[0].Hash() != tx.Hash() {
|
||||||
|
t.Errorf("transaction hash mismatch: got %x want %x", block.Transactions()[0].Hash(), tx.Hash())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("commitWithExtraData", func(t *testing.T) {
|
||||||
|
extra := hexutil.Bytes([]byte("hello"))
|
||||||
|
emptyTxs := []hexutil.Bytes{}
|
||||||
|
hash, err := api.CommitBlockV1(ctx, nextAttrs(), &emptyTxs, &extra)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CommitBlockV1 failed: %v", err)
|
||||||
|
}
|
||||||
|
block := ethservice.BlockChain().GetBlockByHash(hash)
|
||||||
|
if block == nil {
|
||||||
|
t.Fatal("committed block not found in chain")
|
||||||
|
}
|
||||||
|
if string(block.Extra()) != "hello" {
|
||||||
|
t.Errorf("extraData mismatch: got %q want %q", block.Extra(), "hello")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -297,9 +297,11 @@ func (d *Downloader) fetchHeaders(from uint64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the pivot became stale (older than 2*64-8 (bit of wiggle room)),
|
// If the pivot became stale (older than 2*64-8 (bit of wiggle room)),
|
||||||
// move it ahead to HEAD-64
|
// move it ahead to HEAD-64.
|
||||||
|
//
|
||||||
|
// The state syncer is consulted first before the pivot movement.
|
||||||
d.pivotLock.Lock()
|
d.pivotLock.Lock()
|
||||||
if d.pivotHeader != nil {
|
if d.pivotHeader != nil && d.snapSyncer.FrozenPivot() == nil {
|
||||||
if head.Number.Uint64() > d.pivotHeader.Number.Uint64()+2*uint64(fsMinFullBlocks)-8 {
|
if head.Number.Uint64() > d.pivotHeader.Number.Uint64()+2*uint64(fsMinFullBlocks)-8 {
|
||||||
// Retrieve the next pivot header, either from skeleton chain
|
// Retrieve the next pivot header, either from skeleton chain
|
||||||
// or the filled chain
|
// or the filled chain
|
||||||
|
|
|
||||||
|
|
@ -295,12 +295,19 @@ func (d *Downloader) Progress() ethereum.SyncProgress {
|
||||||
SyncedBytecodeBytes: uint64(progress.BytecodeBytes),
|
SyncedBytecodeBytes: uint64(progress.BytecodeBytes),
|
||||||
SyncedStorage: progress.StorageSynced,
|
SyncedStorage: progress.StorageSynced,
|
||||||
SyncedStorageBytes: uint64(progress.StorageBytes),
|
SyncedStorageBytes: uint64(progress.StorageBytes),
|
||||||
|
|
||||||
|
// Snap/1 progress fields
|
||||||
HealedTrienodes: progress.TrienodeHealSynced,
|
HealedTrienodes: progress.TrienodeHealSynced,
|
||||||
HealedTrienodeBytes: uint64(progress.TrienodeHealBytes),
|
HealedTrienodeBytes: uint64(progress.TrienodeHealBytes),
|
||||||
HealedBytecodes: progress.BytecodeHealSynced,
|
HealedBytecodes: progress.BytecodeHealSynced,
|
||||||
HealedBytecodeBytes: uint64(progress.BytecodeHealBytes),
|
HealedBytecodeBytes: uint64(progress.BytecodeHealBytes),
|
||||||
HealingTrienodes: progress.HealingTrienodes,
|
HealingTrienodes: progress.HealingTrienodes,
|
||||||
HealingBytecode: progress.HealingBytecode,
|
HealingBytecode: progress.HealingBytecode,
|
||||||
|
|
||||||
|
// Snap/2 progress fields
|
||||||
|
SyncedAccessLists: progress.AccessListSynced,
|
||||||
|
TotalAccessLists: progress.AccessListTotal,
|
||||||
|
TrieGenProgress: progress.TrieGenPercent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -496,6 +503,18 @@ func (d *Downloader) syncToHead() (err error) {
|
||||||
if mode == ethconfig.SnapSync && pivot == nil {
|
if mode == ethconfig.SnapSync && pivot == nil {
|
||||||
pivot = d.blockchain.CurrentBlock()
|
pivot = d.blockchain.CurrentBlock()
|
||||||
}
|
}
|
||||||
|
// If the snap syncer froze its pivot in a previous cycle, resume against
|
||||||
|
// the frozen header instead of a fresh one.
|
||||||
|
if mode == ethconfig.SnapSync && pivot != nil {
|
||||||
|
if frozen := d.snapSyncer.FrozenPivot(); frozen != nil {
|
||||||
|
if rawdb.ReadCanonicalHash(d.stateDB, frozen.Number.Uint64()) == frozen.Hash() {
|
||||||
|
log.Info("Resuming snap sync against frozen pivot", "number", frozen.Number, "hash", frozen.Hash())
|
||||||
|
pivot = frozen
|
||||||
|
} else {
|
||||||
|
log.Warn("Frozen pivot is no longer canonical", "number", frozen.Number, "hash", frozen.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
height := latest.Number.Uint64()
|
height := latest.Number.Uint64()
|
||||||
|
|
||||||
// In beacon mode, use the skeleton chain for the ancestor lookup
|
// In beacon mode, use the skeleton chain for the ancestor lookup
|
||||||
|
|
@ -921,7 +940,9 @@ func (d *Downloader) processSnapSyncContent() error {
|
||||||
// the results in the meantime.
|
// the results in the meantime.
|
||||||
//
|
//
|
||||||
// Note, there's no issue with memory piling up since after 64 blocks the
|
// Note, there's no issue with memory piling up since after 64 blocks the
|
||||||
// pivot will forcefully move so these accumulators will be dropped.
|
// pivot will forcefully move so these accumulators will be dropped. The
|
||||||
|
// exception is snap/2 trie generation, where the pivot is frozen on
|
||||||
|
// purpose and results accumulate until the generation finishes.
|
||||||
var (
|
var (
|
||||||
oldPivot *fetchResult // Locked in pivot block, might change eventually
|
oldPivot *fetchResult // Locked in pivot block, might change eventually
|
||||||
oldTail []*fetchResult // Downloaded content after the pivot
|
oldTail []*fetchResult // Downloaded content after the pivot
|
||||||
|
|
@ -978,11 +999,15 @@ func (d *Downloader) processSnapSyncContent() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if P != nil {
|
if P != nil {
|
||||||
// If new pivot block found, cancel old state retrieval and restart
|
// If new pivot block found, cancel old state retrieval and restart.
|
||||||
if oldPivot != P {
|
if oldPivot != P {
|
||||||
sync.Cancel()
|
// Skip the restart if the running sync already targets the
|
||||||
sync = d.syncState(P.Header)
|
// pivot's root (e.g, no pivot block movement yet).
|
||||||
go closeOnErr(sync)
|
if sync.pivot.Root != P.Header.Root {
|
||||||
|
sync.Cancel()
|
||||||
|
sync = d.syncState(P.Header)
|
||||||
|
go closeOnErr(sync)
|
||||||
|
}
|
||||||
oldPivot = P
|
oldPivot = P
|
||||||
}
|
}
|
||||||
// Wait for completion, occasionally checking for pivot staleness
|
// Wait for completion, occasionally checking for pivot staleness
|
||||||
|
|
@ -1114,11 +1139,23 @@ func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) erro
|
||||||
// snap/2 syncer skips snap/1-only peers, which cannot answer its BAL requests.
|
// snap/2 syncer skips snap/1-only peers, which cannot answer its BAL requests.
|
||||||
func (d *Downloader) RegisterSnapPeer(p *snap.Peer) error {
|
func (d *Downloader) RegisterSnapPeer(p *snap.Peer) error {
|
||||||
if p.Version() < d.snapSyncer.Version() {
|
if p.Version() < d.snapSyncer.Version() {
|
||||||
|
// The peer speaks an older snap version than the active syncer needs
|
||||||
|
// (e.g. snap/1 while we sync via snap/2). We still serve it, but it
|
||||||
|
// cannot answer our requests, so it is not registered for syncing.
|
||||||
|
// Surface it so an operator can tell a stalled sync from a quiet one.
|
||||||
|
snapPeerSkipMeter.Mark(1)
|
||||||
|
log.Debug("Skipping snap peer below syncer version", "peer", p.ID(), "version", p.Version(), "required", d.snapSyncer.Version())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return d.snapSyncer.Register(p)
|
return d.snapSyncer.Register(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SnapSyncVersion returns the snap protocol version of the active state syncer.
|
||||||
|
// Peers negotiating a lower version cannot serve its requests.
|
||||||
|
func (d *Downloader) SnapSyncVersion() uint {
|
||||||
|
return d.snapSyncer.Version()
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterSnapPeer removes a snap peer from the active state syncer. It mirrors
|
// UnregisterSnapPeer removes a snap peer from the active state syncer. It mirrors
|
||||||
// RegisterSnapPeer's version gate: a peer below the active syncer's version was
|
// RegisterSnapPeer's version gate: a peer below the active syncer's version was
|
||||||
// never registered, so there is nothing to remove.
|
// never registered, so there is nothing to remove.
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,8 @@ var (
|
||||||
receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil)
|
receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil)
|
||||||
|
|
||||||
throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil)
|
throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil)
|
||||||
|
|
||||||
|
// snapPeerSkipMeter tracks snap peers skipped by the state syncer because
|
||||||
|
// they negotiated a version below the one the syncer requires.
|
||||||
|
snapPeerSkipMeter = metrics.NewRegisteredMeter("eth/downloader/snap/peerskip", nil)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -294,11 +294,17 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
|
||||||
}
|
}
|
||||||
reject := false // reserved peer slots
|
reject := false // reserved peer slots
|
||||||
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync {
|
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync {
|
||||||
if snap == nil {
|
// A peer is useful to the active state syncer only if it offers the snap
|
||||||
// If we are running snap-sync, we want to reserve roughly half the peer
|
// extension at (or above) the syncer's version. Non-snap peers AND peers
|
||||||
// slots for peers supporting the snap protocol.
|
// stuck on an older snap version (e.g. snap/1 while we sync via snap/2)
|
||||||
// The logic here is; we only allow up to 5 more non-snap peers than snap-peers.
|
// are both "non-usable": the node still serves them, but they must not be
|
||||||
if all, snp := h.peers.len(), h.peers.snapLen(); all-snp > snp+5 {
|
// allowed to fill the slots reserved for peers that can serve the sync.
|
||||||
|
minVersion := h.downloader.SnapSyncVersion()
|
||||||
|
usable := snap != nil && snap.Version() >= minVersion
|
||||||
|
if !usable {
|
||||||
|
// Reserve roughly half the slots for usable peers: only allow up to 5
|
||||||
|
// more non-usable peers (non-snap or below-version snap) than usable ones.
|
||||||
|
if all, snp := h.peers.len(), h.peers.snapLen(minVersion); all-snp > snp+5 {
|
||||||
reject = true
|
reject = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,7 @@ var (
|
||||||
// peerSet represents the collection of active peers currently participating in
|
// peerSet represents the collection of active peers currently participating in
|
||||||
// the `eth` protocol, with or without the `snap` extension.
|
// the `eth` protocol, with or without the `snap` extension.
|
||||||
type peerSet struct {
|
type peerSet struct {
|
||||||
peers map[string]*ethPeer // Peers connected on the `eth` protocol
|
peers map[string]*ethPeer // Peers connected on the `eth` protocol
|
||||||
snapPeers int // Number of `snap` compatible peers for connection prioritization
|
|
||||||
|
|
||||||
snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension
|
snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension
|
||||||
snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth`
|
snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth`
|
||||||
|
|
@ -161,7 +160,6 @@ func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error {
|
||||||
}
|
}
|
||||||
if ext != nil {
|
if ext != nil {
|
||||||
eth.snapExt = &snapPeer{ext}
|
eth.snapExt = &snapPeer{ext}
|
||||||
ps.snapPeers++
|
|
||||||
}
|
}
|
||||||
ps.peers[id] = eth
|
ps.peers[id] = eth
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -173,14 +171,10 @@ func (ps *peerSet) unregisterPeer(id string) error {
|
||||||
ps.lock.Lock()
|
ps.lock.Lock()
|
||||||
defer ps.lock.Unlock()
|
defer ps.lock.Unlock()
|
||||||
|
|
||||||
peer, ok := ps.peers[id]
|
if _, ok := ps.peers[id]; !ok {
|
||||||
if !ok {
|
|
||||||
return errPeerNotRegistered
|
return errPeerNotRegistered
|
||||||
}
|
}
|
||||||
delete(ps.peers, id)
|
delete(ps.peers, id)
|
||||||
if peer.snapExt != nil {
|
|
||||||
ps.snapPeers--
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,12 +204,21 @@ func (ps *peerSet) len() int {
|
||||||
return len(ps.peers)
|
return len(ps.peers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// snapLen returns if the current number of `snap` peers in the set.
|
// snapLen returns the number of `snap` peers whose negotiated version is at
|
||||||
func (ps *peerSet) snapLen() int {
|
// least minVersion — i.e. peers usable by a state syncer running that version.
|
||||||
|
// Lower-version snap peers (which the node still serves but cannot sync from)
|
||||||
|
// are excluded, so they don't get counted toward the reserved snap-peer slots.
|
||||||
|
func (ps *peerSet) snapLen(minVersion uint) int {
|
||||||
ps.lock.RLock()
|
ps.lock.RLock()
|
||||||
defer ps.lock.RUnlock()
|
defer ps.lock.RUnlock()
|
||||||
|
|
||||||
return ps.snapPeers
|
var n int
|
||||||
|
for _, p := range ps.peers {
|
||||||
|
if p.snapExt != nil && p.snapExt.Version() >= minVersion {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// close disconnects all peers.
|
// close disconnects all peers.
|
||||||
|
|
|
||||||
|
|
@ -861,7 +861,7 @@ func testGetPooledTransaction(t *testing.T, blobTx bool) {
|
||||||
emptyBlob = kzg4844.Blob{}
|
emptyBlob = kzg4844.Blob{}
|
||||||
emptyBlobs = []kzg4844.Blob{emptyBlob}
|
emptyBlobs = []kzg4844.Blob{emptyBlob}
|
||||||
emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob)
|
emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob)
|
||||||
emptyBlobProof, _ = kzg4844.ComputeBlobProof(&emptyBlob, emptyBlobCommit)
|
emptyCellProof, _ = kzg4844.ComputeCellProofs(&emptyBlob)
|
||||||
emptyBlobHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
|
emptyBlobHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
|
||||||
)
|
)
|
||||||
backend := newTestBackendWithGenerator(0, true, true, nil)
|
backend := newTestBackendWithGenerator(0, true, true, nil)
|
||||||
|
|
@ -885,7 +885,7 @@ func testGetPooledTransaction(t *testing.T, blobTx bool) {
|
||||||
To: testAddr,
|
To: testAddr,
|
||||||
BlobHashes: []common.Hash{emptyBlobHash},
|
BlobHashes: []common.Hash{emptyBlobHash},
|
||||||
BlobFeeCap: uint256.MustFromBig(common.Big1),
|
BlobFeeCap: uint256.MustFromBig(common.Big1),
|
||||||
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}),
|
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, emptyCellProof),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ func (s *syncerV2) isStorageFetched(accountHash, storageHash common.Hash) bool {
|
||||||
// applyAccessList applies a single block's access list diffs to the flat state
|
// applyAccessList applies a single block's access list diffs to the flat state
|
||||||
// in the database. For each account, it applies the post-block values (highest
|
// in the database. For each account, it applies the post-block values (highest
|
||||||
// TxIdx entry) for balance, nonce, code, and storage. The storageRoot field is
|
// TxIdx entry) for balance, nonce, code, and storage. The storageRoot field is
|
||||||
// intentionally left stale. It will be recomputed during the trie rebuild.
|
// intentionally left stale. It will be recomputed during the trie generation.
|
||||||
func (s *syncerV2) applyAccessList(b *bal.BlockAccessList, batch ethdb.Batch) error {
|
func (s *syncerV2) applyAccessList(b *bal.BlockAccessList, batch ethdb.Batch) error {
|
||||||
// Iterate over all accounts in the access list
|
// Iterate over all accounts in the access list
|
||||||
for _, access := range *b {
|
for _, access := range *b {
|
||||||
|
|
@ -113,7 +113,7 @@ func (s *syncerV2) applyAccessList(b *bal.BlockAccessList, batch ethdb.Batch) er
|
||||||
rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash)
|
rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash)
|
||||||
} else {
|
} else {
|
||||||
// Store the slot in the same encoding the snapshot and the
|
// Store the slot in the same encoding the snapshot and the
|
||||||
// trie rebuild use: RLP of the minimal big-endian value
|
// trie generation use: RLP of the minimal big-endian value
|
||||||
// (leading zeros trimmed), matching core/state's snapshot
|
// (leading zeros trimmed), matching core/state's snapshot
|
||||||
// writes.
|
// writes.
|
||||||
blob, _ := rlp.EncodeToBytes(value.Bytes())
|
blob, _ := rlp.EncodeToBytes(value.Bytes())
|
||||||
|
|
@ -176,7 +176,7 @@ func (s *syncerV2) applyAccessList(b *bal.BlockAccessList, batch ethdb.Batch) er
|
||||||
case isEmpty && !isNew:
|
case isEmpty && !isNew:
|
||||||
// Existing account got fully drained (e.g., pre-funded
|
// Existing account got fully drained (e.g., pre-funded
|
||||||
// address that gets deployed to with init code that
|
// address that gets deployed to with init code that
|
||||||
// self-destructs). Delete the entry so the trie rebuild
|
// self-destructs). Delete the entry so the trie generation
|
||||||
// doesn't pick it up as an empty leaf.
|
// doesn't pick it up as an empty leaf.
|
||||||
rawdb.DeleteAccountSnapshot(batch, accountHash)
|
rawdb.DeleteAccountSnapshot(batch, accountHash)
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ func TestAccessListApplication(t *testing.T) {
|
||||||
|
|
||||||
// Verify storage updated. Slots are stored in the canonical snapshot
|
// Verify storage updated. Slots are stored in the canonical snapshot
|
||||||
// encoding (RLP of the value with leading zeros trimmed), the same form
|
// encoding (RLP of the value with leading zeros trimmed), the same form
|
||||||
// the download path writes and the trie rebuild consumes.
|
// the download path writes and the trie generation consumes.
|
||||||
storageVal := rawdb.ReadStorageSnapshot(db, accountHash, slotHash)
|
storageVal := rawdb.ReadStorageSnapshot(db, accountHash, slotHash)
|
||||||
wantStorage, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(common.HexToHash("0x02").Bytes()))
|
wantStorage, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(common.HexToHash("0x02").Bytes()))
|
||||||
if !bytes.Equal(storageVal, wantStorage) {
|
if !bytes.Equal(storageVal, wantStorage) {
|
||||||
|
|
@ -325,10 +325,9 @@ func TestAccessListApplicationSkipsUnfetched(t *testing.T) {
|
||||||
// is below Next so isFetched returns true; the unfetched hash equals Next
|
// is below Next so isFetched returns true; the unfetched hash equals Next
|
||||||
// so isFetched returns false.
|
// so isFetched returns false.
|
||||||
syncer.tasks = []*accountTaskV2{{
|
syncer.tasks = []*accountTaskV2{{
|
||||||
Next: unfetchedHash,
|
Next: unfetchedHash,
|
||||||
Last: common.MaxHash,
|
Last: common.MaxHash,
|
||||||
SubTasks: make(map[common.Hash][]*storageTaskV2),
|
SubTasks: make(map[common.Hash][]*storageTaskV2),
|
||||||
stateCompleted: make(map[common.Hash]struct{}),
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
cb := bal.NewConstructionBlockAccessList()
|
cb := bal.NewConstructionBlockAccessList()
|
||||||
|
|
@ -366,10 +365,9 @@ func TestAccessListApplicationSkipsUnfetchedStorage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
syncer.tasks = []*accountTaskV2{{
|
syncer.tasks = []*accountTaskV2{{
|
||||||
Next: unfetchedHash,
|
Next: unfetchedHash,
|
||||||
Last: common.MaxHash,
|
Last: common.MaxHash,
|
||||||
SubTasks: make(map[common.Hash][]*storageTaskV2),
|
SubTasks: make(map[common.Hash][]*storageTaskV2),
|
||||||
stateCompleted: make(map[common.Hash]struct{}),
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// BAL touches an unfetched account with a storage write AND an empty
|
// BAL touches an unfetched account with a storage write AND an empty
|
||||||
|
|
@ -425,7 +423,6 @@ func TestAccessListApplicationPartialStorage(t *testing.T) {
|
||||||
Last: common.MaxHash,
|
Last: common.MaxHash,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
stateCompleted: make(map[common.Hash]struct{}),
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
cb := bal.NewConstructionBlockAccessList()
|
cb := bal.NewConstructionBlockAccessList()
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,10 @@ type Backend interface {
|
||||||
// otherwise only the default (snap/1) versions are offered on the wire.
|
// otherwise only the default (snap/1) versions are offered on the wire.
|
||||||
func MakeProtocols(backend Backend, snapV2 bool) []p2p.Protocol {
|
func MakeProtocols(backend Backend, snapV2 bool) []p2p.Protocol {
|
||||||
versions := ProtocolVersions
|
versions := ProtocolVersions
|
||||||
if snapV2 {
|
if !snapV2 {
|
||||||
versions = append([]uint{SNAP2}, versions...)
|
// snap/2 is not safe to advertise unconditionally yet, so it is gated
|
||||||
|
// behind a feature flag.
|
||||||
|
versions = []uint{SNAP1}
|
||||||
}
|
}
|
||||||
protocols := make([]p2p.Protocol, len(versions))
|
protocols := make([]p2p.Protocol, len(versions))
|
||||||
for i, version := range versions {
|
for i, version := range versions {
|
||||||
|
|
|
||||||
|
|
@ -186,8 +186,8 @@ func TestSyncProgressV1Discarded(t *testing.T) {
|
||||||
syncer := newSyncerV2(db, rawdb.HashScheme)
|
syncer := newSyncerV2(db, rawdb.HashScheme)
|
||||||
syncer.loadSyncStatus()
|
syncer.loadSyncStatus()
|
||||||
|
|
||||||
if syncer.previousPivot != nil {
|
if syncer.pivot != nil {
|
||||||
t.Fatalf("expected previousPivot nil after discarding old format, got %+v", syncer.previousPivot)
|
t.Fatalf("expected pivot nil after discarding old format, got %+v", syncer.pivot)
|
||||||
}
|
}
|
||||||
if len(syncer.tasks) != accountConcurrency {
|
if len(syncer.tasks) != accountConcurrency {
|
||||||
t.Fatalf("expected fresh task split of %d, got %d", accountConcurrency, len(syncer.tasks))
|
t.Fatalf("expected fresh task split of %d, got %d", accountConcurrency, len(syncer.tasks))
|
||||||
|
|
@ -258,8 +258,8 @@ func TestSyncProgressCorruptPayload(t *testing.T) {
|
||||||
syncer := newSyncerV2(db, rawdb.HashScheme)
|
syncer := newSyncerV2(db, rawdb.HashScheme)
|
||||||
syncer.loadSyncStatus()
|
syncer.loadSyncStatus()
|
||||||
|
|
||||||
if syncer.previousPivot != nil {
|
if syncer.pivot != nil {
|
||||||
t.Fatalf("expected previousPivot nil after corrupt payload, got %+v", syncer.previousPivot)
|
t.Fatalf("expected pivot nil after corrupt payload, got %+v", syncer.pivot)
|
||||||
}
|
}
|
||||||
if len(syncer.tasks) != accountConcurrency {
|
if len(syncer.tasks) != accountConcurrency {
|
||||||
t.Fatalf("expected fresh task split of %d, got %d", accountConcurrency, len(syncer.tasks))
|
t.Fatalf("expected fresh task split of %d, got %d", accountConcurrency, len(syncer.tasks))
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,10 @@ const (
|
||||||
// devp2p capability negotiation.
|
// devp2p capability negotiation.
|
||||||
const ProtocolName = "snap"
|
const ProtocolName = "snap"
|
||||||
|
|
||||||
// ProtocolVersions are the supported versions of the `snap` protocol advertised
|
// ProtocolVersions are all the `snap` protocol versions this node implements
|
||||||
// by default (first is primary). snap/2 is not safe to advertise unconditionally
|
// (first is primary). What's actually advertised on the wire is decided by
|
||||||
// yet, so it is gated behind a feature flag and appended in MakeProtocols rather
|
// MakeProtocols, which gates snap/2 behind a feature flag.
|
||||||
// than listed here.
|
var ProtocolVersions = []uint{SNAP2, SNAP1}
|
||||||
var ProtocolVersions = []uint{SNAP1}
|
|
||||||
|
|
||||||
// protocolLengths are the number of implemented messages corresponding to
|
// protocolLengths are the number of implemented messages corresponding to
|
||||||
// different protocol versions. snap/2 adds GetAccessLists/AccessLists (0x08/0x09).
|
// different protocol versions. snap/2 adds GetAccessLists/AccessLists (0x08/0x09).
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ type Progress struct {
|
||||||
BytecodeHealBytes common.StorageSize
|
BytecodeHealBytes common.StorageSize
|
||||||
HealingTrienodes uint64
|
HealingTrienodes uint64
|
||||||
HealingBytecode uint64
|
HealingBytecode uint64
|
||||||
|
|
||||||
|
// snap/2-specific status. Reported by snap/2 only.
|
||||||
|
AccessListSynced uint64 // Block access lists fetched during catch-up
|
||||||
|
AccessListTotal uint64 // Total block access lists to fetch for catch-up
|
||||||
|
TrieGenPercent uint64 // Trie generation completion, in percent (0..100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syncer is the uniform view over the snap/1 (*syncer) and snap/2 (*syncerV2)
|
// Syncer is the uniform view over the snap/1 (*syncer) and snap/2 (*syncerV2)
|
||||||
|
|
@ -58,6 +63,10 @@ type Syncer interface {
|
||||||
OnTrieNodes(peer SyncPeerV2, id uint64, trienodes [][]byte) error
|
OnTrieNodes(peer SyncPeerV2, id uint64, trienodes [][]byte) error
|
||||||
OnAccessLists(peer SyncPeerV2, id uint64, lists rlp.RawList[rlp.RawValue]) error
|
OnAccessLists(peer SyncPeerV2, id uint64, lists rlp.RawList[rlp.RawValue]) error
|
||||||
|
|
||||||
|
// FrozenPivot returns the pivot header the syncer is bound to, or nil if
|
||||||
|
// the pivot may still be chosen and moved freely.
|
||||||
|
FrozenPivot() *types.Header
|
||||||
|
|
||||||
// Version is the snap protocol version this syncer implements.
|
// Version is the snap protocol version this syncer implements.
|
||||||
Version() uint
|
Version() uint
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +131,11 @@ func (syncerV1Adapter) OnAccessLists(SyncPeerV2, uint64, rlp.RawList[rlp.RawValu
|
||||||
// Version is SNAP1
|
// Version is SNAP1
|
||||||
func (syncerV1Adapter) Version() uint { return SNAP1 }
|
func (syncerV1Adapter) Version() uint { return SNAP1 }
|
||||||
|
|
||||||
|
// FrozenPivot is always nil for snap/1: the sync target must keep tracking
|
||||||
|
// the chain head, ensuring the state is available in the network, so the
|
||||||
|
// pivot is never frozen.
|
||||||
|
func (syncerV1Adapter) FrozenPivot() *types.Header { return nil }
|
||||||
|
|
||||||
// syncerV2Adapter adapts the snap/2 *syncerV2 to Syncer. Its peer-facing methods
|
// syncerV2Adapter adapts the snap/2 *syncerV2 to Syncer. Its peer-facing methods
|
||||||
// already take SyncPeerV2 and its Sync already takes a header, so only Progress
|
// already take SyncPeerV2 and its Sync already takes a header, so only Progress
|
||||||
// (different return type) and OnTrieNodes (absent) need wrapping.
|
// (different return type) and OnTrieNodes (absent) need wrapping.
|
||||||
|
|
@ -130,12 +144,15 @@ type syncerV2Adapter struct{ *syncerV2 }
|
||||||
func (s syncerV2Adapter) Progress() Progress {
|
func (s syncerV2Adapter) Progress() Progress {
|
||||||
progress := s.syncerV2.Progress()
|
progress := s.syncerV2.Progress()
|
||||||
return Progress{
|
return Progress{
|
||||||
AccountSynced: progress.AccountSynced,
|
AccountSynced: progress.AccountSynced,
|
||||||
AccountBytes: progress.AccountBytes,
|
AccountBytes: progress.AccountBytes,
|
||||||
BytecodeSynced: progress.BytecodeSynced,
|
BytecodeSynced: progress.BytecodeSynced,
|
||||||
BytecodeBytes: progress.BytecodeBytes,
|
BytecodeBytes: progress.BytecodeBytes,
|
||||||
StorageSynced: progress.StorageSynced,
|
StorageSynced: progress.StorageSynced,
|
||||||
StorageBytes: progress.StorageBytes,
|
StorageBytes: progress.StorageBytes,
|
||||||
|
AccessListSynced: progress.AccessListSynced,
|
||||||
|
AccessListTotal: progress.AccessListTotal,
|
||||||
|
TrieGenPercent: progress.TrieGenPercent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -37,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p/msgrate"
|
"github.com/ethereum/go-ethereum/p2p/msgrate"
|
||||||
|
"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/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
|
|
@ -54,6 +56,22 @@ const (
|
||||||
// the assumption that the gas limit is 60M.
|
// the assumption that the gas limit is 60M.
|
||||||
maxAccessListRequestCount = 28
|
maxAccessListRequestCount = 28
|
||||||
|
|
||||||
|
// maxCatchUpBlocks is the maximum gap (in blocks) that BAL catch-up is
|
||||||
|
// allowed to span. BALs are only retained by peers for a limited window
|
||||||
|
// (roughly two weeks, ~100k blocks at 12s block time). If the pivot has
|
||||||
|
// moved further than this conservative bound, the BALs needed to roll the
|
||||||
|
// flat state forward are likely no longer available, so we discard the
|
||||||
|
// stale progress and restart the sync from scratch rather than attempting
|
||||||
|
// a catch-up that is bound to fail partway through.
|
||||||
|
maxCatchUpBlocks = params.FullImmutabilityThreshold
|
||||||
|
|
||||||
|
// catchUpWindow is the number of blocks BAL catch-up fetches and applies at
|
||||||
|
// a time. The whole gap can span up to maxCatchUpBlocks, so fetching it in
|
||||||
|
// one shot would buffer every block's BAL (~100 KiB each) in memory before
|
||||||
|
// applying any. Processing the gap in bounded windows caps peak memory to a
|
||||||
|
// single window's worth of BALs.
|
||||||
|
catchUpWindow = 512
|
||||||
|
|
||||||
// syncProgressVersion is the version byte prepended to the JSON-encoded
|
// syncProgressVersion is the version byte prepended to the JSON-encoded
|
||||||
// syncProgressV2 when persisted. On load, a mismatching version byte causes
|
// syncProgressV2 when persisted. On load, a mismatching version byte causes
|
||||||
// the persisted progress to be discarded and sync to start fresh.
|
// the persisted progress to be discarded and sync to start fresh.
|
||||||
|
|
@ -68,6 +86,10 @@ const (
|
||||||
// are still hashes left to fetch.
|
// are still hashes left to fetch.
|
||||||
var errAccessListPeersExhausted = errors.New("all peers exhausted for BAL requests")
|
var errAccessListPeersExhausted = errors.New("all peers exhausted for BAL requests")
|
||||||
|
|
||||||
|
// errAccessListUnavailable is returned from the BAL catch-up when some gap
|
||||||
|
// block's access list cannot be retrieved against the current peerset.
|
||||||
|
var errAccessListUnavailable = errors.New("block access lists unavailable")
|
||||||
|
|
||||||
// accountRequestV2 tracks a pending account range request to ensure responses are
|
// accountRequestV2 tracks a pending account range request to ensure responses are
|
||||||
// to actual requests and to validate any security constraints.
|
// to actual requests and to validate any security constraints.
|
||||||
//
|
//
|
||||||
|
|
@ -261,13 +283,34 @@ type storageTaskV2 struct {
|
||||||
done bool // Flag whether the task can be removed
|
done bool // Flag whether the task can be removed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// syncPhase tracks how far a snap/2 sync has progressed for the journaled
|
||||||
|
// pivot. The phases are strictly ordered: each one implies all previous
|
||||||
|
// ones have finished.
|
||||||
|
type syncPhase uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// phaseDownload covers the flat state (account, storage, bytecode)
|
||||||
|
// download. The requests target the pivot root, which remote peers
|
||||||
|
// only serve while it is recent, so the pivot must keep tracking the
|
||||||
|
// chain head (see FrozenPivot).
|
||||||
|
phaseDownload syncPhase = iota
|
||||||
|
|
||||||
|
// phaseGenerate covers the local trie generation after the download
|
||||||
|
// has completed. It targets the exact pivot root it was started with,
|
||||||
|
// so pivot updates are refused from here on.
|
||||||
|
phaseGenerate
|
||||||
|
|
||||||
|
// phaseComplete means the sync ran to completion for the pivot.
|
||||||
|
phaseComplete
|
||||||
|
)
|
||||||
|
|
||||||
// syncProgressV2 is a database entry to allow suspending and resuming a snapshot state
|
// syncProgressV2 is a database entry to allow suspending and resuming a snapshot state
|
||||||
// sync. Opposed to full and fast sync, there is no way to restart a suspended
|
// sync. Opposed to full and fast sync, there is no way to restart a suspended
|
||||||
// snap sync without prior knowledge of the suspension point.
|
// snap sync without prior knowledge of the suspension point.
|
||||||
type syncProgressV2 struct {
|
type syncProgressV2 struct {
|
||||||
Pivot *types.Header // Pivot header being synced (for pivot move and reorg detection)
|
Pivot *types.Header // Pivot header being synced (for pivot move and reorg detection)
|
||||||
Tasks []*accountTaskV2 // The suspended account tasks (contract tasks within)
|
Tasks []*accountTaskV2 // The suspended account tasks (contract tasks within)
|
||||||
Complete bool // True once sync ran to completion for Pivot
|
Phase syncPhase // Phase is how far the sync has progressed for Pivot
|
||||||
|
|
||||||
// Status report during syncing phase
|
// Status report during syncing phase
|
||||||
AccountSynced uint64 // Number of accounts downloaded
|
AccountSynced uint64 // Number of accounts downloaded
|
||||||
|
|
@ -276,6 +319,10 @@ type syncProgressV2 struct {
|
||||||
BytecodeBytes common.StorageSize // Number of bytecode bytes downloaded
|
BytecodeBytes common.StorageSize // Number of bytecode bytes downloaded
|
||||||
StorageSynced uint64 // Number of storage slots downloaded
|
StorageSynced uint64 // Number of storage slots downloaded
|
||||||
StorageBytes common.StorageSize // Number of storage trie bytes persisted to disk
|
StorageBytes common.StorageSize // Number of storage trie bytes persisted to disk
|
||||||
|
|
||||||
|
AccessListSynced uint64 `json:"-"` // Block access lists fetched during catch-up
|
||||||
|
AccessListTotal uint64 `json:"-"` // Total block access lists to fetch for catch-up
|
||||||
|
TrieGenPercent uint64 `json:"-"` // Trie generation completion, in percent (0..100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncPeerV2 abstracts out the methods required for a peer to be synced against
|
// SyncPeerV2 abstracts out the methods required for a peer to be synced against
|
||||||
|
|
@ -313,7 +360,7 @@ type SyncPeerV2 interface {
|
||||||
// syncerV2 is an Ethereum account and storage trie syncer based on the snap
|
// syncerV2 is an Ethereum account and storage trie syncer based on the snap
|
||||||
// protocol. It downloads all accounts, storage slots, and bytecodes from
|
// protocol. It downloads all accounts, storage slots, and bytecodes from
|
||||||
// remote peers as flat state, applies BAL diffs on pivot moves,
|
// remote peers as flat state, applies BAL diffs on pivot moves,
|
||||||
// and triggers a final trie rebuild once flat state is consistent.
|
// and triggers a final trie generation once flat state is consistent.
|
||||||
//
|
//
|
||||||
// Every network request has a variety of failure events:
|
// Every network request has a variety of failure events:
|
||||||
// - The peer disconnects after task assignment, failing to send the request
|
// - The peer disconnects after task assignment, failing to send the request
|
||||||
|
|
@ -322,14 +369,12 @@ type SyncPeerV2 interface {
|
||||||
// - The peer delivers a stale response after a previous timeout
|
// - The peer delivers a stale response after a previous timeout
|
||||||
// - The peer delivers a refusal to serve the requested state
|
// - The peer delivers a refusal to serve the requested state
|
||||||
type syncerV2 struct {
|
type syncerV2 struct {
|
||||||
db ethdb.Database // Database to store the trie nodes into (and dedup)
|
db ethdb.Database // Database to store the trie nodes into (and dedup)
|
||||||
scheme string // Node scheme used in node database
|
scheme string // Node scheme used in node database
|
||||||
|
pivot *types.Header // Current pivot header being synced (lock needed)
|
||||||
pivot *types.Header // Current pivot header being synced (lock needed)
|
phase atomic.Uint32 // Current syncPhase; atomic so phase transitions are visible across goroutines
|
||||||
previousPivot *types.Header // Pivot from previous sync run (for pivot move detection)
|
tasks []*accountTaskV2 // Current account task set being synced
|
||||||
complete bool // Whether the persisted progress was a completed sync
|
update chan struct{} // Notification channel for possible sync progression
|
||||||
tasks []*accountTaskV2 // Current account task set being synced
|
|
||||||
update chan struct{} // Notification channel for possible sync progression
|
|
||||||
|
|
||||||
peers map[string]SyncPeerV2 // Currently active peers to download from
|
peers map[string]SyncPeerV2 // Currently active peers to download from
|
||||||
peerJoin *event.Feed // Event feed to react to peers joining
|
peerJoin *event.Feed // Event feed to react to peers joining
|
||||||
|
|
@ -358,11 +403,17 @@ type syncerV2 struct {
|
||||||
storageSynced uint64 // Number of storage slots downloaded
|
storageSynced uint64 // Number of storage slots downloaded
|
||||||
storageBytes common.StorageSize // Number of storage trie bytes persisted to disk
|
storageBytes common.StorageSize // Number of storage trie bytes persisted to disk
|
||||||
|
|
||||||
|
accessListSynced uint64 // Block access lists fetched so far during catch-up
|
||||||
|
accessListTotal uint64 // Block access lists to fetch for the current catch-up
|
||||||
|
genProgress atomic.Uint64 // The live trie-generation progress
|
||||||
|
|
||||||
extProgress *syncProgressV2 // progress that can be exposed to external caller.
|
extProgress *syncProgressV2 // progress that can be exposed to external caller.
|
||||||
|
|
||||||
startTime time.Time // Time instance when snapshot sync started
|
startTime time.Time // Time instance when snapshot sync started
|
||||||
logTime time.Time // Time instance when status was last reported
|
logTime time.Time // Time instance when status was last reported
|
||||||
|
|
||||||
|
catchUpWindow uint64 // Number of blocks fetched/applied per BAL catch-up window (overridable in tests)
|
||||||
|
|
||||||
pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown
|
pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown
|
||||||
lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, pivot)
|
lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, pivot)
|
||||||
}
|
}
|
||||||
|
|
@ -370,7 +421,7 @@ type syncerV2 struct {
|
||||||
// newSyncerV2 creates a new snapshot syncer to download the Ethereum state over the
|
// newSyncerV2 creates a new snapshot syncer to download the Ethereum state over the
|
||||||
// snap protocol.
|
// snap protocol.
|
||||||
func newSyncerV2(db ethdb.Database, scheme string) *syncerV2 {
|
func newSyncerV2(db ethdb.Database, scheme string) *syncerV2 {
|
||||||
return &syncerV2{
|
s := &syncerV2{
|
||||||
db: db,
|
db: db,
|
||||||
scheme: scheme,
|
scheme: scheme,
|
||||||
|
|
||||||
|
|
@ -391,8 +442,27 @@ func newSyncerV2(db ethdb.Database, scheme string) *syncerV2 {
|
||||||
bytecodeReqs: make(map[uint64]*bytecodeRequestV2),
|
bytecodeReqs: make(map[uint64]*bytecodeRequestV2),
|
||||||
accessListReqs: make(map[uint64]*accessListRequest),
|
accessListReqs: make(map[uint64]*accessListRequest),
|
||||||
|
|
||||||
extProgress: new(syncProgressV2),
|
extProgress: new(syncProgressV2),
|
||||||
|
catchUpWindow: catchUpWindow,
|
||||||
}
|
}
|
||||||
|
if raw := rawdb.ReadSnapshotSyncStatus(db); len(raw) > 0 && raw[0] == syncProgressVersion {
|
||||||
|
var progress syncProgressV2
|
||||||
|
if err := json.Unmarshal(raw[1:], &progress); err == nil {
|
||||||
|
s.pivot = progress.Pivot
|
||||||
|
s.setPhase(progress.Phase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPhase returns the current sync phase.
|
||||||
|
func (s *syncerV2) getPhase() syncPhase {
|
||||||
|
return syncPhase(s.phase.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPhase moves the sync to the given phase.
|
||||||
|
func (s *syncerV2) setPhase(phase syncPhase) {
|
||||||
|
s.phase.Store(uint32(phase))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register injects a new data source into the syncer's peerset.
|
// Register injects a new data source into the syncer's peerset.
|
||||||
|
|
@ -452,19 +522,17 @@ func (s *syncerV2) Unregister(id string) error {
|
||||||
// Sync starts (or resumes a previous) sync cycle to iterate over a state trie
|
// Sync starts (or resumes a previous) sync cycle to iterate over a state trie
|
||||||
// with the given pivot header and reconstruct the nodes based on the snapshot
|
// with the given pivot header and reconstruct the nodes based on the snapshot
|
||||||
// leaves.
|
// leaves.
|
||||||
func (s *syncerV2) Sync(pivot *types.Header, cancel chan struct{}) error {
|
func (s *syncerV2) Sync(target *types.Header, cancel chan struct{}) error {
|
||||||
if pivot == nil {
|
if target == nil {
|
||||||
return errors.New("snap sync: pivot header is nil")
|
return errors.New("snap sync: pivot header is nil")
|
||||||
}
|
}
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
s.pivot = pivot
|
|
||||||
s.previousPivot = nil // loadSyncStatus overwrites when resuming from persisted progress
|
|
||||||
s.statelessPeers = make(map[string]struct{})
|
s.statelessPeers = make(map[string]struct{})
|
||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
if s.startTime.IsZero() {
|
if s.startTime.IsZero() {
|
||||||
s.startTime = time.Now()
|
s.startTime = time.Now()
|
||||||
}
|
}
|
||||||
root := pivot.Root
|
root := target.Root
|
||||||
|
|
||||||
// Retrieve the previous sync status from DB. If there's no persisted
|
// Retrieve the previous sync status from DB. If there's no persisted
|
||||||
// status, sync is either fresh or already complete.
|
// status, sync is either fresh or already complete.
|
||||||
|
|
@ -473,20 +541,24 @@ func (s *syncerV2) Sync(pivot *types.Header, cancel chan struct{}) error {
|
||||||
// isPivotChanged is true when we have prior progress against a different
|
// isPivotChanged is true when we have prior progress against a different
|
||||||
// pivot. That means we need to roll forward via catchUp, or wipe and
|
// pivot. That means we need to roll forward via catchUp, or wipe and
|
||||||
// restart if the prior pivot was reorged out.
|
// restart if the prior pivot was reorged out.
|
||||||
isPivotChanged := s.previousPivot != nil && s.previousPivot.Hash() != s.pivot.Hash()
|
s.lock.RLock()
|
||||||
|
prevPivot := s.pivot
|
||||||
|
s.lock.RUnlock()
|
||||||
|
isPivotChanged := prevPivot != nil && prevPivot.Hash() != target.Hash()
|
||||||
|
|
||||||
// Skip if we've already finished syncing this pivot.
|
// Skip if we've already finished syncing this pivot.
|
||||||
if !isPivotChanged && s.complete {
|
if !isPivotChanged && s.getPhase() == phaseComplete {
|
||||||
log.Info("Snap sync already complete for this pivot", "root", root)
|
log.Info("Snap sync already complete for this pivot", "root", root)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're committing to running this sync. Clear the complete flag so a
|
// We're committing to running this sync. Demote a completed phase so a
|
||||||
// mid-run save (on cancel or error) doesn't persist a stale Complete=true
|
// mid-run save (on cancel or error) doesn't persist a stale complete
|
||||||
// status from a prior pivot.
|
// status from a prior pivot. The download remains done, only the trie
|
||||||
s.lock.Lock()
|
// generation must be redone against the new pivot.
|
||||||
s.complete = false
|
if s.getPhase() == phaseComplete {
|
||||||
s.lock.Unlock()
|
s.setPhase(phaseGenerate)
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Whether sync completed or not, disregard any future packets
|
// Whether sync completed or not, disregard any future packets
|
||||||
|
|
@ -511,28 +583,53 @@ func (s *syncerV2) Sync(pivot *types.Header, cancel chan struct{}) error {
|
||||||
|
|
||||||
log.Debug("Starting snapshot sync cycle", "root", root)
|
log.Debug("Starting snapshot sync cycle", "root", root)
|
||||||
|
|
||||||
// If we resumed against a different pivot, decide whether the persisted
|
if !isPivotChanged {
|
||||||
// progress is still usable. If yes, roll forward via BAL catch-up. If not,
|
if prevPivot != nil {
|
||||||
// wipe everything and restart fresh.
|
// Resumed against the same pivot. An unclean shutdown may have left
|
||||||
if isPivotChanged {
|
// flushed snapshot data the journal doesn't cover.
|
||||||
if isPivotReorged(s.db, s.previousPivot, s.pivot) {
|
if err := s.pruneStaleState(); err != nil {
|
||||||
log.Warn("Persisted progress unusable, restarting snap sync from scratch",
|
log.Warn("Persisted progress unusable, restarting snap sync from scratch", "err", err)
|
||||||
"number", s.previousPivot.Number, "oldHash", s.previousPivot.Hash())
|
s.resetSyncState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we resumed against a different pivot, decide whether the persisted
|
||||||
|
// progress is still usable. If yes, roll forward via BAL catch-up. If not,
|
||||||
|
// wipe everything and restart fresh.
|
||||||
|
switch {
|
||||||
|
case isPivotReorged(s.db, prevPivot, target):
|
||||||
|
log.Warn("Restarting snap sync from scratch", "oldnumber", prevPivot.Number, "oldHash", prevPivot.Hash())
|
||||||
s.resetSyncState()
|
s.resetSyncState()
|
||||||
} else if err := s.catchUp(cancel); err != nil {
|
case catchUpExceedsRetention(prevPivot, target):
|
||||||
return err
|
// The pivot moved further than the BAL retention window. The access
|
||||||
|
// lists required for catch-up are almost certainly unavailable from
|
||||||
|
// peers, so discard the stale progress and resync from scratch
|
||||||
|
// instead of starting a catch-up doomed to stall.
|
||||||
|
log.Warn("Catch-up gap exceeds BAL retention, restarting snap sync from scratch", "oldnumber", prevPivot.Number, "newnumber", target.Number, "gap", new(big.Int).Sub(target.Number, prevPivot.Number), "limit", maxCatchUpBlocks)
|
||||||
|
s.resetSyncState()
|
||||||
|
default:
|
||||||
|
// An unclean shutdown may have left flushed snapshot data the journal
|
||||||
|
// doesn't cover.
|
||||||
|
if err := s.pruneStaleState(); err != nil {
|
||||||
|
log.Warn("Persisted progress unusable, restarting snap sync from scratch", "err", err)
|
||||||
|
s.resetSyncState()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// A canonical pivot move past a frozen pivot should be impossible:
|
||||||
|
// the downloader both refuses moves (FrozenPivot) and resumes new
|
||||||
|
// cycles against the frozen header itself. Reaching this branch
|
||||||
|
// frozen indicates a bug on the downloader side; roll the flat
|
||||||
|
// state forward defensively and regenerate.
|
||||||
|
if s.getPhase() >= phaseGenerate {
|
||||||
|
log.Warn("Frozen pivot moved unexpectedly, rolling forward", "frozen", prevPivot.Number, "new", target.Number)
|
||||||
|
}
|
||||||
|
if err := s.catchUp(target, cancel); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pin previousPivot to the current pivot before downloadState runs.
|
|
||||||
// This is what saveSyncStatus persists. If the download is interrupted
|
|
||||||
// and the next Sync gets a different pivot, this is how isPivotReorged
|
|
||||||
// recognizes the partial flat state belongs to the old pivot. Without
|
|
||||||
// it, isPivotReorged sees nil, skips the reorg branch, and downloadState
|
|
||||||
// would resume from the persisted task markers but mix the old pivot's
|
|
||||||
// already-downloaded accounts with the new pivot's data.
|
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
s.previousPivot = s.pivot
|
s.pivot = target
|
||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
|
|
||||||
log.Info("Starting state download", "root", root)
|
log.Info("Starting state download", "root", root)
|
||||||
|
|
@ -541,21 +638,48 @@ func (s *syncerV2) Sync(pivot *types.Header, cancel chan struct{}) error {
|
||||||
}
|
}
|
||||||
log.Info("State download complete", "root", root)
|
log.Info("State download complete", "root", root)
|
||||||
|
|
||||||
|
// Entering the generation phase makes the downloader stop moving the
|
||||||
|
// pivot (see FrozenPivot) until the pivot block is committed. The phase
|
||||||
|
// is persisted right away so the freeze also holds across a restart,
|
||||||
|
// before the generation has had a chance to finish.
|
||||||
|
if s.getPhase() < phaseGenerate {
|
||||||
|
s.setPhase(phaseGenerate)
|
||||||
|
s.saveSyncStatus()
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("Starting trie generation", "root", root)
|
log.Info("Starting trie generation", "root", root)
|
||||||
if _, err := triedb.GenerateTrie(s.db, s.scheme, root, cancel); err != nil {
|
batch := s.db.NewBatch()
|
||||||
|
s.resetTrienodes(batch)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_, genErr := triedb.GenerateTrieWithProgress(s.db, s.scheme, root, cancel, &s.genProgress)
|
||||||
|
if genErr != nil {
|
||||||
|
return genErr
|
||||||
|
}
|
||||||
log.Info("Trie generation complete", "root", root)
|
log.Info("Trie generation complete", "root", root)
|
||||||
|
|
||||||
// Mark sync complete. The deferred saveSyncStatus persists this with
|
// Mark sync complete. The deferred saveSyncStatus persists this so a
|
||||||
// Complete=true so a follow-up Sync call for the same pivot can skip
|
// follow-up Sync call for the same pivot can skip the work entirely.
|
||||||
// the work entirely.
|
s.setPhase(phaseComplete)
|
||||||
s.lock.Lock()
|
|
||||||
s.complete = true
|
|
||||||
s.lock.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FrozenPivot returns the pivot header the sync is bound to, or nil while
|
||||||
|
// the pivot may still move freely. The pivot freezes once the state
|
||||||
|
// download completes. The remaining work (trie generation) and the pivot
|
||||||
|
// commit is purely local and targets the exact pivot root the download
|
||||||
|
// finished with, so from that point on the downloader must neither move the
|
||||||
|
// pivot nor start a new cycle against a different one.
|
||||||
|
func (s *syncerV2) FrozenPivot() *types.Header {
|
||||||
|
if s.getPhase() < phaseGenerate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.lock.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
return s.pivot
|
||||||
|
}
|
||||||
|
|
||||||
// download runs the bulk flat-state download. It fetches
|
// download runs the bulk flat-state download. It fetches
|
||||||
// account ranges, storage slots, and bytecodes, writing flat state to disk.
|
// account ranges, storage slots, and bytecodes, writing flat state to disk.
|
||||||
func (s *syncerV2) downloadState(cancel chan struct{}) error {
|
func (s *syncerV2) downloadState(cancel chan struct{}) error {
|
||||||
|
|
@ -575,6 +699,7 @@ func (s *syncerV2) downloadState(cancel chan struct{}) error {
|
||||||
accountResps = make(chan *accountResponseV2)
|
accountResps = make(chan *accountResponseV2)
|
||||||
storageResps = make(chan *storageResponseV2)
|
storageResps = make(chan *storageResponseV2)
|
||||||
bytecodeResps = make(chan *bytecodeResponseV2)
|
bytecodeResps = make(chan *bytecodeResponseV2)
|
||||||
|
lastJournal = time.Now()
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
// Remove all completed tasks and terminate if everything's done
|
// Remove all completed tasks and terminate if everything's done
|
||||||
|
|
@ -583,7 +708,15 @@ func (s *syncerV2) downloadState(cancel chan struct{}) error {
|
||||||
if len(s.tasks) == 0 {
|
if len(s.tasks) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Periodically persist the progress journal. The flat state batches
|
||||||
|
// are flushed continuously, while the journal otherwise only hits
|
||||||
|
// disk on graceful teardown; everything written beyond the journaled
|
||||||
|
// markers is sacrificed on an unclean shutdown, so the save interval
|
||||||
|
// bounds the loss.
|
||||||
|
if time.Since(lastJournal) > time.Minute {
|
||||||
|
lastJournal = time.Now()
|
||||||
|
s.saveSyncStatus()
|
||||||
|
}
|
||||||
// Assign all the data retrieval tasks to any free peers
|
// Assign all the data retrieval tasks to any free peers
|
||||||
s.assignAccountTasks(accountResps, accountReqFails, cancel)
|
s.assignAccountTasks(accountResps, accountReqFails, cancel)
|
||||||
s.assignBytecodeTasks(bytecodeResps, bytecodeReqFails, cancel)
|
s.assignBytecodeTasks(bytecodeResps, bytecodeReqFails, cancel)
|
||||||
|
|
@ -591,15 +724,9 @@ func (s *syncerV2) downloadState(cancel chan struct{}) error {
|
||||||
|
|
||||||
// Update sync progress
|
// Update sync progress
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
s.extProgress = &syncProgressV2{
|
s.refreshProgressLocked()
|
||||||
AccountSynced: s.accountSynced,
|
|
||||||
AccountBytes: s.accountBytes,
|
|
||||||
BytecodeSynced: s.bytecodeSynced,
|
|
||||||
BytecodeBytes: s.bytecodeBytes,
|
|
||||||
StorageSynced: s.storageSynced,
|
|
||||||
StorageBytes: s.storageBytes,
|
|
||||||
}
|
|
||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
|
|
||||||
// Wait for something to happen
|
// Wait for something to happen
|
||||||
select {
|
select {
|
||||||
case <-s.update:
|
case <-s.update:
|
||||||
|
|
@ -657,79 +784,102 @@ func isPivotReorged(db ethdb.Database, prev, curr *types.Header) bool {
|
||||||
return canonical != prev.Hash()
|
return canonical != prev.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// catchUpExceedsRetention reports whether rolling the flat state forward from
|
||||||
|
// prev to curr would span more blocks than peers are expected to retain BALs
|
||||||
|
// for. Beyond this bound the access lists needed for catch-up are likely gone,
|
||||||
|
// so the caller should wipe and resync from scratch instead.
|
||||||
|
func catchUpExceedsRetention(prev, curr *types.Header) bool {
|
||||||
|
gap := new(big.Int).Sub(curr.Number, prev.Number)
|
||||||
|
return gap.Cmp(big.NewInt(maxCatchUpBlocks)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// catchUp runs the BAL catch-up. When the pivot has moved, it fetches BALs
|
// catchUp runs the BAL catch-up. When the pivot has moved, it fetches BALs
|
||||||
// for the gap blocks, verifies them against block headers, and applies the
|
// for the gap blocks, verifies them against block headers, and applies the
|
||||||
// diffs to roll flat state forward.
|
// diffs to roll flat state forward.
|
||||||
func (s *syncerV2) catchUp(cancel chan struct{}) error {
|
func (s *syncerV2) catchUp(target *types.Header, cancel chan struct{}) error {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
from := s.previousPivot.Number.Uint64() + 1
|
from := s.pivot.Number.Uint64() + 1
|
||||||
to := s.pivot.Number.Uint64()
|
to := target.Number.Uint64()
|
||||||
s.lock.RUnlock()
|
s.lock.RUnlock()
|
||||||
log.Info("Starting BAL catch-up", "from", from, "to", to, "blocks", to-from+1)
|
log.Info("Starting BAL catch-up", "from", from, "to", to, "blocks", to-from+1)
|
||||||
|
|
||||||
// Collect block hashes and headers for the gap range.
|
s.lock.Lock()
|
||||||
var (
|
s.accessListTotal = to - from + 1
|
||||||
hashes = make([]common.Hash, 0, to-from+1)
|
s.accessListSynced = 0
|
||||||
headers = make(map[common.Hash]*types.Header, to-from+1)
|
s.refreshProgressLocked()
|
||||||
)
|
s.lock.Unlock()
|
||||||
for num := from; num <= to; num++ {
|
|
||||||
hash := rawdb.ReadCanonicalHash(s.db, num)
|
|
||||||
if hash == (common.Hash{}) {
|
|
||||||
return fmt.Errorf("missing canonical hash for block %d during catch-up", num)
|
|
||||||
}
|
|
||||||
header := rawdb.ReadHeader(s.db, hash, num)
|
|
||||||
if header == nil {
|
|
||||||
return fmt.Errorf("missing header for block %d (hash %v) during catch-up", num, hash)
|
|
||||||
}
|
|
||||||
hashes = append(hashes, hash)
|
|
||||||
headers[hash] = header
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch BALs from peers
|
for start := from; start <= to; start += s.catchUpWindow {
|
||||||
rawBALs, err := s.fetchAccessLists(hashes, headers, cancel)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply each BAL in block order. BALs are already verified by fetchAccessLists.
|
|
||||||
for i, raw := range rawBALs {
|
|
||||||
select {
|
select {
|
||||||
case <-cancel:
|
case <-cancel:
|
||||||
return ErrCancelled
|
return ErrCancelled
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
num := from + uint64(i)
|
end := start + s.catchUpWindow - 1
|
||||||
hash := hashes[i]
|
if end > to {
|
||||||
|
end = to
|
||||||
// Decode the raw RLP into a BAL.
|
}
|
||||||
|
// Collect block hashes and headers for this window.
|
||||||
var (
|
var (
|
||||||
b bal.BlockAccessList
|
hashes = make([]common.Hash, 0, end-start+1)
|
||||||
batch = s.db.NewBatch()
|
headers = make(map[common.Hash]*types.Header, end-start+1)
|
||||||
)
|
)
|
||||||
if err := rlp.DecodeBytes(raw, &b); err != nil {
|
for num := start; num <= end; num++ {
|
||||||
return fmt.Errorf("failed to decode BAL for block %d: %v", num, err)
|
hash := rawdb.ReadCanonicalHash(s.db, num)
|
||||||
|
if hash == (common.Hash{}) {
|
||||||
|
return fmt.Errorf("missing canonical hash for block %d during catch-up", num)
|
||||||
|
}
|
||||||
|
header := rawdb.ReadHeader(s.db, hash, num)
|
||||||
|
if header == nil {
|
||||||
|
return fmt.Errorf("missing header for block %d (hash %v) during catch-up", num, hash)
|
||||||
|
}
|
||||||
|
hashes = append(hashes, hash)
|
||||||
|
headers[hash] = header
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyAccessList failures are persistent. If a block's apply fails
|
// Fetch this window's BALs from peers.
|
||||||
// here, the next Sync will resume from this block and hit the same
|
rawBALs, err := s.fetchAccessLists(hashes, headers, cancel)
|
||||||
// failure. Auto-recovery isn't implemented yet.
|
if err != nil {
|
||||||
if err := s.applyAccessList(&b, batch); err != nil {
|
|
||||||
return fmt.Errorf("BAL application failed for block %d: %v", num, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persist incremental progress so a crash mid-catchUp can resume
|
|
||||||
// from the next unapplied block.
|
|
||||||
s.lock.Lock()
|
|
||||||
s.previousPivot = headers[hash]
|
|
||||||
s.lock.Unlock()
|
|
||||||
s.saveSyncStatusWithDB(batch)
|
|
||||||
|
|
||||||
// Commit the state transition alongside the sync progress atomically.
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply each BAL in block order. BALs are already verified by fetchAccessLists.
|
||||||
|
for i, raw := range rawBALs {
|
||||||
|
select {
|
||||||
|
case <-cancel:
|
||||||
|
return ErrCancelled
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
num := start + uint64(i)
|
||||||
|
hash := hashes[i]
|
||||||
|
|
||||||
|
// Decode the raw RLP into a BAL.
|
||||||
|
var (
|
||||||
|
b bal.BlockAccessList
|
||||||
|
batch = s.db.NewBatch()
|
||||||
|
)
|
||||||
|
if err := rlp.DecodeBytes(raw, &b); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode BAL for block %d: %v", num, err)
|
||||||
|
}
|
||||||
|
if err := s.applyAccessList(&b, batch); err != nil {
|
||||||
|
return fmt.Errorf("BAL application failed for block %d: %v", num, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist incremental progress so a crash mid-catchUp can resume
|
||||||
|
// from the next unapplied block.
|
||||||
|
s.lock.Lock()
|
||||||
|
s.pivot = headers[hash]
|
||||||
|
s.lock.Unlock()
|
||||||
|
s.saveSyncStatusWithDB(batch)
|
||||||
|
|
||||||
|
// Commit the state transition alongside the sync progress atomically.
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("BAL catch-up progress", "applied", end, "target", to, "remaining", to-end)
|
||||||
}
|
}
|
||||||
log.Info("BAL catch-up complete", "blocks", len(rawBALs))
|
log.Info("BAL catch-up complete", "from", from, "to", to)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -756,25 +906,22 @@ func (s *syncerV2) fetchAccessLists(hashes []common.Hash, headers map[common.Has
|
||||||
}
|
}
|
||||||
fetched := make(map[common.Hash]rlp.RawValue, len(hashes))
|
fetched := make(map[common.Hash]rlp.RawValue, len(hashes))
|
||||||
|
|
||||||
|
// refused tracks the mapping between BAL and peerset which doesn't have
|
||||||
|
// it available.
|
||||||
|
refused := make(map[common.Hash]map[string]struct{})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accessListReqFails = make(chan *accessListRequest)
|
accessListReqFails = make(chan *accessListRequest)
|
||||||
accessListResps = make(chan *accessListResponse)
|
accessListResps = make(chan *accessListResponse)
|
||||||
lastStallLog = time.Now()
|
lastStallLog = time.Now()
|
||||||
)
|
)
|
||||||
for len(fetched) < len(hashes) {
|
for len(fetched) < len(hashes) {
|
||||||
// Assign BAL retrieval tasks to idle peers
|
if err := s.checkAccessListProgress(pending, refused); err != nil {
|
||||||
s.assignAccessListTasks(pending, accessListResps, accessListReqFails, cancel)
|
log.Warn("BAL fetch cannot progress", "err", err, "fetched", len(fetched), "remaining", len(pending))
|
||||||
|
return nil, err
|
||||||
// If every peer is now stateless and nothing is in flight, no event
|
|
||||||
// short of cancel or a new peer joining can move us forward. Surface
|
|
||||||
// this so the caller can return and let a higher-level retry happen
|
|
||||||
// against a fresh peer set.
|
|
||||||
//
|
|
||||||
// TODO(rjl, jonny) add a time allowance before returning the error.
|
|
||||||
if s.accessListPeersExhausted() {
|
|
||||||
log.Warn("BAL peers exhausted, stopping catch-up early", "fetched", len(fetched), "remaining", len(pending))
|
|
||||||
return nil, errAccessListPeersExhausted
|
|
||||||
}
|
}
|
||||||
|
// Assign BAL retrieval tasks to idle peers
|
||||||
|
s.assignAccessListTasks(pending, refused, accessListResps, accessListReqFails, cancel)
|
||||||
|
|
||||||
// Periodic visibility while stalled with peers connected but idle.
|
// Periodic visibility while stalled with peers connected but idle.
|
||||||
if len(pending) > 0 && time.Since(lastStallLog) > 30*time.Second {
|
if len(pending) > 0 && time.Since(lastStallLog) > 30*time.Second {
|
||||||
|
|
@ -790,13 +937,23 @@ func (s *syncerV2) fetchAccessLists(hashes []common.Hash, headers map[common.Has
|
||||||
// A new peer joined, try to assign it work
|
// A new peer joined, try to assign it work
|
||||||
case id := <-peerDrop:
|
case id := <-peerDrop:
|
||||||
s.revertBALRequests(id, pending)
|
s.revertBALRequests(id, pending)
|
||||||
|
for h, set := range refused {
|
||||||
|
delete(set, id)
|
||||||
|
if len(set) == 0 {
|
||||||
|
delete(refused, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
case <-cancel:
|
case <-cancel:
|
||||||
return nil, ErrCancelled
|
return nil, ErrCancelled
|
||||||
case req := <-accessListReqFails:
|
case req := <-accessListReqFails:
|
||||||
s.revertAccessListRequest(req, pending)
|
s.revertAccessListRequest(req, pending)
|
||||||
case res := <-accessListResps:
|
case res := <-accessListResps:
|
||||||
s.processAccessListResponse(res, headers, pending, fetched)
|
s.processAccessListResponse(res, headers, pending, fetched, refused)
|
||||||
}
|
}
|
||||||
|
s.lock.Lock()
|
||||||
|
s.accessListSynced += uint64(len(fetched))
|
||||||
|
s.refreshProgressLocked()
|
||||||
|
s.lock.Unlock()
|
||||||
}
|
}
|
||||||
// Assemble results in input order
|
// Assemble results in input order
|
||||||
results := make([]rlp.RawValue, len(hashes))
|
results := make([]rlp.RawValue, len(hashes))
|
||||||
|
|
@ -807,8 +964,9 @@ func (s *syncerV2) fetchAccessLists(hashes []common.Hash, headers map[common.Has
|
||||||
}
|
}
|
||||||
|
|
||||||
// assignAccessListTasks attempts to assign BAL fetch requests to idle
|
// assignAccessListTasks attempts to assign BAL fetch requests to idle
|
||||||
// peers for any hashes still in pending.
|
// peers for any hashes still in pending. Hashes a peer has already refused
|
||||||
func (s *syncerV2) assignAccessListTasks(pending map[common.Hash]struct{}, success chan *accessListResponse, fail chan *accessListRequest, cancel chan struct{}) {
|
// (recorded in refused) are not assigned back to that same peer.
|
||||||
|
func (s *syncerV2) assignAccessListTasks(pending map[common.Hash]struct{}, refused map[common.Hash]map[string]struct{}, success chan *accessListResponse, fail chan *accessListRequest, cancel chan struct{}) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -822,6 +980,33 @@ func (s *syncerV2) assignAccessListTasks(pending map[common.Hash]struct{}, succe
|
||||||
)
|
)
|
||||||
idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
|
idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
|
||||||
|
|
||||||
|
// Collect hashes to fetch, capped by peer capacity and the
|
||||||
|
// EIP-8189 2 MiB response soft limit (~72 KiB/BAL -> 28 blocks).
|
||||||
|
if cap > maxAccessListRequestCount {
|
||||||
|
cap = maxAccessListRequestCount
|
||||||
|
}
|
||||||
|
batch := make([]common.Hash, 0, cap)
|
||||||
|
for h := range pending {
|
||||||
|
// Skip hashes this peer has already refused; another peer
|
||||||
|
// must serve them.
|
||||||
|
if set := refused[h]; set != nil {
|
||||||
|
if _, ok := set[idle]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(pending, h)
|
||||||
|
|
||||||
|
batch = append(batch, h)
|
||||||
|
if len(batch) >= cap {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The peer has already refused every pending hash; leave them in
|
||||||
|
// pending for another peer and move on without a wasted request.
|
||||||
|
if len(batch) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a unique request ID
|
// Generate a unique request ID
|
||||||
var reqid uint64
|
var reqid uint64
|
||||||
for {
|
for {
|
||||||
|
|
@ -834,21 +1019,6 @@ func (s *syncerV2) assignAccessListTasks(pending map[common.Hash]struct{}, succe
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect hashes to fetch, capped by peer capacity and the
|
|
||||||
// EIP-8189 2 MiB response soft limit (~72 KiB/BAL -> 28 blocks).
|
|
||||||
if cap > maxAccessListRequestCount {
|
|
||||||
cap = maxAccessListRequestCount
|
|
||||||
}
|
|
||||||
batch := make([]common.Hash, 0, cap)
|
|
||||||
for h := range pending {
|
|
||||||
delete(pending, h)
|
|
||||||
|
|
||||||
batch = append(batch, h)
|
|
||||||
if len(batch) >= cap {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req := &accessListRequest{
|
req := &accessListRequest{
|
||||||
peer: idle,
|
peer: idle,
|
||||||
id: reqid,
|
id: reqid,
|
||||||
|
|
@ -883,7 +1053,7 @@ func (s *syncerV2) assignAccessListTasks(pending map[common.Hash]struct{}, succe
|
||||||
// processAccessListResponse handles a successful BAL response. It
|
// processAccessListResponse handles a successful BAL response. It
|
||||||
// verifies each non-empty BAL against the corresponding block header and
|
// verifies each non-empty BAL against the corresponding block header and
|
||||||
// stores the verified ones in fetched.
|
// stores the verified ones in fetched.
|
||||||
func (s *syncerV2) processAccessListResponse(res *accessListResponse, headers map[common.Hash]*types.Header, pending map[common.Hash]struct{}, fetched map[common.Hash]rlp.RawValue) {
|
func (s *syncerV2) processAccessListResponse(res *accessListResponse, headers map[common.Hash]*types.Header, pending map[common.Hash]struct{}, fetched map[common.Hash]rlp.RawValue, refused map[common.Hash]map[string]struct{}) {
|
||||||
var (
|
var (
|
||||||
stateless bool
|
stateless bool
|
||||||
valid = make(map[common.Hash]rlp.RawValue)
|
valid = make(map[common.Hash]rlp.RawValue)
|
||||||
|
|
@ -892,8 +1062,14 @@ func (s *syncerV2) processAccessListResponse(res *accessListResponse, headers ma
|
||||||
for i, raw := range res.accessLists {
|
for i, raw := range res.accessLists {
|
||||||
h := res.req.hashes[i]
|
h := res.req.hashes[i]
|
||||||
|
|
||||||
// Peer doesn't have this BAL. Add it back to pending for retry.
|
// Peer doesn't have this BAL (a legitimate reply, e.g. the block is
|
||||||
|
// outside its retention window). Record the refusal and add the hash
|
||||||
|
// back to pending for a retry against other peers.
|
||||||
if bytes.Equal(raw, rlp.EmptyString) {
|
if bytes.Equal(raw, rlp.EmptyString) {
|
||||||
|
if refused[h] == nil {
|
||||||
|
refused[h] = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
refused[h][res.req.peer] = struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var b bal.BlockAccessList
|
var b bal.BlockAccessList
|
||||||
|
|
@ -917,6 +1093,7 @@ func (s *syncerV2) processAccessListResponse(res *accessListResponse, headers ma
|
||||||
// Re-add hashes that were not served back or invalid to pending
|
// Re-add hashes that were not served back or invalid to pending
|
||||||
for i := 0; i < len(res.req.hashes); i++ {
|
for i := 0; i < len(res.req.hashes); i++ {
|
||||||
if _, ok := valid[res.req.hashes[i]]; ok {
|
if _, ok := valid[res.req.hashes[i]]; ok {
|
||||||
|
delete(refused, res.req.hashes[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pending[res.req.hashes[i]] = struct{}{}
|
pending[res.req.hashes[i]] = struct{}{}
|
||||||
|
|
@ -952,14 +1129,19 @@ func (s *syncerV2) loadSyncStatus() {
|
||||||
}
|
}
|
||||||
task.StorageCompleted = nil
|
task.StorageCompleted = nil
|
||||||
}
|
}
|
||||||
s.previousPivot = progress.Pivot
|
s.pivot = progress.Pivot
|
||||||
s.complete = progress.Complete
|
s.setPhase(progress.Phase)
|
||||||
s.accountSynced = progress.AccountSynced
|
s.accountSynced = progress.AccountSynced
|
||||||
s.accountBytes = progress.AccountBytes
|
s.accountBytes = progress.AccountBytes
|
||||||
s.bytecodeSynced = progress.BytecodeSynced
|
s.bytecodeSynced = progress.BytecodeSynced
|
||||||
s.bytecodeBytes = progress.BytecodeBytes
|
s.bytecodeBytes = progress.BytecodeBytes
|
||||||
s.storageSynced = progress.StorageSynced
|
s.storageSynced = progress.StorageSynced
|
||||||
s.storageBytes = progress.StorageBytes
|
s.storageBytes = progress.StorageBytes
|
||||||
|
|
||||||
|
// Seed the externally-exposed snapshot from the restored counters so
|
||||||
|
// eth_syncing reports real stats during catch-up and trie generation
|
||||||
|
// after a resume, instead of the zero-valued initial snapshot.
|
||||||
|
s.refreshProgressLocked()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -979,13 +1161,20 @@ func increaseKey(key []byte) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteHistoryByRange completely removes all database entries with the specific
|
// deleteRange completely removes all database entries with the specific
|
||||||
// prefix. Note, this method assumes the space with the given prefix is exclusively
|
// prefix. Note, this method assumes the space with the given prefix is
|
||||||
// occupied!
|
// exclusively occupied!
|
||||||
func deleteRange(batch ethdb.Batch, prefix []byte) {
|
func deleteRange(batch ethdb.Batch, prefix []byte) {
|
||||||
start := prefix
|
deleteKeyRange(batch, prefix, increaseKey(bytes.Clone(prefix)))
|
||||||
limit := increaseKey(bytes.Clone(prefix))
|
}
|
||||||
|
|
||||||
|
// deleteKeyRange removes all database entries within the key range [start,
|
||||||
|
// limit). Note, this method assumes the range is exclusively occupied by
|
||||||
|
// sync data!
|
||||||
|
func deleteKeyRange(batch ethdb.Batch, start, limit []byte) {
|
||||||
|
if limit != nil && bytes.Compare(start, limit) >= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Try to remove the data in the range by a loop, as the leveldb
|
// Try to remove the data in the range by a loop, as the leveldb
|
||||||
// doesn't support the native range deletion.
|
// doesn't support the native range deletion.
|
||||||
for {
|
for {
|
||||||
|
|
@ -997,7 +1186,9 @@ func deleteRange(batch ethdb.Batch, prefix []byte) {
|
||||||
// therefore inconsistent. This is a tradeoff of the current LevelDB-based
|
// therefore inconsistent. This is a tradeoff of the current LevelDB-based
|
||||||
// approach.
|
// approach.
|
||||||
if errors.Is(err, ethdb.ErrTooManyKeys) {
|
if errors.Is(err, ethdb.ErrTooManyKeys) {
|
||||||
batch.Write()
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed to flush state deletions", "err", err)
|
||||||
|
}
|
||||||
batch.Reset()
|
batch.Reset()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -1005,6 +1196,82 @@ func deleteRange(batch ethdb.Batch, prefix []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetTrienodes wipes all persisted trienodes if the path scheme is used.
|
||||||
|
// It's a defensive operation, ensuring all the leftover trie nodes are cleared
|
||||||
|
// before the new generation cycle.
|
||||||
|
func (s *syncerV2) resetTrienodes(batch ethdb.Batch) {
|
||||||
|
if s.scheme == rawdb.PathScheme {
|
||||||
|
deleteRange(batch, rawdb.TrieNodeAccountPrefix)
|
||||||
|
deleteRange(batch, rawdb.TrieNodeStoragePrefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pruneStaleState removes any flat state the persisted journal does not cover.
|
||||||
|
func (s *syncerV2) pruneStaleState() error {
|
||||||
|
var (
|
||||||
|
batch = s.db.NewBatch()
|
||||||
|
accountKey = func(h common.Hash) []byte {
|
||||||
|
return append(bytes.Clone(rawdb.SnapshotAccountPrefix), h.Bytes()...)
|
||||||
|
}
|
||||||
|
storageKey = func(hashes ...common.Hash) []byte {
|
||||||
|
key := bytes.Clone(rawdb.SnapshotStoragePrefix)
|
||||||
|
for _, h := range hashes {
|
||||||
|
key = append(key, h.Bytes()...)
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
)
|
||||||
|
keyRangeLimit := func(base []byte, last common.Hash) []byte {
|
||||||
|
if last != common.MaxHash {
|
||||||
|
return append(base, incHash(last).Bytes()...)
|
||||||
|
}
|
||||||
|
return increaseKey(base)
|
||||||
|
}
|
||||||
|
for _, task := range s.tasks {
|
||||||
|
deleteKeyRange(batch, accountKey(task.Next), keyRangeLimit(bytes.Clone(rawdb.SnapshotAccountPrefix), task.Last))
|
||||||
|
|
||||||
|
protected := make([]common.Hash, 0, len(task.stateCompleted)+len(task.SubTasks))
|
||||||
|
for hash := range task.stateCompleted {
|
||||||
|
if bytes.Compare(hash[:], task.Next[:]) < 0 {
|
||||||
|
return errors.New("unexpected storage marker before the range")
|
||||||
|
}
|
||||||
|
if bytes.Compare(hash[:], task.Last[:]) > 0 {
|
||||||
|
return errors.New("unexpected storage marker after the range")
|
||||||
|
}
|
||||||
|
protected = append(protected, hash)
|
||||||
|
}
|
||||||
|
for hash := range task.SubTasks {
|
||||||
|
if _, ok := task.stateCompleted[hash]; ok {
|
||||||
|
return errors.New("unexpected duplicated storage marker")
|
||||||
|
}
|
||||||
|
if bytes.Compare(hash[:], task.Next[:]) < 0 {
|
||||||
|
return errors.New("unexpected storage marker before the range")
|
||||||
|
}
|
||||||
|
if bytes.Compare(hash[:], task.Last[:]) > 0 {
|
||||||
|
return errors.New("unexpected storage marker after the range")
|
||||||
|
}
|
||||||
|
protected = append(protected, hash)
|
||||||
|
}
|
||||||
|
sort.Slice(protected, func(i, j int) bool {
|
||||||
|
return bytes.Compare(protected[i][:], protected[j][:]) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
start := storageKey(task.Next)
|
||||||
|
for _, hash := range protected {
|
||||||
|
deleteKeyRange(batch, start, storageKey(hash))
|
||||||
|
start = increaseKey(storageKey(hash))
|
||||||
|
}
|
||||||
|
deleteKeyRange(batch, start, keyRangeLimit(bytes.Clone(rawdb.SnapshotStoragePrefix), task.Last))
|
||||||
|
|
||||||
|
for account, subtasks := range task.SubTasks {
|
||||||
|
for _, sub := range subtasks {
|
||||||
|
deleteKeyRange(batch, storageKey(account, sub.Next), keyRangeLimit(storageKey(account), sub.Last))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return batch.Write()
|
||||||
|
}
|
||||||
|
|
||||||
// resetSyncState wipes all persisted snap-sync data (sync status, account
|
// resetSyncState wipes all persisted snap-sync data (sync status, account
|
||||||
// and storage snapshots) and re-initializes in-memory state with a fresh
|
// and storage snapshots) and re-initializes in-memory state with a fresh
|
||||||
// chunking of the account hash range.
|
// chunking of the account hash range.
|
||||||
|
|
@ -1013,17 +1280,21 @@ func (s *syncerV2) resetSyncState() {
|
||||||
rawdb.DeleteSnapshotSyncStatus(batch)
|
rawdb.DeleteSnapshotSyncStatus(batch)
|
||||||
deleteRange(batch, rawdb.SnapshotAccountPrefix)
|
deleteRange(batch, rawdb.SnapshotAccountPrefix)
|
||||||
deleteRange(batch, rawdb.SnapshotStoragePrefix)
|
deleteRange(batch, rawdb.SnapshotStoragePrefix)
|
||||||
|
s.resetTrienodes(batch)
|
||||||
batch.Write()
|
batch.Write()
|
||||||
|
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
s.tasks = nil
|
s.tasks = nil
|
||||||
s.previousPivot = nil
|
s.pivot = nil
|
||||||
s.complete = false
|
s.setPhase(phaseDownload)
|
||||||
s.accountSynced, s.accountBytes = 0, 0
|
s.accountSynced, s.accountBytes = 0, 0
|
||||||
s.bytecodeSynced, s.bytecodeBytes = 0, 0
|
s.bytecodeSynced, s.bytecodeBytes = 0, 0
|
||||||
s.storageSynced, s.storageBytes = 0, 0
|
s.storageSynced, s.storageBytes = 0, 0
|
||||||
|
s.accessListSynced, s.accessListTotal = 0, 0
|
||||||
|
s.genProgress.Store(0)
|
||||||
|
s.refreshProgressLocked()
|
||||||
|
|
||||||
var next common.Hash
|
var next common.Hash
|
||||||
step := new(big.Int).Sub(
|
step := new(big.Int).Sub(
|
||||||
|
|
@ -1069,9 +1340,9 @@ func (s *syncerV2) saveSyncStatusWithDB(db ethdb.KeyValueWriter) {
|
||||||
}
|
}
|
||||||
// Store the actual progress markers.
|
// Store the actual progress markers.
|
||||||
progress := &syncProgressV2{
|
progress := &syncProgressV2{
|
||||||
Pivot: s.previousPivot,
|
Pivot: s.pivot,
|
||||||
Tasks: s.tasks,
|
Tasks: s.tasks,
|
||||||
Complete: s.complete,
|
Phase: s.getPhase(),
|
||||||
AccountSynced: s.accountSynced,
|
AccountSynced: s.accountSynced,
|
||||||
AccountBytes: s.accountBytes,
|
AccountBytes: s.accountBytes,
|
||||||
BytecodeSynced: s.bytecodeSynced,
|
BytecodeSynced: s.bytecodeSynced,
|
||||||
|
|
@ -1088,11 +1359,29 @@ func (s *syncerV2) saveSyncStatusWithDB(db ethdb.KeyValueWriter) {
|
||||||
rawdb.WriteSnapshotSyncStatus(db, status)
|
rawdb.WriteSnapshotSyncStatus(db, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refreshProgressLocked rebuilds the externally-exposed progress snapshot from
|
||||||
|
// the live counters. The caller must hold s.lock.
|
||||||
|
func (s *syncerV2) refreshProgressLocked() {
|
||||||
|
s.extProgress = &syncProgressV2{
|
||||||
|
AccountSynced: s.accountSynced,
|
||||||
|
AccountBytes: s.accountBytes,
|
||||||
|
BytecodeSynced: s.bytecodeSynced,
|
||||||
|
BytecodeBytes: s.bytecodeBytes,
|
||||||
|
StorageSynced: s.storageSynced,
|
||||||
|
StorageBytes: s.storageBytes,
|
||||||
|
AccessListSynced: s.accessListSynced,
|
||||||
|
AccessListTotal: s.accessListTotal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Progress returns the snap sync status statistics.
|
// Progress returns the snap sync status statistics.
|
||||||
func (s *syncerV2) Progress() *syncProgressV2 {
|
func (s *syncerV2) Progress() *syncProgressV2 {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
return s.extProgress
|
|
||||||
|
p := *s.extProgress
|
||||||
|
p.TrieGenPercent = s.genProgress.Load()
|
||||||
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanAccountTasks removes account range retrieval tasks that have already been
|
// cleanAccountTasks removes account range retrieval tasks that have already been
|
||||||
|
|
@ -2028,7 +2317,7 @@ func (s *syncerV2) forwardAccountTask(task *accountTaskV2) {
|
||||||
|
|
||||||
// Persist the received account segments. These flat state maybe
|
// Persist the received account segments. These flat state maybe
|
||||||
// outdated during the sync, but it can be fixed later during the
|
// outdated during the sync, but it can be fixed later during the
|
||||||
// trie rebuild.
|
// trie generation.
|
||||||
oldAccountBytes := s.accountBytes
|
oldAccountBytes := s.accountBytes
|
||||||
|
|
||||||
batch := ethdb.HookedBatch{
|
batch := ethdb.HookedBatch{
|
||||||
|
|
@ -2554,25 +2843,45 @@ func (s *syncerV2) reportSyncProgressV2(force bool) {
|
||||||
"accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed))
|
"accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
// accessListPeersExhausted reports whether forward progress on BAL fetches is
|
// checkAccessListProgress reports whether the BAL fetch can still make
|
||||||
// impossible: at least one peer is connected, every connected peer is marked
|
// forward progress against the current peer set.
|
||||||
// stateless, and no BAL requests are in flight.
|
func (s *syncerV2) checkAccessListProgress(pending map[common.Hash]struct{}, refused map[common.Hash]map[string]struct{}) error {
|
||||||
func (s *syncerV2) accessListPeersExhausted() bool {
|
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
if len(s.peers) == 0 {
|
if len(s.peers) == 0 {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
if len(s.accessListReqs) > 0 {
|
if len(s.accessListReqs) > 0 {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
serviceable := make(map[string]struct{}, len(s.peers))
|
||||||
for id := range s.peers {
|
for id := range s.peers {
|
||||||
if _, ok := s.statelessPeers[id]; !ok {
|
if _, ok := s.statelessPeers[id]; !ok {
|
||||||
return false
|
serviceable[id] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
if len(serviceable) == 0 {
|
||||||
|
return errAccessListPeersExhausted
|
||||||
|
}
|
||||||
|
for h, set := range refused {
|
||||||
|
// Delivered by some other peer after all
|
||||||
|
if _, ok := pending[h]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unobtainable := true
|
||||||
|
for id := range serviceable {
|
||||||
|
if _, ok := set[id]; !ok {
|
||||||
|
unobtainable = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unobtainable {
|
||||||
|
log.Warn("Access list unavailable from all peers", "hash", h)
|
||||||
|
return errAccessListUnavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sortIdlePeers builds a list of idle peers sorted by download capacity
|
// sortIdlePeers builds a list of idle peers sorted by download capacity
|
||||||
|
|
|
||||||
|
|
@ -547,6 +547,65 @@ func testSyncV2(t *testing.T, scheme string) {
|
||||||
verifyAdoptedSyncedState(scheme, syncer.db, sourceAccountTrie.Hash(), elems, t)
|
verifyAdoptedSyncedState(scheme, syncer.db, sourceAccountTrie.Hash(), elems, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSyncV2FrozenPivot checks the pivot freeze signal around the sync
|
||||||
|
// lifecycle. The pivot is unfrozen while flat state is downloading, frozen
|
||||||
|
// once the download completes, stays frozen after the sync returns so the
|
||||||
|
// downloader resumes against it until the pivot block is committed, and
|
||||||
|
// unfreezes again after a state reset.
|
||||||
|
func TestSyncV2FrozenPivot(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testSyncV2FrozenPivot(t, rawdb.HashScheme)
|
||||||
|
testSyncV2FrozenPivot(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSyncV2FrozenPivot(t *testing.T, scheme string) {
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
cancel = make(chan struct{})
|
||||||
|
term = func() { once.Do(func() { close(cancel) }) }
|
||||||
|
)
|
||||||
|
nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme)
|
||||||
|
|
||||||
|
source := newTestPeerV2("source", t, term)
|
||||||
|
source.accountTrie = sourceAccountTrie.Copy()
|
||||||
|
source.accountValues = elems
|
||||||
|
|
||||||
|
syncer := setupSyncerV2(nodeScheme, source)
|
||||||
|
pivot := mkPivot(0, sourceAccountTrie.Hash())
|
||||||
|
|
||||||
|
// The handler runs while account ranges are still being served, so it
|
||||||
|
// can observe the signal mid download.
|
||||||
|
source.accountRequestV2Handler = func(p *testPeerV2, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap int) error {
|
||||||
|
if syncer.FrozenPivot() != nil {
|
||||||
|
t.Error("pivot frozen during flat state download")
|
||||||
|
}
|
||||||
|
return defaultAccountRequestHandlerV2(p, requestId, root, origin, limit, cap)
|
||||||
|
}
|
||||||
|
if syncer.FrozenPivot() != nil {
|
||||||
|
t.Fatal("pivot frozen before sync started")
|
||||||
|
}
|
||||||
|
if err := syncer.Sync(pivot, cancel); err != nil {
|
||||||
|
t.Fatalf("sync failed: %v", err)
|
||||||
|
}
|
||||||
|
if frozen := syncer.FrozenPivot(); frozen == nil || frozen.Hash() != pivot.Hash() {
|
||||||
|
t.Fatal("pivot not frozen at the synced header after download completed")
|
||||||
|
}
|
||||||
|
// A restart must not lose the freeze: a fresh syncer instance on the same
|
||||||
|
// database derives it from the persisted journal, before any Sync call.
|
||||||
|
restarted := newSyncerV2(syncer.db, nodeScheme)
|
||||||
|
if frozen := restarted.FrozenPivot(); frozen == nil || frozen.Hash() != pivot.Hash() {
|
||||||
|
t.Fatal("pivot freeze lost after restart")
|
||||||
|
}
|
||||||
|
syncer.resetSyncState()
|
||||||
|
if syncer.FrozenPivot() != nil {
|
||||||
|
t.Fatal("pivot still frozen after state reset")
|
||||||
|
}
|
||||||
|
// The reset deletes the journal, so a restarted instance is unfrozen too.
|
||||||
|
if restarted := newSyncerV2(syncer.db, nodeScheme); restarted.FrozenPivot() != nil {
|
||||||
|
t.Fatal("pivot still frozen after restart following a state reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// verifyAdoptedSyncedState exercises the snap/2 completion contract end-to-end:
|
// verifyAdoptedSyncedState exercises the snap/2 completion contract end-to-end:
|
||||||
// after a real sync, opening a fresh triedb and calling AdoptSyncedState must
|
// after a real sync, opening a fresh triedb and calling AdoptSyncedState must
|
||||||
// (a) succeed and (b) leave flat-state reads serving immediately, with no
|
// (a) succeed and (b) leave flat-state reads serving immediately, with no
|
||||||
|
|
@ -1326,9 +1385,9 @@ func TestIsPivotReorged(t *testing.T) {
|
||||||
// canonical header at block 100 has a different hash. Sync is then called with
|
// canonical header at block 100 has a different hash. Sync is then called with
|
||||||
// a new pivot at the same height.
|
// a new pivot at the same height.
|
||||||
//
|
//
|
||||||
// If isPivotReorged works, loadSyncStatus restores previousPivot, the check
|
// If isPivotReorged works, loadSyncStatus restores the persisted pivot, the
|
||||||
// flags it as reorged, resetSyncState clears previousPivot, catchUp is
|
// check flags it as reorged, resetSyncState clears it, catchUp is skipped,
|
||||||
// skipped, and the fresh download proceeds to completion.
|
// and the fresh download proceeds to completion.
|
||||||
//
|
//
|
||||||
// If detection doesn't fire, the pivot-move check would call catchUp with
|
// If detection doesn't fire, the pivot-move check would call catchUp with
|
||||||
// from = 101 and to = 100 — the inverted-range guard surfaces that as an
|
// from = 101 and to = 100 — the inverted-range guard surfaces that as an
|
||||||
|
|
@ -1347,17 +1406,14 @@ func TestSyncDetectsPivotReorged(t *testing.T) {
|
||||||
// and non-zero counter so the reset path has something to clean up.
|
// and non-zero counter so the reset path has something to clean up.
|
||||||
orphanPivot := mkPivot(100, common.HexToHash("0xdead"))
|
orphanPivot := mkPivot(100, common.HexToHash("0xdead"))
|
||||||
seed := newSyncerV2(db, nodeScheme)
|
seed := newSyncerV2(db, nodeScheme)
|
||||||
// previousPivot reflects where flat state matches and it is what
|
// pivot reflects where flat state matches and it is what saveSyncStatus
|
||||||
// saveSyncStatus persists. Set it to simulate a prior sync reaching
|
// persists. Set it to simulate a prior sync reaching orphanPivot.
|
||||||
// orphanPivot.
|
|
||||||
seed.previousPivot = orphanPivot
|
|
||||||
seed.pivot = orphanPivot
|
seed.pivot = orphanPivot
|
||||||
seed.accountSynced = 42
|
seed.accountSynced = 42
|
||||||
seed.tasks = []*accountTaskV2{{
|
seed.tasks = []*accountTaskV2{{
|
||||||
Next: common.HexToHash("0x80"),
|
Next: common.HexToHash("0x80"),
|
||||||
Last: common.MaxHash,
|
Last: common.MaxHash,
|
||||||
SubTasks: make(map[common.Hash][]*storageTaskV2),
|
SubTasks: make(map[common.Hash][]*storageTaskV2),
|
||||||
stateCompleted: make(map[common.Hash]struct{}),
|
|
||||||
}}
|
}}
|
||||||
seed.saveSyncStatus()
|
seed.saveSyncStatus()
|
||||||
|
|
||||||
|
|
@ -1391,14 +1447,14 @@ func TestSyncDetectsPivotReorged(t *testing.T) {
|
||||||
if err := syncer.Sync(newPivot, cancel); err != nil {
|
if err := syncer.Sync(newPivot, cancel); err != nil {
|
||||||
t.Fatalf("sync failed (reorg detection likely broken): %v", err)
|
t.Fatalf("sync failed (reorg detection likely broken): %v", err)
|
||||||
}
|
}
|
||||||
// After successful completion, status should be marked Complete=true
|
// After successful completion, status should reach the complete phase
|
||||||
// against the new (canonical) pivot.
|
// against the new (canonical) pivot.
|
||||||
loader := newSyncerV2(db, nodeScheme)
|
loader := newSyncerV2(db, nodeScheme)
|
||||||
loader.loadSyncStatus()
|
loader.loadSyncStatus()
|
||||||
if !loader.complete {
|
if loader.getPhase() != phaseComplete {
|
||||||
t.Fatal("sync status should be marked Complete=true after successful completion")
|
t.Fatal("sync status should reach the complete phase after successful completion")
|
||||||
}
|
}
|
||||||
if loader.previousPivot == nil || loader.previousPivot.Hash() != newPivot.Hash() {
|
if loader.pivot == nil || loader.pivot.Hash() != newPivot.Hash() {
|
||||||
t.Fatalf("expected persisted pivot to match new pivot")
|
t.Fatalf("expected persisted pivot to match new pivot")
|
||||||
}
|
}
|
||||||
if data := rawdb.ReadAccountSnapshot(db, orphanAccountHash); len(data) != 0 {
|
if data := rawdb.ReadAccountSnapshot(db, orphanAccountHash); len(data) != 0 {
|
||||||
|
|
@ -1445,9 +1501,8 @@ func testInterruptedDownloadRecovery(t *testing.T, scheme string) {
|
||||||
syncer1.Register(src1)
|
syncer1.Register(src1)
|
||||||
src1.remote = syncer1
|
src1.remote = syncer1
|
||||||
pivot := mkPivot(0, root)
|
pivot := mkPivot(0, root)
|
||||||
syncer1.pivot = pivot
|
|
||||||
syncer1.previousPivot = pivot // Sync sets this before downloadState
|
|
||||||
syncer1.loadSyncStatus()
|
syncer1.loadSyncStatus()
|
||||||
|
syncer1.pivot = pivot // Sync pins this before downloadState
|
||||||
syncer1.downloadState(cancel1)
|
syncer1.downloadState(cancel1)
|
||||||
|
|
||||||
// Save progress
|
// Save progress
|
||||||
|
|
@ -1483,9 +1538,8 @@ func testInterruptedDownloadRecovery(t *testing.T, scheme string) {
|
||||||
syncer2.Register(src2)
|
syncer2.Register(src2)
|
||||||
src2.remote = syncer2
|
src2.remote = syncer2
|
||||||
pivot2 := mkPivot(0, root)
|
pivot2 := mkPivot(0, root)
|
||||||
syncer2.pivot = pivot2
|
|
||||||
syncer2.previousPivot = pivot2 // Sync sets this before downloadState
|
|
||||||
syncer2.loadSyncStatus()
|
syncer2.loadSyncStatus()
|
||||||
|
syncer2.pivot = pivot2 // Sync pins this before downloadState
|
||||||
if err := syncer2.downloadState(cancel2); err != nil {
|
if err := syncer2.downloadState(cancel2); err != nil {
|
||||||
t.Fatalf("resumed download failed: %v", err)
|
t.Fatalf("resumed download failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1499,10 +1553,10 @@ func testInterruptedDownloadRecovery(t *testing.T, scheme string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSyncPersistsPivotDuringDownload verifies that after a fresh Sync is
|
// TestSyncPersistsPivotDuringDownload verifies that after a fresh Sync is
|
||||||
// interrupted mid-download, the persisted previousPivot equals the current
|
// interrupted mid-download, the persisted pivot equals the current pivot
|
||||||
// pivot (not nil). Without this, a follow-up Sync at a different pivot
|
// (not nil). Without this, a follow-up Sync at a different pivot would not
|
||||||
// would not see that the partial flat state belongs to the old pivot, and
|
// see that the partial flat state belongs to the old pivot, and would mix
|
||||||
// would mix old-pivot accounts with new-pivot data.
|
// old-pivot accounts with new-pivot data.
|
||||||
func TestSyncPersistsPivotDuringDownload(t *testing.T) {
|
func TestSyncPersistsPivotDuringDownload(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, rawdb.HashScheme)
|
nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, rawdb.HashScheme)
|
||||||
|
|
@ -1532,15 +1586,15 @@ func TestSyncPersistsPivotDuringDownload(t *testing.T) {
|
||||||
// Sync should be interrupted by the cancel after a couple of responses.
|
// Sync should be interrupted by the cancel after a couple of responses.
|
||||||
_ = syncer.Sync(pivot, cancel)
|
_ = syncer.Sync(pivot, cancel)
|
||||||
|
|
||||||
// Persisted previousPivot must equal the pivot, so a follow-up Sync at a
|
// Persisted pivot must equal the pivot, so a follow-up Sync at a different
|
||||||
// different pivot can recognize the partial flat state belongs to this one.
|
// pivot can recognize the partial flat state belongs to this one.
|
||||||
loader := newSyncerV2(db, nodeScheme)
|
loader := newSyncerV2(db, nodeScheme)
|
||||||
loader.loadSyncStatus()
|
loader.loadSyncStatus()
|
||||||
if loader.previousPivot == nil {
|
if loader.pivot == nil {
|
||||||
t.Fatal("expected persisted previousPivot to be set after interrupted download, got nil")
|
t.Fatal("expected persisted pivot to be set after interrupted download, got nil")
|
||||||
}
|
}
|
||||||
if loader.previousPivot.Hash() != pivot.Hash() {
|
if loader.pivot.Hash() != pivot.Hash() {
|
||||||
t.Errorf("persisted previousPivot mismatch: got %v, want %v", loader.previousPivot.Hash(), pivot.Hash())
|
t.Errorf("persisted pivot mismatch: got %v, want %v", loader.pivot.Hash(), pivot.Hash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1702,7 +1756,7 @@ func testPivotMovement(t *testing.T, scheme string, pivotMoves int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCatchUpPersistsIncrementally verifies that catchUp updates and persists
|
// TestCatchUpPersistsIncrementally verifies that catchUp updates and persists
|
||||||
// previousPivot after each successfully applied BAL. If a later block in the
|
// the pivot after each successfully applied BAL. If a later block in the
|
||||||
// gap fails to apply, the persisted state reflects the last successful block,
|
// gap fails to apply, the persisted state reflects the last successful block,
|
||||||
// so a follow-up Sync can resume from there rather than reapplying everything.
|
// so a follow-up Sync can resume from there rather than reapplying everything.
|
||||||
func TestCatchUpPersistsIncrementally(t *testing.T) {
|
func TestCatchUpPersistsIncrementally(t *testing.T) {
|
||||||
|
|
@ -1776,7 +1830,7 @@ func testCatchUpPersistsIncrementally(t *testing.T, scheme string) {
|
||||||
blocks[i] = balBlock{header: header, bal: buf.Bytes()}
|
blocks[i] = balBlock{header: header, bal: buf.Bytes()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// First sync: complete sync to A so persisted state has previousPivot=A,
|
// First sync: complete sync to A so persisted state has pivot=A,
|
||||||
// flat state covers all accounts.
|
// flat state covers all accounts.
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
|
|
@ -1826,22 +1880,157 @@ func testCatchUpPersistsIncrementally(t *testing.T, scheme string) {
|
||||||
t.Fatal("expected Sync to fail when applyAccessList hits corrupt flat state")
|
t.Fatal("expected Sync to fail when applyAccessList hits corrupt flat state")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persisted previousPivot should now reflect the last successfully applied
|
// Persisted pivot should now reflect the last successfully applied
|
||||||
// block (A+2). Without per-iteration saves, it would still be at A.
|
// block (A+2). Without per-iteration saves, it would still be at A.
|
||||||
loader := newSyncerV2(db, nodeScheme)
|
loader := newSyncerV2(db, nodeScheme)
|
||||||
loader.loadSyncStatus()
|
loader.loadSyncStatus()
|
||||||
if loader.previousPivot == nil {
|
if loader.pivot == nil {
|
||||||
t.Fatal("expected persisted previousPivot to be set after partial catchUp")
|
t.Fatal("expected persisted pivot to be set after partial catchUp")
|
||||||
}
|
}
|
||||||
wantHash := blocks[1].header.Hash()
|
wantHash := blocks[1].header.Hash()
|
||||||
if loader.previousPivot.Hash() != wantHash {
|
if loader.pivot.Hash() != wantHash {
|
||||||
t.Errorf("persisted previousPivot mismatch after partial catchUp: got %v, want %v (block A+2)",
|
t.Errorf("persisted pivot mismatch after partial catchUp: got %v, want %v (block A+2)",
|
||||||
loader.previousPivot.Hash(), wantHash)
|
loader.pivot.Hash(), wantHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCatchUpWindowed verifies that catch-up correctly rolls the flat state
|
||||||
|
// forward when the gap spans several windows. With catchUpWindow shrunk to 2,
|
||||||
|
// a 5-block gap is processed as three windows ([A+1,A+2], [A+3,A+4], [A+5]),
|
||||||
|
// exercising the window boundaries. Every block's BAL must be fetched and
|
||||||
|
// applied, and the final pivot must reach the target.
|
||||||
|
func TestCatchUpWindowed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCatchUpWindowed(t, rawdb.HashScheme)
|
||||||
|
testCatchUpWindowed(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCatchUpWindowed(t *testing.T, scheme string) {
|
||||||
|
nodeScheme, sourceAccountTrie, elems, addrs := makeAccountTrieWithAddresses(100, scheme)
|
||||||
|
rootA := sourceAccountTrie.Hash()
|
||||||
|
numA := uint64(100)
|
||||||
|
|
||||||
|
targetAddr := addrs[0]
|
||||||
|
targetHash := crypto.Keccak256Hash(targetAddr[:])
|
||||||
|
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
emptyHash := common.Hash{}
|
||||||
|
zero := uint64(0)
|
||||||
|
|
||||||
|
// Persist header + canonical hash for the base pivot A so reorg detection
|
||||||
|
// in Sync passes and catchUp (not reset) runs.
|
||||||
|
pivotA := &types.Header{
|
||||||
|
Number: new(big.Int).SetUint64(numA), Root: rootA, Difficulty: common.Big0,
|
||||||
|
BaseFee: common.Big0, WithdrawalsHash: &emptyHash,
|
||||||
|
BlobGasUsed: &zero, ExcessBlobGas: &zero,
|
||||||
|
ParentBeaconRoot: &emptyHash, RequestsHash: &emptyHash,
|
||||||
|
}
|
||||||
|
rawdb.WriteHeader(db, pivotA)
|
||||||
|
rawdb.WriteCanonicalHash(db, pivotA.Hash(), numA)
|
||||||
|
|
||||||
|
// Build a 5-block gap, each block bumping targetAddr's balance. The last
|
||||||
|
// block's balance is the expected final state.
|
||||||
|
const gap = 5
|
||||||
|
var (
|
||||||
|
lastHeader *types.Header
|
||||||
|
lastBalance *uint256.Int
|
||||||
|
balsByHash = make(map[common.Hash]rlp.RawValue, gap)
|
||||||
|
)
|
||||||
|
for i := 0; i < gap; i++ {
|
||||||
|
blockNum := numA + uint64(i) + 1
|
||||||
|
balance := uint256.NewInt(uint64(1000 * (i + 1)))
|
||||||
|
|
||||||
|
cb := bal.NewConstructionBlockAccessList()
|
||||||
|
cb.BalanceChange(0, targetAddr, balance)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := cb.EncodeRLP(&buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var b bal.BlockAccessList
|
||||||
|
if err := rlp.DecodeBytes(buf.Bytes(), &b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
balHash := b.Hash()
|
||||||
|
header := &types.Header{
|
||||||
|
Number: new(big.Int).SetUint64(blockNum), Difficulty: common.Big0,
|
||||||
|
BaseFee: common.Big0, WithdrawalsHash: &emptyHash,
|
||||||
|
BlobGasUsed: &zero, ExcessBlobGas: &zero,
|
||||||
|
ParentBeaconRoot: &emptyHash, RequestsHash: &emptyHash,
|
||||||
|
BlockAccessListHash: &balHash,
|
||||||
|
}
|
||||||
|
rawdb.WriteHeader(db, header)
|
||||||
|
rawdb.WriteCanonicalHash(db, header.Hash(), blockNum)
|
||||||
|
balsByHash[header.Hash()] = buf.Bytes()
|
||||||
|
lastHeader, lastBalance = header, balance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed sync to A: persisted state ends with pivot=A and full flat state.
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
cancel = make(chan struct{})
|
||||||
|
term = func() { once.Do(func() { close(cancel) }) }
|
||||||
|
)
|
||||||
|
syncer := newSyncerV2(db, nodeScheme)
|
||||||
|
src := newTestPeerV2("seed", t, term)
|
||||||
|
src.accountTrie = sourceAccountTrie.Copy()
|
||||||
|
src.accountValues = elems
|
||||||
|
syncer.Register(src)
|
||||||
|
src.remote = syncer
|
||||||
|
if err := syncer.Sync(pivotA, cancel); err != nil {
|
||||||
|
t.Fatalf("seed sync failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run catch-up to A+5 directly with a window of 2, forcing three windows
|
||||||
|
// ([A+1,A+2], [A+3,A+4], [A+5]). Calling catchUp in isolation keeps the
|
||||||
|
// focus on the windowing logic without the surrounding download/trie-gen
|
||||||
|
// phases (which would need a real target state root).
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
cancel = make(chan struct{})
|
||||||
|
term = func() { once.Do(func() { close(cancel) }) }
|
||||||
|
)
|
||||||
|
syncer := newSyncerV2(db, nodeScheme)
|
||||||
|
syncer.loadSyncStatus() // restores pivot=A from the seed sync
|
||||||
|
if syncer.pivot == nil || syncer.pivot.Number.Uint64() != numA {
|
||||||
|
t.Fatalf("expected restored pivot at block %d, got %v", numA, syncer.pivot)
|
||||||
|
}
|
||||||
|
syncer.catchUpWindow = 2
|
||||||
|
src := newTestPeerV2("catchup", t, term)
|
||||||
|
src.accountTrie = sourceAccountTrie.Copy()
|
||||||
|
src.accountValues = elems
|
||||||
|
src.accessLists = balsByHash
|
||||||
|
syncer.Register(src)
|
||||||
|
src.remote = syncer
|
||||||
|
if err := syncer.catchUp(lastHeader, cancel); err != nil {
|
||||||
|
t.Fatalf("windowed catch-up failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The account must reflect the final block's balance, proving every window
|
||||||
|
// (including the trailing partial one) was fetched and applied in order.
|
||||||
|
data := rawdb.ReadAccountSnapshot(db, targetHash)
|
||||||
|
if len(data) == 0 {
|
||||||
|
t.Fatal("target account missing after windowed catch-up")
|
||||||
|
}
|
||||||
|
account, err := types.FullAccount(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to decode account: %v", err)
|
||||||
|
}
|
||||||
|
if account.Balance.Cmp(lastBalance) != 0 {
|
||||||
|
t.Errorf("balance after windowed catch-up: got %v, want %v", account.Balance, lastBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The persisted pivot must have advanced all the way to the target.
|
||||||
|
loader := newSyncerV2(db, nodeScheme)
|
||||||
|
loader.loadSyncStatus()
|
||||||
|
if loader.pivot == nil || loader.pivot.Hash() != lastHeader.Hash() {
|
||||||
|
t.Errorf("persisted pivot did not reach target after windowed catch-up")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSyncStatusMarkedCompleteAfterCompletion verifies that after a full sync
|
// TestSyncStatusMarkedCompleteAfterCompletion verifies that after a full sync
|
||||||
// completes, the persisted sync status has Complete=true. This lets a
|
// completes, the persisted sync status reaches the complete phase. This lets a
|
||||||
// subsequent Sync call distinguish "already done" from "fresh node" and skip.
|
// subsequent Sync call distinguish "already done" from "fresh node" and skip.
|
||||||
func TestSyncStatusMarkedCompleteAfterCompletion(t *testing.T) {
|
func TestSyncStatusMarkedCompleteAfterCompletion(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
@ -1870,13 +2059,13 @@ func testSyncStatusMarkedCompleteAfterCompletion(t *testing.T, scheme string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// After successful sync, persisted status should be present with
|
// After successful sync, persisted status should be present with
|
||||||
// Complete=true and the pivot we synced to.
|
// the complete phase and the pivot we synced to.
|
||||||
loader := newSyncerV2(syncer.db, nodeScheme)
|
loader := newSyncerV2(syncer.db, nodeScheme)
|
||||||
loader.loadSyncStatus()
|
loader.loadSyncStatus()
|
||||||
if !loader.complete {
|
if loader.getPhase() != phaseComplete {
|
||||||
t.Fatal("expected persisted status to have Complete=true after successful sync")
|
t.Fatal("expected persisted status to reach the complete phase after successful sync")
|
||||||
}
|
}
|
||||||
if loader.previousPivot == nil || loader.previousPivot.Hash() != pivot.Hash() {
|
if loader.pivot == nil || loader.pivot.Hash() != pivot.Hash() {
|
||||||
t.Fatalf("expected persisted pivot to match synced pivot")
|
t.Fatalf("expected persisted pivot to match synced pivot")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1906,7 +2095,7 @@ func TestSyncSkipsIfAlreadyComplete(t *testing.T) {
|
||||||
t.Fatalf("first sync failed: %v", err)
|
t.Fatalf("first sync failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe the flat state. The persisted status (with Complete=true) stays.
|
// Wipe the flat state. The persisted status (in the complete phase) stays.
|
||||||
if err := syncer.db.DeleteRange(rawdb.SnapshotAccountPrefix, []byte{rawdb.SnapshotAccountPrefix[0] + 1}); err != nil {
|
if err := syncer.db.DeleteRange(rawdb.SnapshotAccountPrefix, []byte{rawdb.SnapshotAccountPrefix[0] + 1}); err != nil {
|
||||||
t.Fatalf("failed to wipe account snapshot: %v", err)
|
t.Fatalf("failed to wipe account snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1922,17 +2111,17 @@ func TestSyncSkipsIfAlreadyComplete(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestInterruptedRebuildRecovery verifies that if sync is interrupted after
|
// TestInterruptedGenerationRecovery verifies that if sync is interrupted after
|
||||||
// download completes but before trie rebuild finishes, the next Sync() call
|
// download completes but before trie generation finishes, the next Sync() call
|
||||||
// re-runs the download (which completes immediately) and rebuild.
|
// re-runs the download (which completes immediately) and generation.
|
||||||
func TestInterruptedRebuildRecovery(t *testing.T) {
|
func TestInterruptedGenerationRecovery(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, rawdb.HashScheme)
|
nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, rawdb.HashScheme)
|
||||||
root := sourceAccountTrie.Hash()
|
root := sourceAccountTrie.Hash()
|
||||||
|
|
||||||
// First run: complete download, save status, simulate interruption
|
// First run: complete download, save status, simulate interruption
|
||||||
// before rebuild by calling downloadState() directly
|
// before generation by calling downloadState() directly
|
||||||
var (
|
var (
|
||||||
once1 sync.Once
|
once1 sync.Once
|
||||||
cancel1 = make(chan struct{})
|
cancel1 = make(chan struct{})
|
||||||
|
|
@ -1946,9 +2135,8 @@ func TestInterruptedRebuildRecovery(t *testing.T) {
|
||||||
syncer1.Register(src1)
|
syncer1.Register(src1)
|
||||||
src1.remote = syncer1
|
src1.remote = syncer1
|
||||||
pivot := mkPivot(0, root)
|
pivot := mkPivot(0, root)
|
||||||
syncer1.pivot = pivot
|
|
||||||
syncer1.previousPivot = pivot // Sync sets this before downloadState
|
|
||||||
syncer1.loadSyncStatus()
|
syncer1.loadSyncStatus()
|
||||||
|
syncer1.pivot = pivot // Sync pins this before downloadState
|
||||||
|
|
||||||
if err := syncer1.downloadState(cancel1); err != nil {
|
if err := syncer1.downloadState(cancel1); err != nil {
|
||||||
t.Fatalf("download failed: %v", err)
|
t.Fatalf("download failed: %v", err)
|
||||||
|
|
@ -1960,11 +2148,11 @@ func TestInterruptedRebuildRecovery(t *testing.T) {
|
||||||
syncer1.cleanAccountTasks()
|
syncer1.cleanAccountTasks()
|
||||||
syncer1.saveSyncStatus()
|
syncer1.saveSyncStatus()
|
||||||
|
|
||||||
// Status should exist (rebuild hasn't run yet)
|
// Status should exist (generation hasn't run yet)
|
||||||
if rawdb.ReadSnapshotSyncStatus(db) == nil {
|
if rawdb.ReadSnapshotSyncStatus(db) == nil {
|
||||||
t.Fatal("sync status should exist after download")
|
t.Fatal("sync status should exist after download")
|
||||||
}
|
}
|
||||||
// Second run: full Sync should detect tasks are done, run rebuild
|
// Second run: full Sync should detect tasks are done, run generation
|
||||||
var (
|
var (
|
||||||
once2 sync.Once
|
once2 sync.Once
|
||||||
cancel2 = make(chan struct{})
|
cancel2 = make(chan struct{})
|
||||||
|
|
@ -1980,14 +2168,199 @@ func TestInterruptedRebuildRecovery(t *testing.T) {
|
||||||
if err := syncer2.Sync(mkPivot(0, root), cancel2); err != nil {
|
if err := syncer2.Sync(mkPivot(0, root), cancel2); err != nil {
|
||||||
t.Fatalf("resumed sync failed: %v", err)
|
t.Fatalf("resumed sync failed: %v", err)
|
||||||
}
|
}
|
||||||
// After rebuild completes, status should be marked Complete=true.
|
// The resumed run re-arms the pivot freeze once its no-op download
|
||||||
|
// completes, the downloader relies on it until the pivot block commits.
|
||||||
|
if syncer2.FrozenPivot() == nil {
|
||||||
|
t.Fatal("pivot not frozen after resumed sync")
|
||||||
|
}
|
||||||
|
// After generation completes, status should reach the complete phase.
|
||||||
loader := newSyncerV2(db, nodeScheme)
|
loader := newSyncerV2(db, nodeScheme)
|
||||||
loader.loadSyncStatus()
|
loader.loadSyncStatus()
|
||||||
if !loader.complete {
|
if loader.getPhase() != phaseComplete {
|
||||||
t.Fatal("sync status should be marked Complete=true after rebuild completes")
|
t.Fatal("sync status should reach the complete phase after generation completes")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPruneStaleState verifies that resuming a sync wipes exactly the flat
|
||||||
|
// state the journal does not cover: account and storage entries beyond the
|
||||||
|
// task cursors are removed, while everything below the cursors — and the
|
||||||
|
// journaled storage of completed-storage accounts and chunked large
|
||||||
|
// contracts inside the window — survives.
|
||||||
|
func TestPruneStaleState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
syncer = newSyncerV2(db, rawdb.HashScheme)
|
||||||
|
|
||||||
|
fetched = common.Hash{0x20} // below the cursor, journal-covered
|
||||||
|
next = common.Hash{0x40} // task cursor
|
||||||
|
stale = common.Hash{0x50} // beyond the cursor, not covered
|
||||||
|
completed = common.Hash{0x60} // beyond the cursor, storage journaled complete
|
||||||
|
chunked = common.Hash{0x80} // beyond the cursor, large contract, partially journaled
|
||||||
|
staleToo = common.Hash{0xa0} // beyond the cursor, not covered
|
||||||
|
|
||||||
|
subNext = common.Hash{0x50} // slot cursor of the chunked contract
|
||||||
|
slotLo = common.Hash{0x11} // below the slot cursor, journal-covered
|
||||||
|
slotHi = common.Hash{0x77} // beyond the slot cursor, not covered
|
||||||
|
|
||||||
|
val = []byte{0xde, 0xad}
|
||||||
|
)
|
||||||
|
syncer.tasks = []*accountTaskV2{{
|
||||||
|
Next: next,
|
||||||
|
Last: common.MaxHash,
|
||||||
|
SubTasks: map[common.Hash][]*storageTaskV2{
|
||||||
|
chunked: {{Next: subNext, Last: common.MaxHash}},
|
||||||
|
},
|
||||||
|
stateCompleted: map[common.Hash]struct{}{completed: {}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Journal-covered state: account below the cursor with a storage slot,
|
||||||
|
// the completed account's storage, the chunked contract's low slots.
|
||||||
|
rawdb.WriteAccountSnapshot(db, fetched, val)
|
||||||
|
rawdb.WriteStorageSnapshot(db, fetched, slotLo, val)
|
||||||
|
rawdb.WriteStorageSnapshot(db, completed, slotLo, val)
|
||||||
|
rawdb.WriteStorageSnapshot(db, chunked, slotLo, val)
|
||||||
|
|
||||||
|
// Uncovered state, flushed after the last journal save: accounts beyond
|
||||||
|
// the cursor (including the completed-storage one, whose account entry
|
||||||
|
// is only legitimate below the cursor), their storage, and the chunked
|
||||||
|
// contract's slots beyond its slot cursor.
|
||||||
|
rawdb.WriteAccountSnapshot(db, stale, val)
|
||||||
|
rawdb.WriteAccountSnapshot(db, completed, val)
|
||||||
|
rawdb.WriteAccountSnapshot(db, staleToo, val)
|
||||||
|
rawdb.WriteStorageSnapshot(db, stale, slotLo, val)
|
||||||
|
rawdb.WriteStorageSnapshot(db, staleToo, slotHi, val)
|
||||||
|
rawdb.WriteStorageSnapshot(db, chunked, slotHi, val)
|
||||||
|
|
||||||
|
// Plant bare keys right at the start of the neighboring keyspaces. The
|
||||||
|
// task ranges end at the maximum hash, so a limit computed by bumping
|
||||||
|
// the full key would roll the carry over into these keyspaces and
|
||||||
|
// swallow such keys.
|
||||||
|
neighborOfAccounts := bytes.Clone(rawdb.SnapshotAccountPrefix)
|
||||||
|
neighborOfAccounts[len(neighborOfAccounts)-1]++
|
||||||
|
db.Put(neighborOfAccounts, val)
|
||||||
|
|
||||||
|
neighborOfStorage := bytes.Clone(rawdb.SnapshotStoragePrefix)
|
||||||
|
neighborOfStorage[len(neighborOfStorage)-1]++
|
||||||
|
db.Put(neighborOfStorage, val)
|
||||||
|
|
||||||
|
if err := syncer.pruneStaleState(); err != nil {
|
||||||
|
t.Fatalf("prune failed: %v", err)
|
||||||
|
}
|
||||||
|
for _, key := range [][]byte{neighborOfAccounts, neighborOfStorage} {
|
||||||
|
if has, _ := db.Has(key); !has {
|
||||||
|
t.Errorf("neighboring keyspace entry %x swallowed by the wipe", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
account common.Hash
|
||||||
|
slot *common.Hash
|
||||||
|
want bool
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{fetched, nil, true, "account below cursor"},
|
||||||
|
{fetched, &slotLo, true, "storage below cursor"},
|
||||||
|
{completed, &slotLo, true, "storage of completed account"},
|
||||||
|
{chunked, &slotLo, true, "chunked contract slot below slot cursor"},
|
||||||
|
{stale, nil, false, "account beyond cursor"},
|
||||||
|
{completed, nil, false, "account entry of completed account"},
|
||||||
|
{staleToo, nil, false, "account beyond cursor (second)"},
|
||||||
|
{stale, &slotLo, false, "storage of unprotected account"},
|
||||||
|
{staleToo, &slotHi, false, "storage of unprotected account (second)"},
|
||||||
|
{chunked, &slotHi, false, "chunked contract slot beyond slot cursor"},
|
||||||
|
} {
|
||||||
|
var have bool
|
||||||
|
if tc.slot == nil {
|
||||||
|
have = len(rawdb.ReadAccountSnapshot(db, tc.account)) > 0
|
||||||
|
} else {
|
||||||
|
have = len(rawdb.ReadStorageSnapshot(db, tc.account, *tc.slot)) > 0
|
||||||
|
}
|
||||||
|
if have != tc.want {
|
||||||
|
t.Errorf("%s: present = %v, want %v", tc.desc, have, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestResumeWipesUncoveredState simulates an unclean shutdown mid-download:
|
||||||
|
// flat state flushed beyond the journaled cursor (which the journal therefore
|
||||||
|
// considers unfetched) must be wiped when the sync resumes — otherwise it
|
||||||
|
// would survive the re-download untouched and corrupt the generated trie.
|
||||||
|
func TestResumeWipesUncoveredState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testResumeWipesUncoveredState(t, rawdb.HashScheme)
|
||||||
|
testResumeWipesUncoveredState(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResumeWipesUncoveredState(t *testing.T, scheme string) {
|
||||||
|
nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme)
|
||||||
|
root := sourceAccountTrie.Hash()
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
pivot := mkPivot(0, root)
|
||||||
|
|
||||||
|
// First run: interrupt the download after a couple of responses, leaving
|
||||||
|
// a journal behind (saved on the graceful teardown).
|
||||||
|
var (
|
||||||
|
once1 sync.Once
|
||||||
|
cancel1 = make(chan struct{})
|
||||||
|
term1 = func() { once1.Do(func() { close(cancel1) }) }
|
||||||
|
responses atomic.Int32
|
||||||
|
)
|
||||||
|
syncer1 := newSyncerV2(db, nodeScheme)
|
||||||
|
src1 := newTestPeerV2("source1", t, term1)
|
||||||
|
src1.accountTrie = sourceAccountTrie.Copy()
|
||||||
|
src1.accountValues = elems
|
||||||
|
src1.accountRequestV2Handler = func(tp *testPeerV2, id uint64, root common.Hash, origin common.Hash, limit common.Hash, cap int) error {
|
||||||
|
if responses.Add(1) > 2 {
|
||||||
|
term1()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return defaultAccountRequestHandlerV2(tp, id, root, origin, limit, cap)
|
||||||
|
}
|
||||||
|
syncer1.Register(src1)
|
||||||
|
src1.remote = syncer1
|
||||||
|
syncer1.Sync(pivot, cancel1)
|
||||||
|
|
||||||
|
// Simulate the unclean shutdown: plant a bogus account that the journal
|
||||||
|
// does not cover (right beyond an unfinished task's cursor) — as if it
|
||||||
|
// was flushed after the journal was last saved. The account is not part
|
||||||
|
// of the source trie, so the re-download would never overwrite it and
|
||||||
|
// trie generation would fail the root check if it survived.
|
||||||
|
loader := newSyncerV2(db, nodeScheme)
|
||||||
|
loader.loadSyncStatus()
|
||||||
|
if len(loader.tasks) == 0 {
|
||||||
|
t.Fatal("expected unfinished tasks in the journal")
|
||||||
|
}
|
||||||
|
task := loader.tasks[len(loader.tasks)-1]
|
||||||
|
bogus := incHash(task.Next)
|
||||||
|
rawdb.WriteAccountSnapshot(db, bogus, types.SlimAccountRLP(types.StateAccount{
|
||||||
|
Nonce: 1, Balance: uint256.NewInt(1),
|
||||||
|
Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash[:],
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Second run: the resume must prune the bogus entry and complete cleanly.
|
||||||
|
var (
|
||||||
|
once2 sync.Once
|
||||||
|
cancel2 = make(chan struct{})
|
||||||
|
term2 = func() { once2.Do(func() { close(cancel2) }) }
|
||||||
|
)
|
||||||
|
syncer2 := newSyncerV2(db, nodeScheme)
|
||||||
|
src2 := newTestPeerV2("source2", t, term2)
|
||||||
|
src2.accountTrie = sourceAccountTrie.Copy()
|
||||||
|
src2.accountValues = elems
|
||||||
|
syncer2.Register(src2)
|
||||||
|
src2.remote = syncer2
|
||||||
|
|
||||||
|
if err := syncer2.Sync(pivot, cancel2); err != nil {
|
||||||
|
t.Fatalf("resumed sync failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(rawdb.ReadAccountSnapshot(db, bogus)) != 0 {
|
||||||
|
t.Fatal("uncovered account entry should have been pruned on resume")
|
||||||
|
}
|
||||||
|
verifyTrie(scheme, db, root, t)
|
||||||
|
}
|
||||||
|
|
||||||
// TestFetchAccessListsMultiplePeers verifies that fetch distributes work
|
// TestFetchAccessListsMultiplePeers verifies that fetch distributes work
|
||||||
// across multiple idle peers.
|
// across multiple idle peers.
|
||||||
func TestFetchAccessListsMultiplePeers(t *testing.T) {
|
func TestFetchAccessListsMultiplePeers(t *testing.T) {
|
||||||
|
|
@ -2462,7 +2835,7 @@ func TestCatchUpRetriesOnBadBAL(t *testing.T) {
|
||||||
|
|
||||||
// makeStorageTrieFromSlots builds a storage trie for owner from raw slot
|
// makeStorageTrieFromSlots builds a storage trie for owner from raw slot
|
||||||
// key->value pairs, using the exact on-disk encoding the flat snapshot and the
|
// key->value pairs, using the exact on-disk encoding the flat snapshot and the
|
||||||
// trie rebuild expect: each leaf is keyed by keccak256(slotKey) and its value is
|
// trie generation expect: each leaf is keyed by keccak256(slotKey) and its value is
|
||||||
// rlp(TrimLeftZeroes(value)). Zero-valued slots are skipped (an unset slot has
|
// rlp(TrimLeftZeroes(value)). Zero-valued slots are skipped (an unset slot has
|
||||||
// no leaf). It returns the storage root, the dirty node set, and the sorted
|
// no leaf). It returns the storage root, the dirty node set, and the sorted
|
||||||
// snapshot leaves (which a test peer serves verbatim).
|
// snapshot leaves (which a test peer serves verbatim).
|
||||||
|
|
@ -2529,12 +2902,12 @@ func makeStateWithStorageContract(scheme string, plain []*kv, contractAddr commo
|
||||||
// slot, an overwrite of an existing slot, a write of zero (deletion), and a
|
// slot, an overwrite of an existing slot, a write of zero (deletion), and a
|
||||||
// multi-tx write where the post-block value wins.
|
// multi-tx write where the post-block value wins.
|
||||||
//
|
//
|
||||||
// It fully syncs pivot A (flat-state download + trie rebuild), then moves the
|
// It fully syncs pivot A (flat-state download + trie generation), then moves the
|
||||||
// pivot to A+1. The move triggers catchUp, which fetches the A+1 BAL, applies
|
// pivot to A+1. The move triggers catchUp, which fetches the A+1 BAL, applies
|
||||||
// the storage diffs to the flat state, and rebuilds the trie. The rebuild
|
// the storage diffs to the flat state, and generates the trie. The generation
|
||||||
// verifies the recomputed root against the pivot's expected post-catch-up root,
|
// verifies the recomputed root against the pivot's expected post-catch-up root,
|
||||||
// so a successful Sync proves the storage mutations were applied in the exact
|
// so a successful Sync proves the storage mutations were applied in the exact
|
||||||
// encoding the trie rebuild consumes. verifyTrie re-walks the result as an
|
// encoding the trie generation consumes. verifyTrie re-walks the result as an
|
||||||
// independent confirmation.
|
// independent confirmation.
|
||||||
func TestCatchUpAppliesStorageBALs(t *testing.T) {
|
func TestCatchUpAppliesStorageBALs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
@ -2620,7 +2993,7 @@ func testCatchUpAppliesStorageBALs(t *testing.T, scheme string) {
|
||||||
// Sync, so the follow-up Sync's reorg check sees A as still-canonical and
|
// Sync, so the follow-up Sync's reorg check sees A as still-canonical and
|
||||||
// runs catchUp instead of resetting. The A+1 header carries the BAL hash
|
// runs catchUp instead of resetting. The A+1 header carries the BAL hash
|
||||||
// (verified during catch-up) and the expected post-catch-up state root
|
// (verified during catch-up) and the expected post-catch-up state root
|
||||||
// (verified by the trie rebuild).
|
// (verified by the trie generation).
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
numA := uint64(128)
|
numA := uint64(128)
|
||||||
emptyH := common.Hash{}
|
emptyH := common.Hash{}
|
||||||
|
|
@ -2644,7 +3017,7 @@ func testCatchUpAppliesStorageBALs(t *testing.T, scheme string) {
|
||||||
rawdb.WriteHeader(db, hdrB)
|
rawdb.WriteHeader(db, hdrB)
|
||||||
rawdb.WriteCanonicalHash(db, hdrB.Hash(), numA+1)
|
rawdb.WriteCanonicalHash(db, hdrB.Hash(), numA+1)
|
||||||
|
|
||||||
// Sync 1: full flat-state download + trie rebuild against pivot A.
|
// Sync 1: full flat-state download + trie generation against pivot A.
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
once sync.Once
|
once sync.Once
|
||||||
|
|
@ -2665,7 +3038,7 @@ func testCatchUpAppliesStorageBALs(t *testing.T, scheme string) {
|
||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}
|
}
|
||||||
// Sanity: the rebuilt trie for pivot A is complete and matches rootA. This
|
// Sanity: the generated trie for pivot A is complete and matches rootA. This
|
||||||
// also confirms the test fixture itself is internally consistent.
|
// also confirms the test fixture itself is internally consistent.
|
||||||
verifyTrie(scheme, db, rootA, t)
|
verifyTrie(scheme, db, rootA, t)
|
||||||
|
|
||||||
|
|
@ -2690,6 +3063,12 @@ func testCatchUpAppliesStorageBALs(t *testing.T, scheme string) {
|
||||||
if err := syncer.Sync(hdrB, cancel); err != nil {
|
if err := syncer.Sync(hdrB, cancel); err != nil {
|
||||||
t.Fatalf("pivot A+1 catch-up sync failed: %v", err)
|
t.Fatalf("pivot A+1 catch-up sync failed: %v", err)
|
||||||
}
|
}
|
||||||
|
// The freeze must re-arm on a pivot-moved cycle too, the downloader
|
||||||
|
// relies on it from download completion until commit, and it must
|
||||||
|
// point at the new pivot the catch-up rolled forward to.
|
||||||
|
if frozen := syncer.FrozenPivot(); frozen == nil || frozen.Hash() != hdrB.Hash() {
|
||||||
|
t.Fatal("pivot not frozen at the new header after catch-up sync")
|
||||||
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
|
||||||
gasLimit uint64 = 31000
|
gasLimit uint64 = 31000
|
||||||
startGas uint64 = 10000
|
startGas uint64 = 10000
|
||||||
value = uint256.NewInt(0)
|
value = uint256.NewInt(0)
|
||||||
contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas), nil)
|
contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas, 0), nil)
|
||||||
)
|
)
|
||||||
evm.SetTxContext(vmctx.txCtx)
|
evm.SetTxContext(vmctx.txCtx)
|
||||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
logger = NewStructLogger(nil)
|
logger = NewStructLogger(nil)
|
||||||
evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
|
evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
|
||||||
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000), nil)
|
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000, 0), nil)
|
||||||
)
|
)
|
||||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
||||||
var index common.Hash
|
var index common.Hash
|
||||||
|
|
|
||||||
|
|
@ -839,6 +839,9 @@ type rpcProgress struct {
|
||||||
HealedBytecodeBytes hexutil.Uint64
|
HealedBytecodeBytes hexutil.Uint64
|
||||||
HealingTrienodes hexutil.Uint64
|
HealingTrienodes hexutil.Uint64
|
||||||
HealingBytecode hexutil.Uint64
|
HealingBytecode hexutil.Uint64
|
||||||
|
SyncedAccessLists hexutil.Uint64
|
||||||
|
TotalAccessLists hexutil.Uint64
|
||||||
|
TrieGenProgress hexutil.Uint64
|
||||||
TxIndexFinishedBlocks hexutil.Uint64
|
TxIndexFinishedBlocks hexutil.Uint64
|
||||||
TxIndexRemainingBlocks hexutil.Uint64
|
TxIndexRemainingBlocks hexutil.Uint64
|
||||||
StateIndexRemaining hexutil.Uint64
|
StateIndexRemaining hexutil.Uint64
|
||||||
|
|
@ -867,6 +870,9 @@ func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress {
|
||||||
HealedBytecodeBytes: uint64(p.HealedBytecodeBytes),
|
HealedBytecodeBytes: uint64(p.HealedBytecodeBytes),
|
||||||
HealingTrienodes: uint64(p.HealingTrienodes),
|
HealingTrienodes: uint64(p.HealingTrienodes),
|
||||||
HealingBytecode: uint64(p.HealingBytecode),
|
HealingBytecode: uint64(p.HealingBytecode),
|
||||||
|
SyncedAccessLists: uint64(p.SyncedAccessLists),
|
||||||
|
TotalAccessLists: uint64(p.TotalAccessLists),
|
||||||
|
TrieGenProgress: uint64(p.TrieGenProgress),
|
||||||
TxIndexFinishedBlocks: uint64(p.TxIndexFinishedBlocks),
|
TxIndexFinishedBlocks: uint64(p.TxIndexFinishedBlocks),
|
||||||
TxIndexRemainingBlocks: uint64(p.TxIndexRemainingBlocks),
|
TxIndexRemainingBlocks: uint64(p.TxIndexRemainingBlocks),
|
||||||
StateIndexRemaining: uint64(p.StateIndexRemaining),
|
StateIndexRemaining: uint64(p.StateIndexRemaining),
|
||||||
|
|
|
||||||
|
|
@ -127,13 +127,18 @@ type SyncProgress struct {
|
||||||
SyncedStorage uint64 // Number of storage slots downloaded
|
SyncedStorage uint64 // Number of storage slots downloaded
|
||||||
SyncedStorageBytes uint64 // Number of storage trie bytes persisted to disk
|
SyncedStorageBytes uint64 // Number of storage trie bytes persisted to disk
|
||||||
|
|
||||||
|
// Snap/1 specific fields
|
||||||
HealedTrienodes uint64 // Number of state trie nodes downloaded
|
HealedTrienodes uint64 // Number of state trie nodes downloaded
|
||||||
HealedTrienodeBytes uint64 // Number of state trie bytes persisted to disk
|
HealedTrienodeBytes uint64 // Number of state trie bytes persisted to disk
|
||||||
HealedBytecodes uint64 // Number of bytecodes downloaded
|
HealedBytecodes uint64 // Number of bytecodes downloaded
|
||||||
HealedBytecodeBytes uint64 // Number of bytecodes persisted to disk
|
HealedBytecodeBytes uint64 // Number of bytecodes persisted to disk
|
||||||
|
HealingTrienodes uint64 // Number of state trie nodes pending
|
||||||
|
HealingBytecode uint64 // Number of bytecodes pending
|
||||||
|
|
||||||
HealingTrienodes uint64 // Number of state trie nodes pending
|
// Snap/2 specific fields
|
||||||
HealingBytecode uint64 // Number of bytecodes pending
|
SyncedAccessLists uint64 // Number of block access lists fetched during catch-up
|
||||||
|
TotalAccessLists uint64 // Total number of block access lists to fetch for catch-up
|
||||||
|
TrieGenProgress uint64 // Trie generation completion, in percent (0..100)
|
||||||
|
|
||||||
// "transaction indexing" fields
|
// "transaction indexing" fields
|
||||||
TxIndexFinishedBlocks uint64 // Number of blocks whose transactions are already indexed
|
TxIndexFinishedBlocks uint64 // Number of blocks whose transactions are already indexed
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,9 @@ func (api *EthereumAPI) Syncing(ctx context.Context) (interface{}, error) {
|
||||||
"healedBytecodeBytes": hexutil.Uint64(progress.HealedBytecodeBytes),
|
"healedBytecodeBytes": hexutil.Uint64(progress.HealedBytecodeBytes),
|
||||||
"healingTrienodes": hexutil.Uint64(progress.HealingTrienodes),
|
"healingTrienodes": hexutil.Uint64(progress.HealingTrienodes),
|
||||||
"healingBytecode": hexutil.Uint64(progress.HealingBytecode),
|
"healingBytecode": hexutil.Uint64(progress.HealingBytecode),
|
||||||
|
"syncedAccessLists": hexutil.Uint64(progress.SyncedAccessLists),
|
||||||
|
"totalAccessLists": hexutil.Uint64(progress.TotalAccessLists),
|
||||||
|
"trieGenProgress": hexutil.Uint64(progress.TrieGenProgress),
|
||||||
"txIndexFinishedBlocks": hexutil.Uint64(progress.TxIndexFinishedBlocks),
|
"txIndexFinishedBlocks": hexutil.Uint64(progress.TxIndexFinishedBlocks),
|
||||||
"txIndexRemainingBlocks": hexutil.Uint64(progress.TxIndexRemainingBlocks),
|
"txIndexRemainingBlocks": hexutil.Uint64(progress.TxIndexRemainingBlocks),
|
||||||
"stateIndexRemaining": hexutil.Uint64(progress.StateIndexRemaining),
|
"stateIndexRemaining": hexutil.Uint64(progress.StateIndexRemaining),
|
||||||
|
|
|
||||||
59
internal/memlimit/probe.go
Normal file
59
internal/memlimit/probe.go
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// Package memlimit detects the effective memory limit of the current
|
||||||
|
// process. On Linux the cgroup limit is consulted first, with total
|
||||||
|
// system memory as the fallback on all platforms.
|
||||||
|
package memlimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
gopsutil "github.com/shirou/gopsutil/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Source identifies which mechanism produced the limit value.
|
||||||
|
type Source int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SourceUnknown Source = iota
|
||||||
|
SourceCgroupV2
|
||||||
|
SourceCgroupV1
|
||||||
|
SourceSystem
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Source) String() string {
|
||||||
|
switch s {
|
||||||
|
case SourceCgroupV2:
|
||||||
|
return "cgroup-v2"
|
||||||
|
case SourceCgroupV1:
|
||||||
|
return "cgroup-v1"
|
||||||
|
case SourceSystem:
|
||||||
|
return "system"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit returns the memory limit visible to this process in bytes and
|
||||||
|
// the source that produced it.
|
||||||
|
func Limit() (bytes uint64, source Source) {
|
||||||
|
if v, src, ok := platformLimit(); ok {
|
||||||
|
return v, src
|
||||||
|
}
|
||||||
|
if mem, err := gopsutil.VirtualMemory(); err == nil {
|
||||||
|
return mem.Total, SourceSystem
|
||||||
|
}
|
||||||
|
return 0, SourceUnknown
|
||||||
|
}
|
||||||
155
internal/memlimit/probe_linux.go
Normal file
155
internal/memlimit/probe_linux.go
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// 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 linux
|
||||||
|
|
||||||
|
package memlimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cgroupV1UnlimitedThreshold detects the v1 "no limit" sentinel, which
|
||||||
|
// is LONG_MAX rounded down to the kernel page size. Anything above 1<<62
|
||||||
|
// is treated as unlimited regardless of page size.
|
||||||
|
const cgroupV1UnlimitedThreshold = uint64(1) << 62
|
||||||
|
|
||||||
|
// fileReader abstracts os.ReadFile for testing.
|
||||||
|
type fileReader func(path string) ([]byte, error)
|
||||||
|
|
||||||
|
// platformLimit returns the cgroup limit (v2 memory.max or v1
|
||||||
|
// memory.limit_in_bytes) of the current process. The cgroup limit is
|
||||||
|
// the authoritative budget in a container, where /proc/meminfo
|
||||||
|
// reports the host's RAM.
|
||||||
|
func platformLimit() (uint64, Source, bool) {
|
||||||
|
if v, ok := cgroupV2Limit(os.ReadFile); ok {
|
||||||
|
return v, SourceCgroupV2, true
|
||||||
|
}
|
||||||
|
if v, ok := cgroupV1Limit(os.ReadFile); ok {
|
||||||
|
return v, SourceCgroupV1, true
|
||||||
|
}
|
||||||
|
return 0, SourceUnknown, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// cgroupV2Limit reads the cgroup v2 memory.max for the current process.
|
||||||
|
// It probes /sys/fs/cgroup directly first (the effective root inside a
|
||||||
|
// cgroup-namespaced container), then the path from /proc/self/cgroup
|
||||||
|
// for the bare-metal case where the limit sits on a systemd slice.
|
||||||
|
func cgroupV2Limit(read fileReader) (uint64, bool) {
|
||||||
|
if v, ok := readCgroupV2At("/sys/fs/cgroup", "/", read); ok {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
procPath, ok := readProcSelfCgroupV2(read)
|
||||||
|
if !ok || procPath == "/" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return readCgroupV2At("/sys/fs/cgroup", procPath, read)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readCgroupV2At reads memory.max under root+rel, walking up parents
|
||||||
|
// until a numeric value is found or the path bottoms out.
|
||||||
|
func readCgroupV2At(root, rel string, read fileReader) (uint64, bool) {
|
||||||
|
// cgroup.controllers exists only on v2; if absent, v2 is not mounted here.
|
||||||
|
if _, err := read(path.Join(root, "cgroup.controllers")); err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
raw, err := read(path.Join(root, rel, "memory.max"))
|
||||||
|
if err == nil {
|
||||||
|
s := strings.TrimSpace(string(raw))
|
||||||
|
if s != "max" {
|
||||||
|
// Zero is legal to write but degenerate; treat it like
|
||||||
|
// "max" and keep walking up.
|
||||||
|
if n, err := strconv.ParseUint(s, 10, 64); err == nil && n != 0 {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rel == "/" || rel == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
rel = path.Dir(rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readProcSelfCgroupV2 returns the cgroup path from the v2 line
|
||||||
|
// ("0::<path>") of /proc/self/cgroup.
|
||||||
|
func readProcSelfCgroupV2(read fileReader) (string, bool) {
|
||||||
|
raw, err := read("/proc/self/cgroup")
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for line := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") {
|
||||||
|
// v2 unified line: "0::<path>"
|
||||||
|
if strings.HasPrefix(line, "0::") {
|
||||||
|
return strings.TrimPrefix(line, "0::"), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// cgroupV1Limit reads memory.limit_in_bytes from the v1 memory
|
||||||
|
// controller, walking up parents when a node reports the unlimited
|
||||||
|
// sentinel.
|
||||||
|
func cgroupV1Limit(read fileReader) (uint64, bool) {
|
||||||
|
rel, ok := readProcSelfCgroupV1Memory(read)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
root := "/sys/fs/cgroup/memory"
|
||||||
|
if _, err := read(path.Join(root, "memory.limit_in_bytes")); err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
raw, err := read(path.Join(root, rel, "memory.limit_in_bytes"))
|
||||||
|
if err == nil {
|
||||||
|
if n, err := strconv.ParseUint(strings.TrimSpace(string(raw)), 10, 64); err == nil {
|
||||||
|
if n != 0 && n < cgroupV1UnlimitedThreshold {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rel == "/" || rel == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
rel = path.Dir(rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readProcSelfCgroupV1Memory parses /proc/self/cgroup for the v1 memory
|
||||||
|
// controller line ("<id>:memory:<path>" or "<id>:...,memory,...:<path>").
|
||||||
|
func readProcSelfCgroupV1Memory(read fileReader) (string, bool) {
|
||||||
|
raw, err := read("/proc/self/cgroup")
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for line := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") {
|
||||||
|
// Format: "<hierarchy-id>:<comma-separated-controllers>:<path>"
|
||||||
|
parts := strings.SplitN(line, ":", 3)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for ctrl := range strings.SplitSeq(parts[1], ",") {
|
||||||
|
if ctrl == "memory" {
|
||||||
|
return parts[2], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
172
internal/memlimit/probe_linux_test.go
Normal file
172
internal/memlimit/probe_linux_test.go
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
// 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 linux
|
||||||
|
|
||||||
|
package memlimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeFS is a fileReader backed by an in-memory map. Missing keys
|
||||||
|
// return os.ErrNotExist.
|
||||||
|
type fakeFS map[string]string
|
||||||
|
|
||||||
|
func (f fakeFS) read(path string) ([]byte, error) {
|
||||||
|
v, ok := f[path]
|
||||||
|
if !ok {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
return []byte(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV2Container(t *testing.T) {
|
||||||
|
// Namespaced container (Docker default since 20.10): memory.max
|
||||||
|
// sits directly at the cgroup root.
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/cgroup.controllers": "memory cpu io",
|
||||||
|
"/sys/fs/cgroup/memory.max": "536870912",
|
||||||
|
"/proc/self/cgroup": "0::/",
|
||||||
|
}
|
||||||
|
bytes, ok := cgroupV2Limit(fs.read)
|
||||||
|
if !ok || bytes != 536870912 {
|
||||||
|
t.Errorf("got (%d, %v), want (536870912, true)", bytes, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV2Unlimited(t *testing.T) {
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/cgroup.controllers": "memory cpu io",
|
||||||
|
"/sys/fs/cgroup/memory.max": "max",
|
||||||
|
"/proc/self/cgroup": "0::/",
|
||||||
|
}
|
||||||
|
if _, ok := cgroupV2Limit(fs.read); ok {
|
||||||
|
t.Errorf("expected ok=false for fully unlimited v2 hierarchy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV2LimitOnAncestor(t *testing.T) {
|
||||||
|
// Bare-metal systemd: the leaf has no limit but the containing
|
||||||
|
// slice does.
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/cgroup.controllers": "memory cpu io",
|
||||||
|
"/sys/fs/cgroup/memory.max": "max",
|
||||||
|
"/sys/fs/cgroup/system.slice/memory.max": "8589934592",
|
||||||
|
"/sys/fs/cgroup/system.slice/geth.service/memory.max": "max",
|
||||||
|
"/proc/self/cgroup": "0::/system.slice/geth.service",
|
||||||
|
}
|
||||||
|
bytes, ok := cgroupV2Limit(fs.read)
|
||||||
|
if !ok || bytes != 8589934592 {
|
||||||
|
t.Errorf("got (%d, %v), want (8589934592, true)", bytes, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV2PrefersDirectRoot(t *testing.T) {
|
||||||
|
// In a namespaced container /proc/self/cgroup may show a host-side
|
||||||
|
// path; the direct probe at the root must win.
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/cgroup.controllers": "memory cpu io",
|
||||||
|
"/sys/fs/cgroup/memory.max": "536870912",
|
||||||
|
"/sys/fs/cgroup/system.slice/memory.max": "max",
|
||||||
|
"/proc/self/cgroup": "0::/system.slice/docker-abc.scope",
|
||||||
|
}
|
||||||
|
bytes, ok := cgroupV2Limit(fs.read)
|
||||||
|
if !ok || bytes != 536870912 {
|
||||||
|
t.Errorf("got (%d, ok=%v), want (536870912, true)", bytes, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV2ZeroWalksUp(t *testing.T) {
|
||||||
|
// memory.max="0" is legal but degenerate; walk up like "max".
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/cgroup.controllers": "memory cpu io",
|
||||||
|
"/sys/fs/cgroup/memory.max": "max",
|
||||||
|
"/sys/fs/cgroup/system.slice/memory.max": "536870912",
|
||||||
|
"/sys/fs/cgroup/system.slice/geth.service/memory.max": "0",
|
||||||
|
"/proc/self/cgroup": "0::/system.slice/geth.service",
|
||||||
|
}
|
||||||
|
bytes, ok := cgroupV2Limit(fs.read)
|
||||||
|
if !ok || bytes != 536870912 {
|
||||||
|
t.Errorf("got (%d, ok=%v), want (536870912, true)", bytes, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV1(t *testing.T) {
|
||||||
|
fs := fakeFS{
|
||||||
|
// no v2 hallmark file
|
||||||
|
"/sys/fs/cgroup/memory/memory.limit_in_bytes": "9223372036854771712",
|
||||||
|
"/sys/fs/cgroup/memory/docker/abc/memory.limit_in_bytes": "1073741824",
|
||||||
|
"/proc/self/cgroup": "12:memory:/docker/abc\n11:cpu:/docker/abc",
|
||||||
|
}
|
||||||
|
if _, ok := cgroupV2Limit(fs.read); ok {
|
||||||
|
t.Errorf("expected v2 probe to fail on a v1-only host")
|
||||||
|
}
|
||||||
|
bytes, ok := cgroupV1Limit(fs.read)
|
||||||
|
if !ok || bytes != 1073741824 {
|
||||||
|
t.Errorf("got (%d, %v), want (1073741824, true)", bytes, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV1Unlimited(t *testing.T) {
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/memory/memory.limit_in_bytes": "9223372036854771712",
|
||||||
|
"/sys/fs/cgroup/memory/docker/abc/memory.limit_in_bytes": "9223372036854771712",
|
||||||
|
"/proc/self/cgroup": "12:memory:/docker/abc",
|
||||||
|
}
|
||||||
|
if _, ok := cgroupV1Limit(fs.read); ok {
|
||||||
|
t.Errorf("expected ok=false when v1 reports the unlimited sentinel everywhere")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV1NonDefaultPageSize(t *testing.T) {
|
||||||
|
// The unlimited sentinel is LONG_MAX aligned to the local page
|
||||||
|
// size, so it differs on 16 KiB and 64 KiB page kernels.
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/memory/memory.limit_in_bytes": "9223372036854710272", // 64 KiB-page sentinel
|
||||||
|
"/sys/fs/cgroup/memory/foo/memory.limit_in_bytes": "9223372036854767616", // 16 KiB-page sentinel
|
||||||
|
"/proc/self/cgroup": "12:memory:/foo",
|
||||||
|
}
|
||||||
|
if _, ok := cgroupV1Limit(fs.read); ok {
|
||||||
|
t.Errorf("expected ok=false: both values are page-aligned LONG_MAX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupV1CombinedControllers(t *testing.T) {
|
||||||
|
// Some kernels list multiple controllers per line.
|
||||||
|
fs := fakeFS{
|
||||||
|
"/sys/fs/cgroup/memory/memory.limit_in_bytes": "9223372036854771712",
|
||||||
|
"/sys/fs/cgroup/memory/foo/memory.limit_in_bytes": "2147483648",
|
||||||
|
"/proc/self/cgroup": "8:cpu,memory,blkio:/foo",
|
||||||
|
}
|
||||||
|
bytes, ok := cgroupV1Limit(fs.read)
|
||||||
|
if !ok || bytes != 2147483648 {
|
||||||
|
t.Errorf("got (%d, ok=%v), want (2147483648, true)", bytes, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoCgroup(t *testing.T) {
|
||||||
|
fs := fakeFS{
|
||||||
|
"/proc/self/cgroup": "0::/",
|
||||||
|
}
|
||||||
|
if _, ok := cgroupV2Limit(fs.read); ok {
|
||||||
|
t.Errorf("expected v2 ok=false when v2 is not mounted")
|
||||||
|
}
|
||||||
|
if _, ok := cgroupV1Limit(fs.read); ok {
|
||||||
|
t.Errorf("expected v1 ok=false when v1 is not mounted")
|
||||||
|
}
|
||||||
|
}
|
||||||
25
internal/memlimit/probe_other.go
Normal file
25
internal/memlimit/probe_other.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// 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 !linux
|
||||||
|
|
||||||
|
package memlimit
|
||||||
|
|
||||||
|
// platformLimit reports no platform-specific limit on non-Linux
|
||||||
|
// systems; the caller falls back to total system memory.
|
||||||
|
func platformLimit() (uint64, Source, bool) {
|
||||||
|
return 0, SourceUnknown, false
|
||||||
|
}
|
||||||
28
internal/memlimit/probe_test.go
Normal file
28
internal/memlimit/probe_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
package memlimit
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// TestLimitSmoke asserts that Limit() returns a non-zero value,
|
||||||
|
// exercising the real probe and the gopsutil fallback.
|
||||||
|
func TestLimitSmoke(t *testing.T) {
|
||||||
|
bytes, src := Limit()
|
||||||
|
if bytes == 0 {
|
||||||
|
t.Errorf("Limit() returned 0 bytes (source=%s); expected non-zero on any sane host", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -123,7 +123,7 @@ func FormatSlogValue(v slog.Value, tmp []byte) (result []byte) {
|
||||||
var value any
|
var value any
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
|
if v := reflect.ValueOf(value); v.Kind() == reflect.Pointer && v.IsNil() {
|
||||||
result = []byte("<nil>")
|
result = []byte("<nil>")
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,7 @@ func (payload *Payload) updateSpanForDelivery(bSpan trace.Span) {
|
||||||
|
|
||||||
// BuildTestingPayload is for testing_buildBlockV*. It creates a block with the exact content given
|
// BuildTestingPayload is for testing_buildBlockV*. It creates a block with the exact content given
|
||||||
// by the parameters instead of using the locally available transactions.
|
// by the parameters instead of using the locally available transactions.
|
||||||
func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*types.Transaction, empty bool, extraData []byte) (*engine.ExecutionPayloadEnvelope, error) {
|
func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*types.Transaction, empty bool, extraData []byte) (*types.Block, *engine.ExecutionPayloadEnvelope, error) {
|
||||||
fullParams := &generateParams{
|
fullParams := &generateParams{
|
||||||
timestamp: args.Timestamp,
|
timestamp: args.Timestamp,
|
||||||
forceTime: true,
|
forceTime: true,
|
||||||
|
|
@ -358,7 +358,7 @@ func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*
|
||||||
}
|
}
|
||||||
res := miner.generateWork(context.Background(), fullParams, false)
|
res := miner.generateWork(context.Background(), fullParams, false)
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
return nil, res.err
|
return nil, nil, res.err
|
||||||
}
|
}
|
||||||
return engine.BlockToExecutableData(res.block, res.fees, res.sidecars, res.requests), nil
|
return res.block, engine.BlockToExecutableData(res.block, res.fees, res.sidecars, res.requests), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ func (it *lookupIterator) slowdown() {
|
||||||
if diff > minInterval {
|
if diff > minInterval {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wait := time.NewTimer(diff)
|
wait := time.NewTimer(minInterval - diff)
|
||||||
defer wait.Stop()
|
defer wait.Stop()
|
||||||
select {
|
select {
|
||||||
case <-wait.C:
|
case <-wait.C:
|
||||||
|
|
|
||||||
|
|
@ -290,28 +290,33 @@ func (tab *Table) refresh() <-chan struct{} {
|
||||||
// preferLive is true and the table contains any verified nodes, the result will not
|
// preferLive is true and the table contains any verified nodes, the result will not
|
||||||
// contain unverified nodes. However, if there are no verified nodes at all, the result
|
// contain unverified nodes. However, if there are no verified nodes at all, the result
|
||||||
// will contain unverified nodes.
|
// will contain unverified nodes.
|
||||||
func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance {
|
func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) (nodes nodesByDistance) {
|
||||||
|
nodes.target = target
|
||||||
tab.mutex.Lock()
|
tab.mutex.Lock()
|
||||||
defer tab.mutex.Unlock()
|
defer tab.mutex.Unlock()
|
||||||
|
|
||||||
// Scan all buckets. There might be a better way to do this, but there aren't that many
|
// Scan all buckets. There might be a better way to do this, but there aren't that many
|
||||||
// buckets, so this solution should be fine. The worst-case complexity of this loop
|
// buckets, so this solution should be fine. The worst-case complexity of this loop
|
||||||
// is O(tab.len() * nresults).
|
// is O(tab.len() * nresults).
|
||||||
nodes := &nodesByDistance{target: target}
|
if preferLive {
|
||||||
liveNodes := &nodesByDistance{target: target}
|
for _, b := range &tab.buckets {
|
||||||
for _, b := range &tab.buckets {
|
for _, n := range b.entries {
|
||||||
for _, n := range b.entries {
|
if n.isValidatedLive {
|
||||||
nodes.push(n.Node, nresults)
|
nodes.push(n.Node, nresults)
|
||||||
if preferLive && n.isValidatedLive {
|
}
|
||||||
liveNodes.push(n.Node, nresults)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(nodes.entries) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if preferLive && len(liveNodes.entries) > 0 {
|
for _, b := range &tab.buckets {
|
||||||
return liveNodes
|
for _, n := range b.entries {
|
||||||
|
nodes.push(n.Node, nresults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nodes
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendBucketNodes adds nodes at the given distance to the result slice.
|
// appendBucketNodes adds nodes at the given distance to the result slice.
|
||||||
|
|
|
||||||
|
|
@ -597,3 +597,77 @@ func newkey() *ecdsa.PrivateKey {
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BenchmarkTable_findnodeByID exercises findnodeByID across table sizes, result
|
||||||
|
// counts, and liveness ratios. The _PreferLive_NoLive cases cover the fallback
|
||||||
|
// path where preferLive is requested but no validated-live nodes exist.
|
||||||
|
func BenchmarkTable_findnodeByID(b *testing.B) {
|
||||||
|
benchmarks := []struct {
|
||||||
|
name string
|
||||||
|
tableSize int
|
||||||
|
nresults int
|
||||||
|
preferLive bool
|
||||||
|
liveRatio float64 // fraction of nodes marked validated-live
|
||||||
|
}{
|
||||||
|
{"SmallTable_5Results_NoPreferLive", 50, 5, false, 0.0},
|
||||||
|
{"SmallTable_16Results_NoPreferLive", 50, 16, false, 0.0},
|
||||||
|
{"SmallTable_5Results_PreferLive_AllLive", 50, 5, true, 1.0},
|
||||||
|
{"SmallTable_16Results_PreferLive_AllLive", 50, 16, true, 1.0},
|
||||||
|
{"SmallTable_5Results_PreferLive_HalfLive", 50, 5, true, 0.5},
|
||||||
|
{"SmallTable_16Results_PreferLive_HalfLive", 50, 16, true, 0.5},
|
||||||
|
{"SmallTable_5Results_PreferLive_NoLive", 50, 5, true, 0.0},
|
||||||
|
{"SmallTable_16Results_PreferLive_NoLive", 50, 16, true, 0.0},
|
||||||
|
|
||||||
|
{"MediumTable_5Results_NoPreferLive", 200, 5, false, 0.0},
|
||||||
|
{"MediumTable_16Results_NoPreferLive", 200, 16, false, 0.0},
|
||||||
|
{"MediumTable_5Results_PreferLive_AllLive", 200, 5, true, 1.0},
|
||||||
|
{"MediumTable_16Results_PreferLive_AllLive", 200, 16, true, 1.0},
|
||||||
|
{"MediumTable_5Results_PreferLive_HalfLive", 200, 5, true, 0.5},
|
||||||
|
{"MediumTable_16Results_PreferLive_HalfLive", 200, 16, true, 0.5},
|
||||||
|
{"MediumTable_5Results_PreferLive_NoLive", 200, 5, true, 0.0},
|
||||||
|
{"MediumTable_16Results_PreferLive_NoLive", 200, 16, true, 0.0},
|
||||||
|
|
||||||
|
{"FullTable_5Results_NoPreferLive", nBuckets * bucketSize, 5, false, 0.0},
|
||||||
|
{"FullTable_16Results_NoPreferLive", nBuckets * bucketSize, 16, false, 0.0},
|
||||||
|
{"FullTable_5Results_PreferLive_AllLive", nBuckets * bucketSize, 5, true, 1.0},
|
||||||
|
{"FullTable_16Results_PreferLive_AllLive", nBuckets * bucketSize, 16, true, 1.0},
|
||||||
|
{"FullTable_5Results_PreferLive_HalfLive", nBuckets * bucketSize, 5, true, 0.5},
|
||||||
|
{"FullTable_16Results_PreferLive_HalfLive", nBuckets * bucketSize, 16, true, 0.5},
|
||||||
|
{"FullTable_5Results_PreferLive_NoLive", nBuckets * bucketSize, 5, true, 0.0},
|
||||||
|
{"FullTable_16Results_PreferLive_NoLive", nBuckets * bucketSize, 16, true, 0.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bm := range benchmarks {
|
||||||
|
b.Run(bm.name, func(b *testing.B) {
|
||||||
|
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||||
|
defer db.Close()
|
||||||
|
defer tab.close()
|
||||||
|
<-tab.initDone
|
||||||
|
|
||||||
|
self := tab.self().ID()
|
||||||
|
nodes := make([]*enode.Node, bm.tableSize)
|
||||||
|
for i := range nodes {
|
||||||
|
// Spread across buckets by varying log-distance in the valid range.
|
||||||
|
d := bucketMinDistance + 1 + i%nBuckets
|
||||||
|
nodes[i] = nodeAtDistance(self, d, intIP(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
liveCount := int(float64(len(nodes)) * bm.liveRatio)
|
||||||
|
if liveCount > 0 {
|
||||||
|
fillTable(tab, nodes[:liveCount], true)
|
||||||
|
}
|
||||||
|
if liveCount < len(nodes) {
|
||||||
|
fillTable(tab, nodes[liveCount:], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var target enode.ID
|
||||||
|
rand.Read(target[:])
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = tab.findnodeByID(target, bm.nresults, bm.preferLive)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,10 @@ func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire
|
||||||
|
|
||||||
// RequestENR sends ENRRequest to the given node and waits for a response.
|
// RequestENR sends ENRRequest to the given node and waits for a response.
|
||||||
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||||
addr, _ := n.UDPEndpoint()
|
addr, ok := n.UDPEndpoint()
|
||||||
|
if !ok {
|
||||||
|
return nil, errNoUDPEndpoint
|
||||||
|
}
|
||||||
t.ensureBond(n.ID(), addr)
|
t.ensureBond(n.ID(), addr)
|
||||||
|
|
||||||
req := &v4wire.ENRRequest{
|
req := &v4wire.ENRRequest{
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,23 @@ func TestUDPv4_pingTimeout(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUDPv4_RequestENRNoUDPEndpoint verifies that RequestENR returns a clean
|
||||||
|
// errNoUDPEndpoint error for a node without a usable UDP endpoint, instead of
|
||||||
|
// silently sending a packet to the zero address and waiting for a timeout.
|
||||||
|
// This mirrors the existing guards in ping/Ping/findnode.
|
||||||
|
func TestUDPv4_RequestENRNoUDPEndpoint(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test := newUDPTest(t)
|
||||||
|
defer test.close()
|
||||||
|
|
||||||
|
key := newkey()
|
||||||
|
// UDP port 0 makes UDPEndpoint return ok=false.
|
||||||
|
node := enode.NewV4(&key.PublicKey, net.ParseIP("1.2.3.4"), 2222, 0)
|
||||||
|
if _, err := test.udp.RequestENR(node); !errors.Is(err, errNoUDPEndpoint) {
|
||||||
|
t.Errorf("expected errNoUDPEndpoint, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testPacket byte
|
type testPacket byte
|
||||||
|
|
||||||
func (req testPacket) Kind() byte { return byte(req) }
|
func (req testPacket) Kind() byte { return byte(req) }
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ const (
|
||||||
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
|
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
|
||||||
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
|
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
|
||||||
Create2Gas uint64 = 32000 // Once per CREATE2 operation
|
Create2Gas uint64 = 32000 // Once per CREATE2 operation
|
||||||
|
CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately.
|
||||||
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
|
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
|
||||||
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
|
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
|
||||||
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
|
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
|
||||||
|
|
@ -100,6 +101,7 @@ const (
|
||||||
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
|
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
|
||||||
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
|
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
|
||||||
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
|
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
|
||||||
|
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
|
||||||
|
|
||||||
// These have been changed during the course of the chain
|
// These have been changed during the course of the chain
|
||||||
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
|
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
|
||||||
|
|
@ -196,6 +198,12 @@ const (
|
||||||
// the bound has a small safety margin for system-contract accesses that
|
// the bound has a small safety margin for system-contract accesses that
|
||||||
// don't consume block gas.
|
// don't consume block gas.
|
||||||
BALItemCost uint64 = 2000
|
BALItemCost uint64 = 2000
|
||||||
|
|
||||||
|
AccountCreationSize = 120
|
||||||
|
StorageCreationSize = 64
|
||||||
|
AuthorizationCreationSize = 23
|
||||||
|
CostPerStateByte = 1530
|
||||||
|
SystemMaxSStoresPerCall = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation
|
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error)
|
||||||
return decodeU256, nil
|
return decodeU256, nil
|
||||||
case typ == u256Int:
|
case typ == u256Int:
|
||||||
return decodeU256NoPtr, nil
|
return decodeU256NoPtr, nil
|
||||||
case kind == reflect.Ptr:
|
case kind == reflect.Pointer:
|
||||||
return makePtrDecoder(typ, tags)
|
return makePtrDecoder(typ, tags)
|
||||||
case reflect.PointerTo(typ).Implements(decoderInterface):
|
case reflect.PointerTo(typ).Implements(decoderInterface):
|
||||||
return decodeDecoder, nil
|
return decodeDecoder, nil
|
||||||
|
|
@ -936,7 +936,7 @@ func (s *Stream) Decode(val interface{}) error {
|
||||||
}
|
}
|
||||||
rval := reflect.ValueOf(val)
|
rval := reflect.ValueOf(val)
|
||||||
rtyp := rval.Type()
|
rtyp := rval.Type()
|
||||||
if rtyp.Kind() != reflect.Ptr {
|
if rtyp.Kind() != reflect.Pointer {
|
||||||
return errNoPointer
|
return errNoPointer
|
||||||
}
|
}
|
||||||
if rval.IsNil() {
|
if rval.IsNil() {
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) {
|
||||||
return writeU256IntPtr, nil
|
return writeU256IntPtr, nil
|
||||||
case typ == u256Int:
|
case typ == u256Int:
|
||||||
return writeU256IntNoPtr, nil
|
return writeU256IntNoPtr, nil
|
||||||
case kind == reflect.Ptr:
|
case kind == reflect.Pointer:
|
||||||
return makePtrWriter(typ, ts)
|
return makePtrWriter(typ, ts)
|
||||||
case reflect.PointerTo(typ).Implements(encoderInterface):
|
case reflect.PointerTo(typ).Implements(encoderInterface):
|
||||||
return makeEncoderWriter(typ), nil
|
return makeEncoderWriter(typ), nil
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ func parseTag(field Field, lastPublic int) (Tags, error) {
|
||||||
ts.Ignored = true
|
ts.Ignored = true
|
||||||
case "nil", "nilString", "nilList":
|
case "nil", "nilString", "nilList":
|
||||||
ts.NilOK = true
|
ts.NilOK = true
|
||||||
if field.Type.Kind != reflect.Ptr {
|
if field.Type.Kind != reflect.Pointer {
|
||||||
return ts, TagError{Field: name, Tag: t, Err: "field is not a pointer"}
|
return ts, TagError{Field: name, Tag: t, Err: "field is not a pointer"}
|
||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func typeReflectKind(typ types.Type) reflect.Kind {
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
return reflect.Map
|
return reflect.Map
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
return reflect.Ptr
|
return reflect.Pointer
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
return reflect.Func
|
return reflect.Func
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ func rtypeToStructType(typ reflect.Type, rec map[reflect.Type]*rlpstruct.Type) *
|
||||||
IsDecoder: typ.Implements(decoderInterface),
|
IsDecoder: typ.Implements(decoderInterface),
|
||||||
}
|
}
|
||||||
rec[typ] = t
|
rec[typ] = t
|
||||||
if k == reflect.Array || k == reflect.Slice || k == reflect.Ptr {
|
if k == reflect.Array || k == reflect.Slice || k == reflect.Pointer {
|
||||||
t.Elem = rtypeToStructType(typ.Elem(), rec)
|
t.Elem = rtypeToStructType(typ.Elem(), rec)
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er
|
||||||
// The result must be a pointer so that package json can unmarshal into it. You
|
// The result must be a pointer so that package json can unmarshal into it. You
|
||||||
// can also pass nil, in which case the result is ignored.
|
// can also pass nil, in which case the result is ignored.
|
||||||
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||||
if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr {
|
if result != nil && reflect.TypeOf(result).Kind() != reflect.Pointer {
|
||||||
return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result)
|
return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result)
|
||||||
}
|
}
|
||||||
msg, err := c.newMessage(method, args...)
|
msg, err := c.newMessage(method, args...)
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]
|
||||||
}
|
}
|
||||||
// Set any missing args to nil.
|
// Set any missing args to nil.
|
||||||
for i := len(args); i < len(types); i++ {
|
for i := len(args); i < len(types); i++ {
|
||||||
if types[i].Kind() != reflect.Ptr {
|
if types[i].Kind() != reflect.Pointer {
|
||||||
return nil, fmt.Errorf("missing value for required argument %d", i)
|
return nil, fmt.Errorf("missing value for required argument %d", i)
|
||||||
}
|
}
|
||||||
args = append(args, reflect.Zero(types[i]))
|
args = append(args, reflect.Zero(types[i]))
|
||||||
|
|
@ -425,7 +425,7 @@ func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Valu
|
||||||
if err := dec.Decode(argval.Interface()); err != nil {
|
if err := dec.Decode(argval.Interface()); err != nil {
|
||||||
return args, fmt.Errorf("invalid argument %d: %v", i, err)
|
return args, fmt.Errorf("invalid argument %d: %v", i, err)
|
||||||
}
|
}
|
||||||
if argval.IsNil() && types[i].Kind() != reflect.Ptr {
|
if argval.IsNil() && types[i].Kind() != reflect.Pointer {
|
||||||
return args, fmt.Errorf("missing value for required argument %d", i)
|
return args, fmt.Errorf("missing value for required argument %d", i)
|
||||||
}
|
}
|
||||||
args = append(args, argval.Elem())
|
args = append(args, argval.Elem())
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ func isErrorType(t reflect.Type) bool {
|
||||||
|
|
||||||
// Is t Subscription or *Subscription?
|
// Is t Subscription or *Subscription?
|
||||||
func isSubscriptionType(t reflect.Type) bool {
|
func isSubscriptionType(t reflect.Type) bool {
|
||||||
for t.Kind() == reflect.Ptr {
|
for t.Kind() == reflect.Pointer {
|
||||||
t = t.Elem()
|
t = t.Elem()
|
||||||
}
|
}
|
||||||
return t == subscriptionType
|
return t == subscriptionType
|
||||||
|
|
|
||||||
|
|
@ -346,17 +346,27 @@ func TestTracingSubscribeUnsubscribe(t *testing.T) {
|
||||||
// like notifications (no "id" field).
|
// like notifications (no "id" field).
|
||||||
func postJSONRPC(t *testing.T, url, body string) {
|
func postJSONRPC(t *testing.T, url, body string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
if err := tryPostJSONRPC(url, body); err != nil {
|
||||||
|
t.Fatalf("request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryPostJSONRPC is like postJSONRPC but returns the transport error instead of
|
||||||
|
// failing the test. The write-timeout test uses this because the HTTP
|
||||||
|
// WriteTimeout can drop the connection before the response is flushed.
|
||||||
|
func tryPostJSONRPC(url, body string) error {
|
||||||
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body))
|
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("new request: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("request: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
_, _ = io.Copy(io.Discard, resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTracingHTTPNotification verifies that a JSON-RPC notification emits the
|
// TestTracingHTTPNotification verifies that a JSON-RPC notification emits the
|
||||||
|
|
@ -630,7 +640,12 @@ func TestTracingHTTPTimeout(t *testing.T) {
|
||||||
// test_block waits on ctx.Done() and returns an error. The internal
|
// test_block waits on ctx.Done() and returns an error. The internal
|
||||||
// timer cancels ctx, so test_block unblocks shortly after the timeout
|
// timer cancels ctx, so test_block unblocks shortly after the timeout
|
||||||
// response goes out.
|
// response goes out.
|
||||||
postJSONRPC(t, httpsrv.URL, `{"jsonrpc":"2.0","id":1,"method":"test_block"}`)
|
//
|
||||||
|
// Ignore the client-side result. Under load the HTTP WriteTimeout can
|
||||||
|
// drop the connection before the timeout response is flushed, which the
|
||||||
|
// client sees as EOF. The server still records the timeout on its span,
|
||||||
|
// which is what we assert below.
|
||||||
|
_ = tryPostJSONRPC(httpsrv.URL, `{"jsonrpc":"2.0","id":1,"method":"test_block"}`)
|
||||||
|
|
||||||
// Wait for the in-flight request to finish so the deferred spanEnd fires
|
// Wait for the in-flight request to finish so the deferred spanEnd fires
|
||||||
// before GetSpans is called.
|
// before GetSpans is called.
|
||||||
|
|
|
||||||
|
|
@ -325,10 +325,10 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
initialGas := vm.NewGasBudget(msg.GasLimit)
|
initialGas := vm.NewGasBudget(msg.GasLimit, 0)
|
||||||
|
|
||||||
// Execute the message.
|
// Execute the message.
|
||||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), msg.Value)
|
_, result, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas, msg.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
return
|
return
|
||||||
|
|
@ -337,7 +337,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
elapsed += uint64(time.Since(start))
|
elapsed += uint64(time.Since(start))
|
||||||
refund += state.StateDB.GetRefund()
|
refund += state.StateDB.GetRefund()
|
||||||
gasUsed += leftOverGas.Used(initialGas)
|
gasUsed += result.Used(initialGas)
|
||||||
|
|
||||||
state.StateDB.RevertToSnapshot(snapshot)
|
state.StateDB.RevertToSnapshot(snapshot)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ func (tt *TransactionTest) Run() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Intrinsic gas
|
// Intrinsic cost
|
||||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,21 @@ const (
|
||||||
partitionFinished = ^uint64(0)
|
partitionFinished = ^uint64(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// genCounters bundles the progress counters threaded through a GenerateTrie run.
|
||||||
|
type genCounters struct {
|
||||||
|
accounts atomic.Int64 // accounts scanned
|
||||||
|
slots atomic.Int64 // storage slots scanned
|
||||||
|
accountUpdated atomic.Int64 // accounts whose stale storage Root was rewritten
|
||||||
|
storageDeleted atomic.Int64 // dangling storage slots removed
|
||||||
|
|
||||||
|
accountTrieNodes atomic.Int64 // generated account trie nodes
|
||||||
|
accountTrieBytes atomic.Int64 // generated account trie bytes
|
||||||
|
storageTrieNodes atomic.Int64 // generated storage trie nodes
|
||||||
|
storageTrieBytes atomic.Int64 // generated storage trie bytes
|
||||||
|
|
||||||
|
progress [numPartitions]atomic.Uint64 // per-partition keyspace position
|
||||||
|
}
|
||||||
|
|
||||||
// rangeIterators bundles the per-partition account and storage iterators.
|
// rangeIterators bundles the per-partition account and storage iterators.
|
||||||
type rangeIterators struct {
|
type rangeIterators struct {
|
||||||
db ethdb.Database
|
db ethdb.Database
|
||||||
|
|
@ -134,7 +149,7 @@ func reopenFlatIterator(db ethdb.Database, old *internal.HoldableIterator, prefi
|
||||||
// both per-account storage subtries and the partition's slice of the
|
// both per-account storage subtries and the partition's slice of the
|
||||||
// account trie. Returns the partition's stripped subtree root blob, or
|
// account trie. Returns the partition's stripped subtree root blob, or
|
||||||
// nil if the partition had no accounts at all.
|
// nil if the partition had no accounts at all.
|
||||||
func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Database, scheme string, partition byte, rangeStart, rangeEnd common.Hash, scanned, updated, deleted *atomic.Int64, pos *atomic.Uint64) ([]byte, error) {
|
func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Database, scheme string, partition byte, rangeStart, rangeEnd common.Hash, c *genCounters) ([]byte, error) {
|
||||||
iters := openRangeIterators(db, rangeStart)
|
iters := openRangeIterators(db, rangeStart)
|
||||||
defer iters.release()
|
defer iters.release()
|
||||||
|
|
||||||
|
|
@ -154,6 +169,13 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
if len(path) == 1 {
|
if len(path) == 1 {
|
||||||
root = common.CopyBytes(blob)
|
root = common.CopyBytes(blob)
|
||||||
}
|
}
|
||||||
|
c.accountTrieNodes.Add(1)
|
||||||
|
|
||||||
|
if scheme == rawdb.PathScheme {
|
||||||
|
c.accountTrieBytes.Add(int64(len(path) + len(blob)))
|
||||||
|
} else {
|
||||||
|
c.accountTrieBytes.Add(int64(common.HashLength + len(blob)))
|
||||||
|
}
|
||||||
rawdb.WriteTrieNode(batch, common.Hash{}, path, hash, blob, scheme)
|
rawdb.WriteTrieNode(batch, common.Hash{}, path, hash, blob, scheme)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -172,8 +194,8 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
if bytes.Compare(accountHash[:], rangeEnd[:]) > 0 {
|
if bytes.Compare(accountHash[:], rangeEnd[:]) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
scanned.Add(1)
|
c.accounts.Add(1)
|
||||||
pos.Store(binary.BigEndian.Uint64(accountHash[:8]))
|
c.progress[partition].Store(binary.BigEndian.Uint64(accountHash[:8]))
|
||||||
|
|
||||||
// Decode the account object
|
// Decode the account object
|
||||||
account, err := types.FullAccount(iters.acct.Value())
|
account, err := types.FullAccount(iters.acct.Value())
|
||||||
|
|
@ -184,6 +206,13 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
// Build the account's storage trie from the flat storage snapshot.
|
// Build the account's storage trie from the flat storage snapshot.
|
||||||
// StackTrie's onTrieNode callback persists nodes as they finalize.
|
// StackTrie's onTrieNode callback persists nodes as they finalize.
|
||||||
storageTrie := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
storageTrie := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||||
|
c.storageTrieNodes.Add(1)
|
||||||
|
|
||||||
|
if scheme == rawdb.PathScheme {
|
||||||
|
c.storageTrieBytes.Add(int64(len(path) + common.HashLength + len(blob)))
|
||||||
|
} else {
|
||||||
|
c.storageTrieBytes.Add(int64(common.HashLength + len(blob)))
|
||||||
|
}
|
||||||
rawdb.WriteTrieNode(batch, accountHash, path, hash, blob, scheme)
|
rawdb.WriteTrieNode(batch, accountHash, path, hash, blob, scheme)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -213,7 +242,7 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
copy(lastDanglingAccount, storageAccount)
|
copy(lastDanglingAccount, storageAccount)
|
||||||
log.Error("Unexpected storage entries for dangling account", "expected", accountHash, "got", common.BytesToHash(storageAccount))
|
log.Error("Unexpected storage entries for dangling account", "expected", accountHash, "got", common.BytesToHash(storageAccount))
|
||||||
}
|
}
|
||||||
deleted.Add(1)
|
c.storageDeleted.Add(1)
|
||||||
slotHash := sk[len(rawdb.SnapshotStoragePrefix)+common.HashLength:]
|
slotHash := sk[len(rawdb.SnapshotStoragePrefix)+common.HashLength:]
|
||||||
rawdb.DeleteStorageSnapshot(batch, common.BytesToHash(storageAccount), common.BytesToHash(slotHash))
|
rawdb.DeleteStorageSnapshot(batch, common.BytesToHash(storageAccount), common.BytesToHash(slotHash))
|
||||||
if err := iters.flushIfFull(batch, "dangling"); err != nil {
|
if err := iters.flushIfFull(batch, "dangling"); err != nil {
|
||||||
|
|
@ -237,6 +266,7 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
if err := storageTrie.Update(slotHash, iters.stor.Value()); err != nil {
|
if err := storageTrie.Update(slotHash, iters.stor.Value()); err != nil {
|
||||||
return nil, fmt.Errorf("storage stack trie update for %x: %w", accountHash, err)
|
return nil, fmt.Errorf("storage stack trie update for %x: %w", accountHash, err)
|
||||||
}
|
}
|
||||||
|
c.slots.Add(1)
|
||||||
if err := iters.flushIfFull(batch, "storage"); err != nil {
|
if err := iters.flushIfFull(batch, "storage"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +282,7 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
if computed != account.Root {
|
if computed != account.Root {
|
||||||
account.Root = computed
|
account.Root = computed
|
||||||
rawdb.WriteAccountSnapshot(batch, accountHash, types.SlimAccountRLP(*account))
|
rawdb.WriteAccountSnapshot(batch, accountHash, types.SlimAccountRLP(*account))
|
||||||
updated.Add(1)
|
c.accountUpdated.Add(1)
|
||||||
}
|
}
|
||||||
fullAccount, err := rlp.EncodeToBytes(account)
|
fullAccount, err := rlp.EncodeToBytes(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -291,7 +321,7 @@ func generatePartition(ctx context.Context, cancel <-chan struct{}, db ethdb.Dat
|
||||||
copy(lastDanglingTail, acct)
|
copy(lastDanglingTail, acct)
|
||||||
log.Error("Unexpected storage entries for dangling account", "addrhash", common.BytesToHash(acct))
|
log.Error("Unexpected storage entries for dangling account", "addrhash", common.BytesToHash(acct))
|
||||||
}
|
}
|
||||||
deleted.Add(1)
|
c.storageDeleted.Add(1)
|
||||||
slotHash := sk[len(rawdb.SnapshotStoragePrefix)+common.HashLength:]
|
slotHash := sk[len(rawdb.SnapshotStoragePrefix)+common.HashLength:]
|
||||||
rawdb.DeleteStorageSnapshot(batch, common.BytesToHash(acct), common.BytesToHash(slotHash))
|
rawdb.DeleteStorageSnapshot(batch, common.BytesToHash(acct), common.BytesToHash(slotHash))
|
||||||
if err := iters.flushIfFull(batch, "dangling tail"); err != nil {
|
if err := iters.flushIfFull(batch, "dangling tail"); err != nil {
|
||||||
|
|
@ -337,7 +367,7 @@ func hashRanges(total int) [][2]common.Hash {
|
||||||
return ranges
|
return ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateTrie rebuilds all tries (storage + account) from flat snapshot
|
// GenerateTrie builds all tries (storage + account) from flat snapshot
|
||||||
// data in the database. The account hash space is partitioned into 16
|
// data in the database. The account hash space is partitioned into 16
|
||||||
// slices aligned with the first-nibble branching of the MPT root. Each
|
// slices aligned with the first-nibble branching of the MPT root. Each
|
||||||
// partition is processed by its own goroutine, which walks its slice,
|
// partition is processed by its own goroutine, which walks its slice,
|
||||||
|
|
@ -346,29 +376,28 @@ func hashRanges(total int) [][2]common.Hash {
|
||||||
// trie. Once every partition has produced its subtree root, the top-level
|
// trie. Once every partition has produced its subtree root, the top-level
|
||||||
// branch is assembled and its hash verified against the expected root.
|
// branch is assembled and its hash verified against the expected root.
|
||||||
//
|
//
|
||||||
// Resume: on entry, any partition that has a "done" marker from a
|
// Generation is all or nothing: an interrupted run leaves no resume
|
||||||
// previous run is skipped. Its subtree blob is read from the marker
|
// state and the next run builds every partition from scratch.
|
||||||
// and handed to assembleRoot directly. On a mid-run crash, only the
|
|
||||||
// in-flight partition(s) are redone.
|
|
||||||
func GenerateTrie(db ethdb.Database, scheme string, root common.Hash, cancel <-chan struct{}) (GenerateStats, error) {
|
func GenerateTrie(db ethdb.Database, scheme string, root common.Hash, cancel <-chan struct{}) (GenerateStats, error) {
|
||||||
|
return GenerateTrieWithProgress(db, scheme, root, cancel, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateTrieWithProgress is GenerateTrie with live progress reporting.
|
||||||
|
func GenerateTrieWithProgress(db ethdb.Database, scheme string, root common.Hash, cancel <-chan struct{}, prog *atomic.Uint64) (GenerateStats, error) {
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
scanned atomic.Int64
|
c genCounters
|
||||||
updated atomic.Int64
|
|
||||||
deleted atomic.Int64
|
|
||||||
progress [numPartitions]atomic.Uint64
|
|
||||||
progressDone = make(chan struct{})
|
progressDone = make(chan struct{})
|
||||||
|
|
||||||
// partitionBlobs[i] holds the root node for partition i, or nil if
|
// partitionBlobs[i] holds the root node for partition i, or nil if
|
||||||
// the partition is empty.
|
// the partition is empty.
|
||||||
partitionBlobs [numPartitions][]byte
|
partitionBlobs [numPartitions][]byte
|
||||||
)
|
)
|
||||||
go tickProgress(progressDone, start, &scanned, &updated, &progress)
|
go tickProgress(progressDone, start, &c, prog)
|
||||||
defer close(progressDone)
|
defer close(progressDone)
|
||||||
|
|
||||||
// For each partition, either skip (prior done marker found) or run
|
// Run every partition concurrently, each producing the subtree root
|
||||||
// it. Prior runs can leave the partition's raw root blob in the done
|
// blob that assembleRoot needs.
|
||||||
// marker. We recover it here so assembleRoot has everything it needs.
|
|
||||||
var (
|
var (
|
||||||
ranges = hashRanges(numPartitions)
|
ranges = hashRanges(numPartitions)
|
||||||
eg, ctx = errgroup.WithContext(context.Background())
|
eg, ctx = errgroup.WithContext(context.Background())
|
||||||
|
|
@ -376,26 +405,16 @@ func GenerateTrie(db ethdb.Database, scheme string, root common.Hash, cancel <-c
|
||||||
for i, r := range ranges {
|
for i, r := range ranges {
|
||||||
partition := byte(i)
|
partition := byte(i)
|
||||||
rangeStart, rangeEnd := r[0], r[1]
|
rangeStart, rangeEnd := r[0], r[1]
|
||||||
if blob, ok := rawdb.ReadGenerateTriePartitionDone(db, partition); ok {
|
|
||||||
partitionBlobs[partition] = blob
|
|
||||||
progress[partition].Store(partitionFinished)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
blob, err := generatePartition(ctx, cancel, db, scheme, partition, rangeStart, rangeEnd, &scanned, &updated, &deleted, &progress[partition])
|
blob, err := generatePartition(ctx, cancel, db, scheme, partition, rangeStart, rangeEnd, &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("Partition done", "partition", partition, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Info("Partition done", "partition", partition, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
|
||||||
progress[partition].Store(partitionFinished)
|
c.progress[partition].Store(partitionFinished)
|
||||||
partitionBlobs[partition] = blob
|
partitionBlobs[partition] = blob
|
||||||
|
|
||||||
// Record completion only after the partition's batch has
|
|
||||||
// flushed inside generatePartition, so this marker appears
|
|
||||||
// on disk only when every write the partition did is durable.
|
|
||||||
rawdb.WriteGenerateTriePartitionDone(db, partition, blob)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -404,10 +423,11 @@ func GenerateTrie(db ethdb.Database, scheme string, root common.Hash, cancel <-c
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
return GenerateStats{}, err
|
return GenerateStats{}, err
|
||||||
}
|
}
|
||||||
|
if prog != nil {
|
||||||
// Assemble the top-level root from the partition blobs, verify it
|
prog.Store(100)
|
||||||
// matches the expected root, and clear all partition markers on
|
}
|
||||||
// success.
|
// Assemble the top-level root from the partition blobs and verify it
|
||||||
|
// matches the expected root.
|
||||||
got, err := assembleRoot(db, scheme, partitionBlobs)
|
got, err := assembleRoot(db, scheme, partitionBlobs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GenerateStats{}, fmt.Errorf("assemble root: %w", err)
|
return GenerateStats{}, fmt.Errorf("assemble root: %w", err)
|
||||||
|
|
@ -415,20 +435,17 @@ func GenerateTrie(db ethdb.Database, scheme string, root common.Hash, cancel <-c
|
||||||
if got != root {
|
if got != root {
|
||||||
return GenerateStats{}, fmt.Errorf("state root mismatch: got %x, want %x", got, root)
|
return GenerateStats{}, fmt.Errorf("state root mismatch: got %x, want %x", got, root)
|
||||||
}
|
}
|
||||||
|
log.Info("Generated state trie",
|
||||||
|
"accounts", c.accounts.Load(), "slots", c.slots.Load(),
|
||||||
|
"account-nodes", c.accountTrieNodes.Load(), "storage-nodes", c.storageTrieNodes.Load(),
|
||||||
|
"account-nodebytes", common.StorageSize(c.accountTrieBytes.Load()), "storage-nodebytes", common.StorageSize(c.storageTrieBytes.Load()),
|
||||||
|
"updated-accounts", c.accountUpdated.Load(), "dangling-slots", c.storageDeleted.Load(),
|
||||||
|
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
|
||||||
// Clear the partition progress marker, ending the generation process.
|
|
||||||
batch := db.NewBatch()
|
|
||||||
for i := range numPartitions {
|
|
||||||
rawdb.DeleteGenerateTriePartitionDone(batch, byte(i))
|
|
||||||
}
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return GenerateStats{}, fmt.Errorf("clear partition markers: %w", err)
|
|
||||||
}
|
|
||||||
log.Info("Generated state trie", "scanned", scanned.Load(), "updated", updated.Load(), "dangling-slots", deleted.Load(), "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
return GenerateStats{
|
return GenerateStats{
|
||||||
Scanned: scanned.Load(),
|
Scanned: c.accounts.Load(),
|
||||||
Updated: updated.Load(),
|
Updated: c.accountUpdated.Load(),
|
||||||
Deleted: deleted.Load(),
|
Deleted: c.storageDeleted.Load(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,25 +523,32 @@ func assembleRoot(db ethdb.Database, scheme string, partitionBlobs [numPartition
|
||||||
|
|
||||||
// tickProgress logs an aggregate progress line every 30 seconds until done
|
// tickProgress logs an aggregate progress line every 30 seconds until done
|
||||||
// is closed. Cheap: a handful of atomic loads and one log line per tick.
|
// is closed. Cheap: a handful of atomic loads and one log line per tick.
|
||||||
func tickProgress(done <-chan struct{}, start time.Time, scanned, updated *atomic.Int64, progress *[numPartitions]atomic.Uint64) {
|
func tickProgress(done <-chan struct{}, start time.Time, c *genCounters, prog *atomic.Uint64) {
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
fraction := progressFraction(progress)
|
fraction := progressFraction(&c.progress)
|
||||||
|
|
||||||
|
// Notify the external subscriber about the generation progress
|
||||||
|
if prog != nil {
|
||||||
|
prog.Store(uint64(100 * fraction))
|
||||||
|
}
|
||||||
eta := "n/a"
|
eta := "n/a"
|
||||||
if fraction > 0.005 {
|
if fraction > 0.005 {
|
||||||
eta = common.PrettyDuration(time.Duration(float64(elapsed) * (1.0/fraction - 1.0))).String()
|
eta = common.PrettyDuration(time.Duration(float64(elapsed) * (1.0/fraction - 1.0))).String()
|
||||||
}
|
}
|
||||||
log.Info("Generating trie",
|
log.Info("Generating trie",
|
||||||
"progress", fmt.Sprintf("%.1f%%", fraction*100), "eta", eta,
|
"progress", fmt.Sprintf("%.1f%%", fraction*100), "eta", eta,
|
||||||
"scanned", scanned.Load(), "updated", updated.Load(),
|
"accounts", c.accounts.Load(), "slots", c.slots.Load(),
|
||||||
|
"account-updated", c.accountUpdated.Load(), "dangling-slots", c.storageDeleted.Load(),
|
||||||
"elapsed", common.PrettyDuration(elapsed),
|
"elapsed", common.PrettyDuration(elapsed),
|
||||||
"acct/s", uint64(float64(scanned.Load())/elapsed.Seconds()))
|
"acct/s", uint64(float64(c.accounts.Load())/elapsed.Seconds()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -370,97 +369,6 @@ func TestGenerateTrieOrphanStorage(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGenerateTriePartialResume proves that the resume path actually
|
|
||||||
// fires when a partition's done marker is present.
|
|
||||||
func TestGenerateTriePartialResume(t *testing.T) {
|
|
||||||
// Build the account set. Empty storage keeps the test focused on the
|
|
||||||
// account-trie resume path.
|
|
||||||
const n = 200
|
|
||||||
accounts := make([]testAccount, 0, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
addr := common.BytesToAddress([]byte{byte(i >> 8), byte(i)})
|
|
||||||
hash := crypto.Keccak256Hash(addr[:])
|
|
||||||
accounts = append(accounts, testAccount{
|
|
||||||
hash: hash,
|
|
||||||
account: types.StateAccount{
|
|
||||||
Nonce: uint64(i),
|
|
||||||
Balance: uint256.NewInt(uint64(i + 1)),
|
|
||||||
Root: types.EmptyRootHash,
|
|
||||||
CodeHash: types.EmptyCodeHash.Bytes(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
expectedRoot := buildExpectedRoot(t, accounts)
|
|
||||||
|
|
||||||
for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} {
|
|
||||||
t.Run(scheme, func(t *testing.T) {
|
|
||||||
db := rawdb.NewMemoryDatabase()
|
|
||||||
|
|
||||||
// Step 1: write the account snapshots for this run.
|
|
||||||
for _, a := range accounts {
|
|
||||||
rawdb.WriteAccountSnapshot(db, a.hash, types.SlimAccountRLP(a.account))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: run every partition once to populate trie nodes on disk
|
|
||||||
// and capture each partition's raw root blob.
|
|
||||||
var (
|
|
||||||
scanned atomic.Int64
|
|
||||||
updated atomic.Int64
|
|
||||||
deleted atomic.Int64
|
|
||||||
)
|
|
||||||
ranges := hashRanges(numPartitions)
|
|
||||||
blobs := make([][]byte, numPartitions)
|
|
||||||
for i, r := range ranges {
|
|
||||||
var pos atomic.Uint64
|
|
||||||
blob, err := generatePartition(context.Background(), nil, db, scheme, byte(i), r[0], r[1], &scanned, &updated, &deleted, &pos)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("pre-run partition %d: %v", i, err)
|
|
||||||
}
|
|
||||||
blobs[i] = blob
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: pre-seed done markers for even partitions only.
|
|
||||||
for i := 0; i < numPartitions; i++ {
|
|
||||||
if i%2 == 0 {
|
|
||||||
rawdb.WriteGenerateTriePartitionDone(db, byte(i), blobs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: delete flat-state account snapshots for every account that
|
|
||||||
// lives in an even partition. After this, rerunning generatePartition for
|
|
||||||
// an even partition would find no accounts and produce a nil blob,
|
|
||||||
// so a correct final root requires the resume path.
|
|
||||||
numDeleted := 0
|
|
||||||
for _, a := range accounts {
|
|
||||||
if (a.hash[0]>>4)%2 == 0 {
|
|
||||||
rawdb.DeleteAccountSnapshot(db, a.hash)
|
|
||||||
numDeleted++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if numDeleted == 0 {
|
|
||||||
t.Fatal("test setup failure: no accounts fell in even partitions")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: run GenerateTrie. Success implies resume actually consulted
|
|
||||||
// the markers. Without it, even partitions would yield nil blobs and
|
|
||||||
// the root check inside GenerateTrie would fail.
|
|
||||||
if _, err := GenerateTrie(db, scheme, expectedRoot, nil); err != nil {
|
|
||||||
t.Fatalf("partial-resume GenerateTrie failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// All markers cleared on success.
|
|
||||||
for i := 0; i < numPartitions; i++ {
|
|
||||||
if _, ok := rawdb.ReadGenerateTriePartitionDone(db, byte(i)); ok {
|
|
||||||
t.Errorf("partition %d marker not cleared after successful resume", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if scheme == rawdb.PathScheme {
|
|
||||||
assertCanonicalNodes(t, db, accounts)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestHashRanges checks that hashRanges fully and contiguously covers the
|
// TestHashRanges checks that hashRanges fully and contiguously covers the
|
||||||
// 256-bit hash space, with the last range absorbing the rounding remainder.
|
// 256-bit hash space, with the last range absorbing the rounding remainder.
|
||||||
func TestHashRanges(t *testing.T) {
|
func TestHashRanges(t *testing.T) {
|
||||||
|
|
@ -765,22 +673,21 @@ func TestGenerateTrieBatchFlush(t *testing.T) {
|
||||||
tc.build(db)
|
tc.build(db)
|
||||||
|
|
||||||
peak := 0
|
peak := 0
|
||||||
var scanned, updated, deleted atomic.Int64
|
var c genCounters
|
||||||
var pos atomic.Uint64
|
|
||||||
ranges := hashRanges(numPartitions)
|
ranges := hashRanges(numPartitions)
|
||||||
if _, err := generatePartition(context.Background(), nil, peakBatchDB{Database: db, peak: &peak},
|
if _, err := generatePartition(context.Background(), nil, peakBatchDB{Database: db, peak: &peak},
|
||||||
rawdb.HashScheme, 0, ranges[0][0], ranges[0][1], &scanned, &updated, &deleted, &pos); err != nil {
|
rawdb.HashScheme, 0, ranges[0][0], ranges[0][1], &c); err != nil {
|
||||||
t.Fatalf("generatePartition: %v", err)
|
t.Fatalf("generatePartition: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if scanned.Load() != tc.wantScanned {
|
if c.accounts.Load() != tc.wantScanned {
|
||||||
t.Errorf("scanned = %d, want %d (an account was skipped?)", scanned.Load(), tc.wantScanned)
|
t.Errorf("scanned = %d, want %d (an account was skipped?)", c.accounts.Load(), tc.wantScanned)
|
||||||
}
|
}
|
||||||
if deleted.Load() != tc.wantDeleted {
|
if c.storageDeleted.Load() != tc.wantDeleted {
|
||||||
t.Errorf("deleted = %d, want %d", deleted.Load(), tc.wantDeleted)
|
t.Errorf("deleted = %d, want %d", c.storageDeleted.Load(), tc.wantDeleted)
|
||||||
}
|
}
|
||||||
if updated.Load() != 0 {
|
if c.accountUpdated.Load() != 0 {
|
||||||
t.Errorf("updated = %d, want 0 (a storage slot was dropped across a flush?)", updated.Load())
|
t.Errorf("updated = %d, want 0 (a storage slot was dropped across a flush?)", c.accountUpdated.Load())
|
||||||
}
|
}
|
||||||
// The batch must have stayed bounded. Without this site's flush its
|
// The batch must have stayed bounded. Without this site's flush its
|
||||||
// full write set (far larger than IdealBatchSize) buffers into one batch.
|
// full write set (far larger than IdealBatchSize) buffers into one batch.
|
||||||
|
|
|
||||||
|
|
@ -766,7 +766,7 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
version = fmt.Sprintf("%d", m.Version)
|
version = fmt.Sprintf("%d", m.Version)
|
||||||
}
|
}
|
||||||
log.Info("Cleaned up obsolete history index", "type", typ, "version", version, "want", version)
|
log.Info("Cleaned up obsolete history index", "type", typ, "version", version, "want", fmt.Sprintf("%d", ver))
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHistoryIndexer constructs the history indexer and launches the background
|
// newHistoryIndexer constructs the history indexer and launches the background
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue