Coreth 0.8.16 rc2 (#216)

* Unwrap ETHChain

* hook in vm.ethConfig

* statesync for subnet-evm

* remove unused configJSON

* add err check

* add synchronization

* add default config values

* delay initialization of gossiper

* Revert "add synchronization"

This reverts commit 2e8c93e58d7853de15a21ebd1460a3cd3858a6af.

* fix test

* fix statesync code prefix issue

* fix comment

* wip

* wip2

* add coreth changes

* Merge branch 'master' into coreth-0.8.16-rc2

* updates

* remove coreth from go.mod

* fix linter

* fix

* update gas prices (?)

* fix tracer test

* move dependabot yml to upper file

* more changes ported in from coreth 0.8.16

* Update sync/README.md

Co-authored-by: Ceyhun Onur <[email protected]>

* Update sync/README.md

Co-authored-by: Ceyhun Onur <[email protected]>

* Update sync/README.md

Co-authored-by: Ceyhun Onur <[email protected]>

* Update core/rawdb/accessors_state_sync.go

* port missing file from coreth

* fix-import

* subscribe before goroutine

* Rebase apply coreth diff onto statesync

* fix EOF in tests

* Nits (#265)

* Nits

* Add EIP-1559 link to readme

* Fix linting introduced by merge
This commit is contained in:
Ceyhun Onur
2022-09-22 18:42:37 +09:00
committed by GitHub
parent 042814df00
commit a6d013d4ab
205 changed files with 4535 additions and 2510 deletions
+11
View File
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
+1
View File
@@ -21,6 +21,7 @@ linters:
- misspell
- unconvert
- varcheck
- whitespace
linters-settings:
gofmt:
+4 -19
View File
@@ -234,7 +234,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
case "constructor":
abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil)
case "function":
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs)
case "fallback":
// New introduced function type in v0.6.0, check more detail
@@ -254,9 +254,11 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
}
abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil)
case "event":
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs)
case "error":
// Errors cannot be overloaded or overridden but are inherited,
// no need to resolve the name conflict here.
abi.Errors[field.Name] = NewError(field.Name, field.Inputs)
default:
return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name)
@@ -321,20 +323,3 @@ func UnpackRevert(data []byte) (string, error) {
}
return unpacked[0].(string), nil
}
// overloadedName returns the next available name for a given thing.
// Needed since solidity allows for overloading.
//
// e.g. if the abi contains Methods send, send1
// overloadedName would return send2 for input send.
//
// overloadedName works for methods, events and errors.
func overloadedName(rawName string, isAvail func(string) bool) string {
name := rawName
ok := isAvail(name)
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
ok = isAvail(name)
}
return name
}
+6 -7
View File
@@ -96,7 +96,8 @@ var (
TupleF, _ = NewType("tuple", "struct Overloader.F", []ArgumentMarshaling{
{Name: "_f", Type: "uint256"},
{Name: "__f", Type: "uint256"},
{Name: "f", Type: "uint256"}})
{Name: "f", Type: "uint256"},
})
)
var methods = map[string]Method{
@@ -884,12 +885,12 @@ func TestUnpackMethodIntoMap(t *testing.T) {
func TestUnpackIntoMapNamingConflict(t *testing.T) {
// Two methods have the same name
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
abiJSON := `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
t.Fatal(err)
}
var hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
hexdata := `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
data, err := hex.DecodeString(hexdata)
if err != nil {
t.Fatal(err)
@@ -1048,9 +1049,7 @@ func TestABI_EventById(t *testing.T) {
}
if event == nil {
t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum)
}
if event.ID != topicID {
} else if event.ID != topicID {
t.Errorf("Event id %s does not match topic %s, test #%d", event.ID.Hex(), topicID.Hex(), testnum)
}
@@ -1143,7 +1142,7 @@ func TestUnnamedEventParam(t *testing.T) {
func TestUnpackRevert(t *testing.T) {
t.Parallel()
var cases = []struct {
cases := []struct {
input string
expect string
expectErr error
+8 -7
View File
@@ -28,6 +28,7 @@ package abi
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
@@ -88,8 +89,8 @@ func (arguments Arguments) isTuple() bool {
// Unpack performs the operation hexdata -> Go format.
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
if len(data) == 0 {
if len(arguments) != 0 {
return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
if len(arguments.NonIndexed()) != 0 {
return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
}
return make([]interface{}, 0), nil
}
@@ -100,11 +101,11 @@ func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
// Make sure map is not nil
if v == nil {
return fmt.Errorf("abi: cannot unpack into a nil map")
return errors.New("abi: cannot unpack into a nil map")
}
if len(data) == 0 {
if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
if len(arguments.NonIndexed()) != 0 {
return errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
}
return nil // Nothing to unmarshal, return
}
@@ -125,8 +126,8 @@ func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
if len(values) == 0 {
if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to copy no values while %d arguments are expected", len(arguments))
if len(arguments.NonIndexed()) != 0 {
return errors.New("abi: attempting to copy no values while arguments are expected")
}
return nil // Nothing to copy, return
}
+2 -3
View File
@@ -31,7 +31,6 @@ import (
"crypto/ecdsa"
"errors"
"io"
"io/ioutil"
"math/big"
"github.com/ava-labs/subnet-evm/accounts"
@@ -55,7 +54,7 @@ var ErrNotAuthorized = errors.New("not authorized to sign this account")
// Deprecated: Use NewTransactorWithChainID instead.
func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID")
json, err := ioutil.ReadAll(keyin)
json, err := io.ReadAll(keyin)
if err != nil {
return nil, err
}
@@ -116,7 +115,7 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
// NewTransactorWithChainID is a utility method to easily create a transaction signer from
// an encrypted json key stream and the associated passphrase.
func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) {
json, err := ioutil.ReadAll(keyin)
json, err := io.ReadAll(keyin)
if err != nil {
return nil, err
}
+11 -6
View File
@@ -117,8 +117,8 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis
database: database,
blockchain: blockchain,
config: genesis.Config,
events: filters.NewEventSystem(&filterBackend{database, blockchain}, false),
}
backend.events = filters.NewEventSystem(&filterBackend{database, blockchain, backend}, false)
backend.rollback(blockchain.CurrentBlock())
return backend
}
@@ -138,7 +138,7 @@ func (b *SimulatedBackend) Close() error {
// Commit imports all the pending transactions as a single block and starts a
// fresh new state.
func (b *SimulatedBackend) Commit(accept bool) {
func (b *SimulatedBackend) Commit(accept bool) common.Hash {
b.mu.Lock()
defer b.mu.Unlock()
@@ -151,9 +151,13 @@ func (b *SimulatedBackend) Commit(accept bool) {
}
b.blockchain.DrainAcceptorQueue()
}
blockHash := b.acceptedBlock.Hash()
// Using the last inserted block here makes it possible to build on a side
// chain after a fork.
b.rollback(b.acceptedBlock)
return blockHash
}
// Rollback aborts all pending transactions, reverting to the last committed state.
@@ -724,7 +728,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query interfaces.Filt
var filter *filters.Filter
if query.BlockHash != nil {
// Block filter requested, construct a single-shot filter
filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics)
filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain, b}, *query.BlockHash, query.Addresses, query.Topics)
} else {
// Initialize unset filter boundaries to run from genesis to chain head
from := int64(0)
@@ -736,7 +740,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query interfaces.Filt
to = query.ToBlock.Int64()
}
// Construct the range filter
filter, _ = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
filter, _ = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain, b}, from, to, query.Addresses, query.Topics)
}
// Run the filter and return all the logs
logs, err := filter.Logs(ctx)
@@ -857,8 +861,9 @@ func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
type filterBackend struct {
db ethdb.Database
bc *core.BlockChain
db ethdb.Database
bc *core.BlockChain
backend *SimulatedBackend
}
func (fb *filterBackend) SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription {
+40 -2
View File
@@ -673,8 +673,7 @@ func TestHeaderByNumber(t *testing.T) {
}
if latestBlockHeader == nil {
t.Errorf("received a nil block header")
}
if latestBlockHeader.Number.Uint64() != uint64(0) {
} else if latestBlockHeader.Number.Uint64() != uint64(0) {
t.Errorf("expected block header number 0, instead got %v", latestBlockHeader.Number.Uint64())
}
@@ -1362,3 +1361,42 @@ contract Callable {
// t.Errorf("TX included in wrong block: %d", h)
// }
// }
func TestCommitReturnValue(t *testing.T) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
defer sim.Close()
startBlockHeight := sim.blockchain.CurrentBlock().NumberU64()
// Test if Commit returns the correct block hash
h1 := sim.Commit(true)
if h1 != sim.blockchain.CurrentBlock().Hash() {
t.Error("Commit did not return the hash of the last block.")
}
// Create a block in the original chain (containing a transaction to force different block hashes)
head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
_tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey)
sim.SendTransaction(context.Background(), tx)
h2 := sim.Commit(false)
// Create another block in the original chain
sim.Commit(false)
// Fork at the first bock
if err := sim.Fork(context.Background(), h1); err != nil {
t.Errorf("forking: %v", err)
}
// Test if Commit returns the correct block hash after the reorg
h2fork := sim.Commit(false)
if h2 == h2fork {
t.Error("The block in the fork and the original block are the same block!")
}
if sim.blockchain.GetHeader(h2fork, startBlockHeight+2) == nil {
t.Error("Could not retrieve the just created block (side-chain)")
}
}
+4 -1
View File
@@ -181,7 +181,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
return ErrNoAcceptedState
}
output, err = pb.AcceptedCallContract(ctx, msg)
if err == nil && len(output) == 0 {
if err != nil {
return err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = pb.AcceptedCodeAt(ctx, c.address); err != nil {
return err
+165 -11
View File
@@ -28,6 +28,7 @@ package bind_test
import (
"context"
"errors"
"math/big"
"reflect"
"strings"
@@ -85,34 +86,50 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac
}
type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
acceptedCodeAtCalled bool
acceptedCallContractCalled bool
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
callContractBytes []byte
callContractErr error
codeAtBytes []byte
codeAtErr error
}
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
mc.codeAtBlockNumber = blockNumber
return []byte{1, 2, 3}, nil
return mc.codeAtBytes, mc.codeAtErr
}
func (mc *mockCaller) CallContract(ctx context.Context, call interfaces.CallMsg, blockNumber *big.Int) ([]byte, error) {
mc.callContractBlockNumber = blockNumber
return nil, nil
return mc.callContractBytes, mc.callContractErr
}
func (mc *mockCaller) AcceptedCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
type mockAcceptedCaller struct {
*mockCaller
acceptedCodeAtBytes []byte
acceptedCodeAtErr error
acceptedCodeAtCalled bool
acceptedCallContractCalled bool
acceptedCallContractBytes []byte
acceptedCallContractErr error
}
func (mc *mockAcceptedCaller) AcceptedCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
mc.acceptedCodeAtCalled = true
return nil, nil
return mc.acceptedCodeAtBytes, mc.acceptedCodeAtErr
}
func (mc *mockCaller) AcceptedCallContract(ctx context.Context, call interfaces.CallMsg) ([]byte, error) {
func (mc *mockAcceptedCaller) AcceptedCallContract(ctx context.Context, call interfaces.CallMsg) ([]byte, error) {
mc.acceptedCallContractCalled = true
return nil, nil
return mc.acceptedCallContractBytes, mc.acceptedCallContractErr
}
func TestPassingBlockNumber(t *testing.T) {
mc := &mockCaller{}
mc := &mockAcceptedCaller{
mockCaller: &mockCaller{
codeAtBytes: []byte{1, 2, 3},
},
}
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
Methods: map[string]abi.Method{
@@ -351,3 +368,140 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
Removed: false,
}
}
func TestCall(t *testing.T) {
method, methodWithArg := "something", "somethingArrrrg"
tests := []struct {
name, method string
opts *bind.CallOpts
mc bind.ContractCaller
results *[]interface{}
wantErr bool
wantErrExact error
}{{
name: "ok not accepted",
mc: &mockCaller{
codeAtBytes: []byte{0},
},
method: method,
}, {
name: "ok accepted",
mc: &mockAcceptedCaller{
acceptedCodeAtBytes: []byte{0},
},
opts: &bind.CallOpts{
Accepted: true,
},
method: method,
}, {
name: "pack error, no method",
mc: new(mockCaller),
method: "else",
wantErr: true,
}, {
name: "interface error, accepted but not a AcceptedContractCaller",
mc: new(mockCaller),
opts: &bind.CallOpts{
Accepted: true,
},
method: method,
wantErrExact: bind.ErrNoAcceptedState,
}, {
name: "accepted call canceled",
mc: &mockAcceptedCaller{
acceptedCallContractErr: context.DeadlineExceeded,
},
opts: &bind.CallOpts{
Accepted: true,
},
method: method,
wantErrExact: context.DeadlineExceeded,
}, {
name: "accepted code at error",
mc: &mockAcceptedCaller{
acceptedCodeAtErr: errors.New(""),
},
opts: &bind.CallOpts{
Accepted: true,
},
method: method,
wantErr: true,
}, {
name: "no accepted code at",
mc: new(mockAcceptedCaller),
opts: &bind.CallOpts{
Accepted: true,
},
method: method,
wantErrExact: bind.ErrNoCode,
}, {
name: "call contract error",
mc: &mockCaller{
callContractErr: context.DeadlineExceeded,
},
method: method,
wantErrExact: context.DeadlineExceeded,
}, {
name: "code at error",
mc: &mockCaller{
codeAtErr: errors.New(""),
},
method: method,
wantErr: true,
}, {
name: "no code at",
mc: new(mockCaller),
method: method,
wantErrExact: bind.ErrNoCode,
}, {
name: "unpack error missing arg",
mc: &mockCaller{
codeAtBytes: []byte{0},
},
method: methodWithArg,
wantErr: true,
}, {
name: "interface unpack error",
mc: &mockCaller{
codeAtBytes: []byte{0},
},
method: method,
results: &[]interface{}{0},
wantErr: true,
}}
for _, test := range tests {
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
Methods: map[string]abi.Method{
method: {
Name: method,
Outputs: abi.Arguments{},
},
methodWithArg: {
Name: methodWithArg,
Outputs: abi.Arguments{abi.Argument{}},
},
},
}, test.mc, nil, nil)
err := bc.Call(test.opts, test.results, test.method)
if test.wantErr || test.wantErrExact != nil {
if err == nil {
t.Fatalf("%q expected error", test.name)
}
if test.wantErrExact != nil && !errors.Is(err, test.wantErrExact) {
t.Fatalf("%q expected error %q but got %q", test.name, test.wantErrExact, err)
}
continue
}
if err != nil {
t.Fatalf("%q unexpected error: %v", test.name, err)
}
}
}
// TestCrashers contains some strings which previously caused the abi codec to crash.
func TestCrashers(t *testing.T) {
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`))
}
+60 -6
View File
@@ -60,6 +60,43 @@ const (
LangObjC
)
func isKeyWord(arg string) bool {
switch arg {
case "break":
case "case":
case "chan":
case "const":
case "continue":
case "default":
case "defer":
case "else":
case "fallthrough":
case "for":
case "func":
case "go":
case "goto":
case "if":
case "import":
case "interface":
case "iota":
case "map":
case "make":
case "new":
case "package":
case "range":
case "return":
case "select":
case "struct":
case "switch":
case "type":
case "var":
default:
return false
}
return true
}
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
// to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention opposed to having to
@@ -130,7 +167,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs {
if input.Name == "" {
if input.Name == "" || isKeyWord(input.Name) {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
}
if hasStruct(input.Type) {
@@ -175,12 +212,22 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
eventIdentifiers[normalizedName] = true
normalized.Name = normalizedName
used := make(map[string]bool)
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs {
if input.Name == "" {
if input.Name == "" || isKeyWord(input.Name) {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
}
// Event is a bit special, we need to define event struct in binding,
// ensure there is no camel-case-style name conflict.
for index := 0; ; index++ {
if !used[capitalise(normalized.Inputs[j].Name)] {
used[capitalise(normalized.Inputs[j].Name)] = true
break
}
normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index)
}
if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs)
}
@@ -202,7 +249,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
contracts[types[i]] = &tmplContract{
Type: capitalise(types[i]),
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
Constructor: evmABI.Constructor,
Calls: calls,
@@ -478,15 +525,22 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
if s, exist := structs[id]; exist {
return s.Name
}
var fields []*tmplField
var (
names = make(map[string]bool)
fields []*tmplField
)
for i, elem := range kind.TupleElems {
field := bindStructTypeGo(*elem, structs)
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
name := capitalise(kind.TupleRawNames[i])
name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] })
names[name] = true
fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem})
}
name := kind.TupleRawName
if name == "" {
name = fmt.Sprintf("Struct%d", len(structs))
}
name = capitalise(name)
structs[id] = &tmplStruct{
Name: name,
Fields: fields,
@@ -104,7 +104,6 @@ func golangBindingsFailure(t *testing.T) {
if tt.errorMsg != err.Error() {
t.Fatalf("test %d: expected Err %s but got actual Err: %s", i, tt.errorMsg, err.Error())
}
})
}
}
+89 -9
View File
@@ -28,7 +28,6 @@ package bind
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -1977,6 +1976,91 @@ var bindTests = []struct {
}
`,
},
{
name: `NameConflict`,
contract: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract oracle {
struct request {
bytes data;
bytes _data;
}
event log (int msg, int _msg);
function addRequest(request memory req) public pure {}
function getRequest() pure public returns (request memory) {
return request("", "");
}
}
`,
bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"},
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
imports: `
"math/big"
"github.com/ava-labs/subnet-evm/accounts/abi/bind"
"github.com/ava-labs/subnet-evm/accounts/abi/bind/backends"
"github.com/ava-labs/subnet-evm/core"
"github.com/ethereum/go-ethereum/crypto"
`,
tester: `
var (
gasCeil = uint64(30000000) // Note: from geth's ethconfig.Defaults.Miner.GasCeil
key, _ = crypto.GenerateKey()
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, gasCeil)
)
defer sim.Close()
_, tx, _, err := DeployNameConflict(user, sim)
if err != nil {
t.Fatalf("DeployNameConflict() got err %v; want nil err", err)
}
sim.Commit(true)
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
t.Logf("Deployment tx: %+v", tx)
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
}
`,
},
{
name: "RangeKeyword",
contract: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract keywordcontract {
function functionWithKeywordParameter(range uint256) public pure {}
}
`,
bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"},
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
imports: `
"math/big"
"github.com/ava-labs/subnet-evm/accounts/abi/bind"
"github.com/ava-labs/subnet-evm/accounts/abi/bind/backends"
"github.com/ava-labs/subnet-evm/core"
"github.com/ethereum/go-ethereum/crypto"
`,
tester: `
var (
gasCeil = uint64(30000000) // Note: from geth's ethconfig.Defaults.Miner.GasCeil
key, _ = crypto.GenerateKey()
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, gasCeil)
)
_, tx, _, err := DeployRangeKeyword(user, sim)
if err != nil {
t.Fatalf("error deploying contract: %v", err)
}
sim.Commit(true)
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
t.Errorf("error deploying the contract: %v", err)
}
`,
},
}
// The binding tests have been modified to run in two separate test
@@ -1998,14 +2082,10 @@ func golangBindings(t *testing.T, overload bool) {
t.Skip("go sdk not found for testing")
}
// Create a temporary workspace for the test suite
ws, err := ioutil.TempDir("", "binding-test")
if err != nil {
t.Fatalf("failed to create temporary workspace: %v", err)
}
// defer os.RemoveAll(ws)
ws := t.TempDir()
pkg := filepath.Join(ws, "bindtest")
if err = os.MkdirAll(pkg, 0o700); err != nil {
if err := os.MkdirAll(pkg, 0o700); err != nil {
t.Fatalf("failed to create package: %v", err)
}
// Generate the test suite for all the contracts
@@ -2030,7 +2110,7 @@ func golangBindings(t *testing.T, overload bool) {
if err != nil {
t.Fatalf("test %d: failed to generate binding: %v", i, err)
}
if err = ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0o600); err != nil {
if err = os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0o600); err != nil {
t.Fatalf("test %d: failed to write binding: %v", i, err)
}
// Generate the test file with the injected test code
@@ -2046,7 +2126,7 @@ func golangBindings(t *testing.T, overload bool) {
%s
}
`, tt.imports, tt.name, tt.tester)
if err := ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0o600); err != nil {
if err := os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0o600); err != nil {
t.Fatalf("test %d: failed to write tests: %v", i, err)
}
})
+1 -1
View File
@@ -171,7 +171,7 @@ var (
}
{{range $pattern, $name := .Libraries}}
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
{{$contract.Type}}Bin = strings.Replace({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:], -1)
{{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:])
{{end}}
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
if err != nil {
+4 -2
View File
@@ -40,11 +40,13 @@ type Error struct {
Name string
Inputs Arguments
str string
// Sig contains the string signature according to the ABI spec.
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
// Please note that "int" is substitute for its canonical representation "int256"
Sig string
// ID returns the canonical representation of the event's signature used by the
// ID returns the canonical representation of the error's signature used by the
// abi definition to identify event names and types.
ID common.Hash
}
+1 -4
View File
@@ -32,9 +32,7 @@ import (
"reflect"
)
var (
errBadBool = errors.New("abi: improperly encoded boolean value")
)
var errBadBool = errors.New("abi: improperly encoded boolean value")
// formatSliceString formats the reflection kind with the given slice size
// and returns a formatted string representation.
@@ -83,7 +81,6 @@ func typeCheck(t Type, value reflect.Value) error {
} else {
return nil
}
}
// typeErr returns a formatted type casting error.
-1
View File
@@ -171,7 +171,6 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) {
}
func TestEventTupleUnpack(t *testing.T) {
type EventTransfer struct {
Value *big.Int
}
-1
View File
@@ -230,7 +230,6 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
// second round ~~~
for _, argName := range argNames {
structFieldName := ToCamelCase(argName)
if structFieldName == "" {
+1 -1
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2019 The go-ethereum Authors
// Copyright 2020 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
+42 -24
View File
@@ -33,6 +33,8 @@ import (
"regexp"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/ethereum/go-ethereum/common"
)
@@ -69,10 +71,8 @@ type Type struct {
TupleType reflect.Type // Underlying struct of the tuple
}
var (
// typeRegex parses the abi sub types
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
)
// typeRegex parses the abi sub types
var typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
// NewType creates a new reflection type of abi type given in t.
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
@@ -171,19 +171,24 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
elems []*Type
names []string
expression string // canonical parameter expression
used = make(map[string]bool)
)
expression += "("
overloadedNames := make(map[string]string)
for idx, c := range components {
cType, err := NewType(c.Type, c.InternalType, c.Components)
if err != nil {
return Type{}, err
}
fieldName, err := overloadedArgName(c.Name, overloadedNames)
if err != nil {
return Type{}, err
name := ToCamelCase(c.Name)
if name == "" {
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
}
fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] })
used[fieldName] = true
if !isValidFieldName(fieldName) {
return Type{}, fmt.Errorf("field %d has invalid name", idx)
}
overloadedNames[fieldName] = fieldName
fields = append(fields, reflect.StructField{
Name: fieldName, // reflect.StructOf will panic for any exported field.
Type: cType.GetType(),
@@ -211,7 +216,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
if internalType != "" && strings.HasPrefix(internalType, structPrefix) {
// Foo.Bar type definition is not allowed in golang,
// convert the format to FooBar
typ.TupleRawName = strings.Replace(internalType[len(structPrefix):], ".", "", -1)
typ.TupleRawName = strings.ReplaceAll(internalType[len(structPrefix):], ".", "")
}
case "function":
@@ -260,20 +265,6 @@ func (t Type) GetType() reflect.Type {
}
}
func overloadedArgName(rawName string, names map[string]string) (string, error) {
fieldName := ToCamelCase(rawName)
if fieldName == "" {
return "", errors.New("abi: purely anonymous or underscored field is not supported")
}
// Handle overloaded fieldNames
_, ok := names[fieldName]
for idx := 0; ok; idx++ {
fieldName = fmt.Sprintf("%s%d", ToCamelCase(rawName), idx)
_, ok = names[fieldName]
}
return fieldName, nil
}
// String implements Stringer.
func (t Type) String() (out string) {
return t.stringKind
@@ -409,3 +400,30 @@ func getTypeSize(t Type) int {
}
return 32
}
// isLetter reports whether a given 'rune' is classified as a Letter.
// This method is copied from reflect/type.go
func isLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
}
// isValidFieldName checks if a string is a valid (struct) field name or not.
//
// According to the language spec, a field name should be an identifier.
//
// identifier = letter { letter | unicode_digit } .
// letter = unicode_letter | "_" .
// This method is copied from reflect/type.go
func isValidFieldName(fieldName string) bool {
for i, c := range fieldName {
if i == 0 && !isLetter(c) {
return false
}
if !(isLetter(c) || unicode.IsDigit(c)) {
return false
}
}
return len(fieldName) > 0
}
+5 -6
View File
@@ -264,7 +264,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type.
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32])
bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32])
bigOffsetEnd.Add(bigOffsetEnd, common.Big32)
outputLength := big.NewInt(int64(len(output)))
@@ -277,11 +277,10 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
}
offsetEnd := int(bigOffsetEnd.Uint64())
lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd])
lengthBig := new(big.Int).SetBytes(output[offsetEnd-32 : offsetEnd])
totalSize := new(big.Int).Add(bigOffsetEnd, lengthBig)
totalSize := big.NewInt(0)
totalSize.Add(totalSize, bigOffsetEnd)
totalSize.Add(totalSize, lengthBig)
if totalSize.BitLen() > 63 {
return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize)
}
@@ -296,7 +295,7 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
// tuplePointsTo resolves the location reference for dynamic tuple.
func tuplePointsTo(index int, output []byte) (start int, err error) {
offset := big.NewInt(0).SetBytes(output[index : index+32])
offset := new(big.Int).SetBytes(output[index : index+32])
outputLen := big.NewInt(int64(len(output)))
if offset.Cmp(outputLen) > 0 {
+25 -8
View File
@@ -44,7 +44,7 @@ import (
func TestUnpack(t *testing.T) {
for i, test := range packUnpackTests {
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
//Unpack
// Unpack
def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
abi, err := JSON(strings.NewReader(def))
if err != nil {
@@ -211,6 +211,23 @@ var unpackTests = []unpackTest{
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"type":"bool"}]`,
enc: "",
want: false,
err: "abi: attempting to unmarshall an empty string while arguments are expected",
},
{
def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`,
enc: "",
want: false,
err: "abi: attempting to unmarshall an empty string while arguments are expected",
},
{
def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`,
enc: "",
want: false,
},
}
// TestLocalUnpackTests runs test specially designed only for unpacking.
@@ -218,7 +235,7 @@ var unpackTests = []unpackTest{
func TestLocalUnpackTests(t *testing.T) {
for i, test := range unpackTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
//Unpack
// Unpack
def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
abi, err := JSON(strings.NewReader(def))
if err != nil {
@@ -299,7 +316,7 @@ type methodMultiOutput struct {
func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
const definition = `[
{ "name" : "multi", "type": "function", "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
var expected = methodMultiOutput{big.NewInt(1), "hello"}
expected := methodMultiOutput{big.NewInt(1), "hello"}
abi, err := JSON(strings.NewReader(definition))
require.NoError(err)
@@ -325,7 +342,7 @@ func TestMethodMultiReturn(t *testing.T) {
abi, data, expected := methodMultiReturn(require.New(t))
bigint := new(big.Int)
var testCases = []struct {
testCases := []struct {
dest interface{}
expected interface{}
error string
@@ -417,7 +434,7 @@ func TestMultiReturnWithStringArray(t *testing.T) {
}
buff := new(bytes.Buffer)
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000"))
temp, _ := big.NewInt(0).SetString("30000000000000000000", 10)
temp, _ := new(big.Int).SetString("30000000000000000000", 10)
ret1, ret1Exp := new([3]*big.Int), [3]*big.Int{big.NewInt(1545304298), big.NewInt(6), temp}
ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f")
ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"}
@@ -485,7 +502,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
// construct the test array, each 3 char element is joined with 61 '0' chars,
// to from the ((3 + 61) * 0.5) = 32 byte elements in the array.
buff.Write(common.Hex2Bytes(strings.Join([]string{
"", //empty, to apply the 61-char separator to the first element as well.
"", // empty, to apply the 61-char separator to the first element as well.
"111", "112", "113", "121", "122", "123",
"211", "212", "213", "221", "222", "223",
"311", "312", "313", "321", "322", "323",
@@ -779,7 +796,7 @@ func TestUnpackTuple(t *testing.T) {
type r struct {
Result v
}
var ret0 = new(r)
ret0 := new(r)
err = abi.UnpackIntoInterface(ret0, "tuple", buff.Bytes())
if err != nil {
@@ -838,7 +855,7 @@ func TestUnpackTuple(t *testing.T) {
A *big.Int
}
var ret Ret
var expected = Ret{
expected := Ret{
FieldS: S{
A: big.NewInt(1),
B: []*big.Int{big.NewInt(1), big.NewInt(2)},
+51
View File
@@ -0,0 +1,51 @@
// (c) 2022, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2022 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 abi
import "fmt"
// ResolveNameConflict returns the next available name for a given thing.
// This helper can be used for lots of purposes:
//
// - In solidity function overloading is supported, this function can fix
// the name conflicts of overloaded functions.
// - In golang binding generation, the parameter(in function, event, error,
// and struct definition) name will be converted to camelcase style which
// may eventually lead to name conflicts.
//
// Name conflicts are mostly resolved by adding number suffix.
// e.g. if the abi contains Methods send, send1
// ResolveNameConflict would return send2 for input send.
func ResolveNameConflict(rawName string, used func(string) bool) string {
name := rawName
ok := used(name)
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
ok = used(name)
}
return name
}
-4
View File
@@ -162,10 +162,6 @@ func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain int
log.Error("operation SelfDerive not supported on external signers")
}
func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]byte, error) {
return []byte{}, fmt.Errorf("operation not supported on external signers")
}
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
var res hexutil.Bytes
+1 -1
View File
@@ -51,7 +51,7 @@ var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60,
var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
// DerivationPath represents the computer friendly version of a hierarchical
// deterministic wallet account derivaion path.
// deterministic wallet account derivation path.
//
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
// defines derivation paths to be of the form:
+6 -8
View File
@@ -28,7 +28,6 @@ package keystore
import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
@@ -66,7 +65,6 @@ func TestWatchNewFile(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStore(t, false)
defer os.RemoveAll(dir)
// Ensure the watcher is started before adding any files.
ks.Accounts()
@@ -116,7 +114,7 @@ func TestWatchNoDir(t *testing.T) {
time.Sleep(100 * time.Millisecond)
// Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700)
os.MkdirAll(dir, 0o700)
defer os.RemoveAll(dir)
file := filepath.Join(dir, "aaa")
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
@@ -343,7 +341,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
time.Sleep(100 * time.Millisecond)
// Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700)
os.MkdirAll(dir, 0o700)
defer os.RemoveAll(dir)
file := filepath.Join(dir, "aaa")
@@ -392,11 +390,11 @@ func TestUpdatedKeyfileContents(t *testing.T) {
return
}
// needed so that modTime of `file` is different to its current value after ioutil.WriteFile
// needed so that modTime of `file` is different to its current value after os.WriteFile
time.Sleep(1000 * time.Millisecond)
// Now replace file contents with crap
if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
if err := os.WriteFile(file, []byte("foo"), 0o600); err != nil {
t.Fatal(err)
return
}
@@ -409,9 +407,9 @@ func TestUpdatedKeyfileContents(t *testing.T) {
// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
func forceCopyFile(dst, src string) error {
data, err := ioutil.ReadFile(src)
data, err := os.ReadFile(src)
if err != nil {
return err
}
return ioutil.WriteFile(dst, data, 0644)
return os.WriteFile(dst, data, 0o644)
}
+8 -5
View File
@@ -27,7 +27,6 @@
package keystore
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -51,7 +50,7 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
t0 := time.Now()
// List all the failes from the keystore folder
files, err := ioutil.ReadDir(keyDir)
files, err := os.ReadDir(keyDir)
if err != nil {
return nil, nil, nil, err
}
@@ -75,7 +74,11 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
// Gather the set of all and fresly modified files
all.Add(path)
modified := fi.ModTime()
info, err := fi.Info()
if err != nil {
return nil, nil, nil, err
}
modified := info.ModTime()
if modified.After(fc.lastMod) {
mods.Add(path)
}
@@ -99,13 +102,13 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
}
// nonKeyFile ignores editor backups, hidden files and folders/symlinks.
func nonKeyFile(fi os.FileInfo) bool {
func nonKeyFile(fi os.DirEntry) bool {
// Skip editor backups and UNIX-style hidden files.
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
return true
}
// Skip misc special files, directories (yes, symlinks too).
if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
if fi.IsDir() || !fi.Type().IsRegular() {
return true
}
return false
+2 -3
View File
@@ -33,7 +33,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -205,13 +204,13 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou
func writeTemporaryKeyFile(file string, content []byte) (string, error) {
// Create the keystore directory with appropriate permissions
// in case it is not present yet.
const dirPerm = 0700
const dirPerm = 0o700
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
return "", err
}
// Atomic write: create a temporary hidden file first
// then move it into place. TempFile assigns mode 0600.
f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
if err != nil {
return "", err
}
+15 -32
View File
@@ -27,7 +27,6 @@
package keystore
import (
"io/ioutil"
"math/rand"
"os"
"runtime"
@@ -48,7 +47,6 @@ var testSigData = make([]byte, 32)
func TestKeyStore(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
a, err := ks.NewAccount("foo")
if err != nil {
@@ -61,8 +59,8 @@ func TestKeyStore(t *testing.T) {
if err != nil {
t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
}
if runtime.GOOS != "windows" && stat.Mode() != 0600 {
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
if runtime.GOOS != "windows" && stat.Mode() != 0o600 {
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0o600)
}
if !ks.HasAddress(a.Address) {
t.Errorf("HasAccount(%x) should've returned true", a.Address)
@@ -82,8 +80,7 @@ func TestKeyStore(t *testing.T) {
}
func TestSign(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, true)
pass := "" // not used but required by API
a1, err := ks.NewAccount(pass)
@@ -99,8 +96,7 @@ func TestSign(t *testing.T) {
}
func TestSignWithPassphrase(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, true)
pass := "passwd"
acc, err := ks.NewAccount(pass)
@@ -127,8 +123,7 @@ func TestSignWithPassphrase(t *testing.T) {
}
func TestTimedUnlock(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, true)
pass := "foo"
a1, err := ks.NewAccount(pass)
@@ -162,8 +157,7 @@ func TestTimedUnlock(t *testing.T) {
}
func TestOverrideUnlock(t *testing.T) {
dir, ks := tmpKeyStore(t, false)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, false)
pass := "foo"
a1, err := ks.NewAccount(pass)
@@ -203,8 +197,7 @@ func TestOverrideUnlock(t *testing.T) {
// This test should fail under -race if signing races the expiration goroutine.
func TestSignRace(t *testing.T) {
dir, ks := tmpKeyStore(t, false)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, false)
// Create a test account.
a1, err := ks.NewAccount("")
@@ -232,8 +225,7 @@ func TestSignRace(t *testing.T) {
// addition and removal of wallet event subscriptions.
func TestWalletNotifierLifecycle(t *testing.T) {
// Create a temporary kesytore to test with
dir, ks := tmpKeyStore(t, false)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, false)
// Ensure that the notification updater is not running yet
time.Sleep(250 * time.Millisecond)
@@ -294,8 +286,7 @@ type walletEvent struct {
// or deleted from the keystore.
func TestWalletNotifications(t *testing.T) {
t.Skip("FLAKY")
dir, ks := tmpKeyStore(t, false)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, false)
// Subscribe to the wallet feed and collect events.
var (
@@ -356,8 +347,7 @@ func TestWalletNotifications(t *testing.T) {
// TestImportExport tests the import functionality of a keystore.
func TestImportECDSA(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, true)
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate key: %v", key)
@@ -375,8 +365,7 @@ func TestImportECDSA(t *testing.T) {
// TestImportECDSA tests the import and export functionality of a keystore.
func TestImportExport(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old")
if err != nil {
t.Fatalf("failed to create account: %v", acc)
@@ -385,8 +374,7 @@ func TestImportExport(t *testing.T) {
if err != nil {
t.Fatalf("failed to export account: %v", acc)
}
dir2, ks2 := tmpKeyStore(t, true)
defer os.RemoveAll(dir2)
_, ks2 := tmpKeyStore(t, true)
if _, err = ks2.Import(json, "old", "old"); err == nil {
t.Errorf("importing with invalid password succeeded")
}
@@ -405,8 +393,7 @@ func TestImportExport(t *testing.T) {
// TestImportRace tests the keystore on races.
// This test should fail under -race if importing races.
func TestImportRace(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old")
if err != nil {
t.Fatalf("failed to create account: %v", acc)
@@ -415,8 +402,7 @@ func TestImportRace(t *testing.T) {
if err != nil {
t.Fatalf("failed to export account: %v", acc)
}
dir2, ks2 := tmpKeyStore(t, true)
defer os.RemoveAll(dir2)
_, ks2 := tmpKeyStore(t, true)
var atom uint32
var wg sync.WaitGroup
wg.Add(2)
@@ -471,10 +457,7 @@ func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
}
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
d, err := ioutil.TempDir("", "eth-keystore-test")
if err != nil {
t.Fatal(err)
}
d := t.TempDir()
newKs := NewPlaintextKeyStore
if encrypted {
newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
+1 -3
View File
@@ -44,7 +44,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -92,7 +91,7 @@ type keyStorePassphrase struct {
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
// Load the key from the keystore and decrypt its contents
keyjson, err := ioutil.ReadFile(filename)
keyjson, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
@@ -351,7 +350,6 @@ func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
r := ensureInt(cryptoJSON.KDFParams["r"])
p := ensureInt(cryptoJSON.KDFParams["p"])
return scrypt.Key(authArray, salt, n, r, p, dkLen)
} else if cryptoJSON.KDF == "pbkdf2" {
c := ensureInt(cryptoJSON.KDFParams["c"])
prf := cryptoJSON.KDFParams["prf"].(string)
+3 -3
View File
@@ -27,7 +27,7 @@
package keystore
import (
"io/ioutil"
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -40,7 +40,7 @@ const (
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
func TestKeyEncryptDecrypt(t *testing.T) {
keyjson, err := ioutil.ReadFile("testdata/very-light-scrypt.json")
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
if err != nil {
t.Fatal(err)
}
@@ -62,7 +62,7 @@ func TestKeyEncryptDecrypt(t *testing.T) {
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
}
// Recrypt with a new password and start over
password += "new data appended"
password += "new data appended" // nolint: gosec
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
t.Errorf("test %d: failed to recrypt key %v", i, err)
}
+4 -13
View File
@@ -30,8 +30,6 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
@@ -41,10 +39,7 @@ import (
)
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
d, err := ioutil.TempDir("", "geth-keystore-test")
if err != nil {
t.Fatal(err)
}
d := t.TempDir()
if encrypted {
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
} else {
@@ -54,8 +49,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
}
func TestKeyStorePlain(t *testing.T) {
dir, ks := tmpKeyStoreIface(t, false)
defer os.RemoveAll(dir)
_, ks := tmpKeyStoreIface(t, false)
pass := "" // not used but required by API
k1, account, err := storeNewKey(ks, rand.Reader, pass)
@@ -75,8 +69,7 @@ func TestKeyStorePlain(t *testing.T) {
}
func TestKeyStorePassphrase(t *testing.T) {
dir, ks := tmpKeyStoreIface(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStoreIface(t, true)
pass := "foo"
k1, account, err := storeNewKey(ks, rand.Reader, pass)
@@ -96,8 +89,7 @@ func TestKeyStorePassphrase(t *testing.T) {
}
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
dir, ks := tmpKeyStoreIface(t, true)
defer os.RemoveAll(dir)
_, ks := tmpKeyStoreIface(t, true)
pass := "foo"
k1, account, err := storeNewKey(ks, rand.Reader, pass)
@@ -111,7 +103,6 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
func TestImportPreSaleKey(t *testing.T) {
dir, ks := tmpKeyStoreIface(t, true)
defer os.RemoveAll(dir)
// file content of a presale key file generated with:
// python pyethsaletool.py genwallet
+3 -3
View File
@@ -44,7 +44,7 @@ package scwallet
import (
"encoding/json"
"io/ioutil"
"io"
"os"
"path/filepath"
"sort"
@@ -106,7 +106,7 @@ func (hub *Hub) readPairings() error {
return err
}
pairingData, err := ioutil.ReadAll(pairingFile)
pairingData, err := io.ReadAll(pairingFile)
if err != nil {
return err
}
@@ -122,7 +122,7 @@ func (hub *Hub) readPairings() error {
}
func (hub *Hub) writePairings() error {
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0o755)
if err != nil {
return err
}
+2 -2
View File
@@ -188,7 +188,7 @@ func (s *SecureChannelSession) mutuallyAuthenticate() error {
return err
}
if response.Sw1 != 0x90 || response.Sw2 != 0x00 {
return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: 0x%x%x", response.Sw1, response.Sw2)
return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: %#x%x", response.Sw1, response.Sw2)
}
if len(response.Data) != scSecretLength {
@@ -271,7 +271,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b
rapdu.deserialize(plainData)
if rapdu.Sw1 != sw1Ok {
return nil, fmt.Errorf("unexpected response status Cla=0x%x, Ins=0x%x, Sw=0x%x%x", cla, ins, rapdu.Sw1, rapdu.Sw2)
return nil, fmt.Errorf("unexpected response status Cla=%#x, Ins=%#x, Sw=%#x%x", cla, ins, rapdu.Sw1, rapdu.Sw2)
}
return rapdu, nil
+1 -1
View File
@@ -177,7 +177,7 @@ func transmit(card *pcsc.Card, command *commandAPDU) (*responseAPDU, error) {
}
if response.Sw1 != sw1Ok {
return nil, fmt.Errorf("unexpected insecure response status Cla=0x%x, Ins=0x%x, Sw=0x%x%x", command.Cla, command.Ins, response.Sw1, response.Sw2)
return nil, fmt.Errorf("unexpected insecure response status Cla=%#x, Ins=%#x, Sw=%#x%x", command.Cla, command.Ins, response.Sw1, response.Sw2)
}
return response, nil
+4 -3
View File
@@ -42,9 +42,10 @@ func TestURLParsing(t *testing.T) {
t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path)
}
_, err = parseURL("ethereum.org")
if err == nil {
t.Error("expected err, got: nil")
for _, u := range []string{"ethereum.org", ""} {
if _, err = parseURL(u); err == nil {
t.Errorf("input %v, expected err, got: nil", u)
}
}
}
+33
View File
@@ -0,0 +1,33 @@
# Consensus
Disclaimer: the consensus package in subnet-evm is a complete misnomer.
The consensus package in go-ethereum handles block validation and specifically handles validating the PoW portion of consensus - thus the name.
Since AvalancheGo handles consensus for Subnet-EVM, Subnet-EVM is just the VM, but we keep the consensus package in place to handle part of the block verification process.
## Block Verification
The dummy consensus engine is responsible for performing verification on the header of a block. The engine verifies that all of the fields of the header are correct.
## Dynamic Fees
Subnet-EVM includes a dynamic fee algorithm based off of (EIP-1559)[https://eips.ethereum.org/EIPS/eip-1559]. This introduces a field to the block type called `BaseFee`. The Base Fee sets a minimum gas price for any transaction to be included in the block. For example, a transaction with a gas price of 49 gwei, will be invalid to include in a block with a base fee of 50 gwei.
The dynamic fee algorithm aims to adjust the base fee to handle network congestion. Subnet-EVM sets a target utilization on the network, and the dynamic fee algorithm adjusts the base fee accordingly. If the network operates above the target utilization, the dynamic fee algorithm will increase the base fee to make utilizing he network more expensive and bring overall utilization down. If the network operates below the target utilization, the dynamic fee algorithm will decrease the base fee to make it cheaper to use the network.
- EIP-1559 is intended for Ethereum where a block is produced roughly every 10s
- The dynamic fee algorithm needs to handle the case that the network quiesces and there are no blocks for a long period of time
- Since Subnet-EVM produces blocks at a different cadence, it adapts EIP-1559 to sum the amount of gas consumed within a 10 second interval instead of using only the amount of gas consumed in the parent block
## Consensus Engine Callbacks
The consensus engine is called while blocks are being both built and processed and Subnet-EVM adds callback functions into the dummy consensus engine to insert its own logic into these stages.
### FinalizeAndAssemble
The FinalizeAndAssemble callback is used as the final step in building a block within the miner package.
### Finalize
Finalize is called as the final step in processing a block [here](../../core/state_processor.go). Since either Finalize or FinalizeAndAssemble are called, but not both, when building or verifying/processing a block they need to perform the exact same processing/verification step to ensure that a block produced by the miner where FinalizeAndAssemble is called will be processed and verified in the same way when Finalize gets called.
+16 -2
View File
@@ -254,11 +254,20 @@ func (self *DummyEngine) verifyBlockFee(
if err != nil {
return err
}
// Multiply the [txFeePremium] by the gasUsed in the transaction since this gives the total coin that was paid
// above the amount required if the transaction had simply paid the minimum base fee for the block.
//
// Ex. LegacyTx paying a gas price of 100 gwei for 1M gas in a block with a base fee of 10 gwei.
// Total Fee = 100 gwei * 1M gas
// Minimum Fee = 10 gwei * 1M gas (minimum fee that would have been accepted for this transaction)
// Fee Premium = 90 gwei
// Total Overpaid = 90 gwei * 1M gas
blockFeeContribution.Mul(txFeePremium, gasUsed.SetUint64(receipt.GasUsed))
totalBlockFee.Add(totalBlockFee, blockFeeContribution)
}
// Calculate how much gas the [totalBlockFee] would purchase at the price level
// set by this block.
// set by the base fee of this block.
blockGas := new(big.Int).Div(totalBlockFee, baseFee)
// Require that the amount of gas purchased by the effective tips within the block, [blockGas],
@@ -282,6 +291,8 @@ func (self *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *type
return err
}
// Calculate the expected blockGasCost for this block.
// Note: this is a deterministic transtion that defines an exact block fee for this block.
blockGasCost := calcBlockGasCost(
feeConfig.TargetBlockRate,
feeConfig.MinBlockGasCost,
@@ -290,9 +301,11 @@ func (self *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *type
parent.BlockGasCost,
parent.Time, block.Time(),
)
// Verify the BlockGasCost set in the header matches the calculated value.
if blockBlockGasCost := block.BlockGasCost(); blockBlockGasCost == nil || !blockBlockGasCost.IsUint64() || blockBlockGasCost.Cmp(blockGasCost) != 0 {
return fmt.Errorf("invalid blockGasCost: have %d, want %d", blockBlockGasCost, blockGasCost)
}
// Verify the block fee was paid.
if err := self.verifyBlockFee(
block.BaseFee(),
block.BlockGasCost(),
@@ -314,7 +327,7 @@ func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader,
if err != nil {
return nil, err
}
// Calculate the required block gas cost for this block.
header.BlockGasCost = calcBlockGasCost(
feeConfig.TargetBlockRate,
feeConfig.MinBlockGasCost,
@@ -323,6 +336,7 @@ func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader,
parent.BlockGasCost,
parent.Time, header.Time,
)
// Verify that this block covers the block fee.
if err := self.verifyBlockFee(
header.BaseFee,
header.BlockGasCost,
+31
View File
@@ -0,0 +1,31 @@
# Core Package
The core package maintains the backend for the blockchain, transaction pool, and maintains the required indexes for blocks, transactions, logs, and transaction receipts.
## Blockchain
The [BlockChain](./blockchain.go) struct handles the insertion of blocks into the maintained chain. It maintains a "canonical chain", which is essentially the preferred chain (the chain that ends with the block preferred by the AvalancheGo consensus engine).
When the consensus engine verifies blocks as they are ready to be issued into consensus, it calls `Verify()` on the ChainVM Block interface implemented [here](../plugin/evm/block.go). This calls `InsertBlockManual` on the BlockChain struct implemented in this package, which is the first entrypoint of a block into the blockchain.
InsertBlockManual verifies the block, inserts it into the state manager to track the merkle trie for the block, and adds it to the canonical chain if it extends the currently preferred chain.
Subnet-EVM adds functions for Accept and Reject, which take care of marking a block as finalized and performing garbage collection where possible.
The consensus engine can also call `SetPreference` on a VM to tell the VM that a specific block is preferred by the consensus engine to be accepted. This triggers a call to `reorg` the blockchain and set the newly preferred block as the preferred chain.
## Transaction Pool
The transaction pool maintains the set of transactions that need to be issued into a new block. The VM exposes APIs that allow clients to issue transactions into the transaction pool and also performs gossip across the network in order to send and receive pending transactions that need to be issued into a new block. The transaction pool asynchronously follows the preferred block of the `BlockChain` struct by subscribing to new head events and updating its state accordingly. When the transaction pool updates, it ensures that any transactions it contains are still valid to be issued on top of the new preferred block.
## State Manager
The State Manager manages the [TrieDB](../trie/database.go). The TrieDB tracks a merkle forest of all of the merkle tries for the last accepted block and processing blocks. When a block is processed, the state transition results in a new merkle trie added to the merkle forest. The State Manager can operate in either archival or pruning mode.
### Archival Mode
In archival mode, every merkle trie is written to disk so that the node maintains a complete history of all the blocks that it has processed.
### Pruning Mode
In pruning mode, the State Manager keeps a reference to merkle tries of processing blocks. When a block gets accepted, it stays in memory. When a block gets rejected, the state manager can dereference and clean up the no longer needed merkle trie. The State Manager does not immediately write the merkle trie to disk of a block when it gets accepted. Instead, at a regular interval (~4096 blocks) it writes the merkle trie to disk, so that it does not add the overhead of storing every accepted block's merkle trie to disk.
+4 -15
View File
@@ -28,7 +28,6 @@ package core
import (
"crypto/ecdsa"
"io/ioutil"
"math/big"
"os"
"testing"
@@ -152,14 +151,11 @@ func genTxRing(naccounts int) func(int, *BlockGen) {
func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
// Create the database in memory or in a temporary directory.
var db ethdb.Database
var err error
if !disk {
db = rawdb.NewMemoryDatabase()
} else {
dir, err := ioutil.TempDir("", "eth-core-bench")
if err != nil {
b.Fatalf("cannot create temporary directory: %v", err)
}
defer os.RemoveAll(dir)
dir := b.TempDir()
db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false)
if err != nil {
b.Fatalf("cannot create temporary database: %v", err)
@@ -264,10 +260,7 @@ func makeChainForBench(db ethdb.Database, full bool, count uint64) {
func benchWriteChain(b *testing.B, full bool, count uint64) {
for i := 0; i < b.N; i++ {
dir, err := ioutil.TempDir("", "eth-chain-bench")
if err != nil {
b.Fatalf("cannot create temporary directory: %v", err)
}
dir := b.TempDir()
db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
if err != nil {
b.Fatalf("error opening database at %v: %v", dir, err)
@@ -279,11 +272,7 @@ func benchWriteChain(b *testing.B, full bool, count uint64) {
}
func benchReadChain(b *testing.B, full bool, count uint64) {
dir, err := ioutil.TempDir("", "eth-chain-bench")
if err != nil {
b.Fatalf("cannot create temporary directory: %v", err)
}
defer os.RemoveAll(dir)
dir := b.TempDir()
db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
if err != nil {
+19 -20
View File
@@ -56,7 +56,9 @@ import (
)
var (
acceptorQueueGauge = metrics.NewRegisteredGauge("blockchain/acceptor/queue/size", nil)
acceptorQueueGauge = metrics.NewRegisteredGauge("blockchain/acceptor/queue/size", nil)
processedBlockGasUsedCounter = metrics.NewRegisteredCounter("blockchain/blocks/gas/used/processed", nil)
acceptedBlockGasUsedCounter = metrics.NewRegisteredCounter("blockchain/blocks/gas/used/accepted", nil)
ErrRefuseToCorruptArchiver = errors.New("node has operated with pruning disabled, shutting down to prevent missing tries")
@@ -545,20 +547,25 @@ func (bc *BlockChain) Export(w io.Writer) error {
// ExportN writes a subset of the active chain to the given writer.
func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
bc.chainmu.RLock()
defer bc.chainmu.RUnlock()
if first > last {
return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last)
}
log.Info("Exporting batch of blocks", "count", last-first+1)
start, reported := time.Now(), time.Now()
var (
parentHash common.Hash
start = time.Now()
reported = time.Now()
)
for nr := first; nr <= last; nr++ {
block := bc.GetBlockByNumber(nr)
if block == nil {
return fmt.Errorf("export failed on #%d: not found", nr)
}
if nr > first && block.ParentHash() != parentHash {
return fmt.Errorf("export failed: chain reorg during export")
}
parentHash = block.Hash()
if err := block.EncodeRLP(w); err != nil {
return err
}
@@ -816,6 +823,8 @@ func (bc *BlockChain) Accept(block *types.Block) error {
bc.lastAccepted = block
bc.addAcceptorQueue(block)
acceptedBlockGasUsedCounter.Inc(int64(block.GasUsed()))
return nil
}
@@ -1113,6 +1122,7 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error {
"root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(),
)
processedBlockGasUsedCounter.Inc(int64(block.GasUsed()))
return nil
}
@@ -1308,7 +1318,7 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e
Chain config: %v
Number: %v
Hash: 0x%x
Hash: %#x
%v
Error: %v
@@ -1683,6 +1693,7 @@ func (bc *BlockChain) CleanBlockRootsAboveLastAccepted() error {
// consensus engine will reject the lowest ancestor first. In this case, these blocks will not be considered acceptable in
// the future.
// Ex.
//
// A
// / \
// B C
@@ -1758,22 +1769,10 @@ func (bc *BlockChain) ResetState(block *types.Block) error {
// Make sure the state associated with the block is available
head := bc.CurrentBlock()
if _, err := state.New(head.Root(), bc.stateCache, nil); err != nil {
if !bc.HasState(head.Root()) {
return fmt.Errorf("head state missing %d:%s", head.Number(), head.Hash())
}
// Load any existing snapshot, regenerating it if loading failed
if bc.cacheConfig.SnapshotLimit > 0 {
var err error
// If we are starting from genesis, generate the original snapshot disk layer
// up front, so we can use it while executing blocks in bootstrapping. This
// also avoids a costly async generation process when reaching tip.
async := bc.cacheConfig.SnapshotAsync && head.NumberU64() > 0
log.Info("Initializing snapshots", "async", async)
bc.snaps, err = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Hash(), head.Root(), async, true, bc.cacheConfig.SnapshotVerify)
if err != nil {
log.Error("failed to initialize snapshots", "headHash", head.Hash(), "headRoot", head.Root(), "err", err, "async", async)
}
}
bc.initSnapshot(head)
return nil
}
-12
View File
@@ -240,18 +240,6 @@ func (bc *BlockChain) ContractCode(hash common.Hash) ([]byte, error) {
return bc.stateCache.ContractCode(common.Hash{}, hash)
}
// ContractCodeWithPrefix retrieves a blob of data associated with a contract
// hash either from ephemeral in-memory cache, or from persistent storage.
//
// If the code doesn't exist in the in-memory cache, check the storage with
// new code scheme.
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) {
type codeReader interface {
ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error)
}
return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash)
}
// State returns a new mutable state based on the current HEAD block.
func (bc *BlockChain) State() (*state.StateDB, error) {
return bc.StateAt(bc.CurrentBlock().Root())
+1 -7
View File
@@ -31,9 +31,7 @@
package core
import (
"io/ioutil"
"math/big"
"os"
"testing"
"github.com/ava-labs/subnet-evm/consensus/dummy"
@@ -510,11 +508,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
// fmt.Println(tt.dump(true))
// Create a temporary persistent database
datadir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Failed to create temporary datadir: %v", err)
}
os.RemoveAll(datadir)
datadir := t.TempDir()
db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false)
if err != nil {
+3 -61
View File
@@ -32,7 +32,6 @@ package core
import (
"bytes"
"fmt"
"io/ioutil"
"math/big"
"os"
"strings"
@@ -68,11 +67,7 @@ type snapshotTestBasic struct {
func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) {
// Create a temporary persistent database
datadir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Failed to create temporary datadir: %v", err)
}
os.RemoveAll(datadir)
datadir := t.TempDir()
db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false)
if err != nil {
@@ -164,6 +159,7 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [
}
}
//nolint:unused
func (basic *snapshotTestBasic) dump() string {
buffer := new(strings.Builder)
@@ -321,60 +317,6 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
snaptest.verify(t, newchain, blocks)
}
// restartCrashSnapshotTest is the test type used to test this scenario:
// - have a complete snapshot
// - restart chain
// - insert more blocks with enabling the snapshot
// - commit the snapshot
// - crash
// - restart again
type restartCrashSnapshotTest struct {
snapshotTestBasic
newBlocks int
}
func (snaptest *restartCrashSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
// fmt.Println(tt.dump())
chain, blocks := snaptest.prepare(t)
// Firstly, stop the chain properly, with all snapshot journal
// and state committed.
chain.Stop()
newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
newBlocks, _, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, 10, func(i int, b *BlockGen) {})
newchain.InsertChain(newBlocks)
// Commit the entire snapshot into the disk if requested. Note only
// (a) snapshot root and (b) snapshot generator will be committed,
// the diff journal is not.
for i := uint64(0); i < uint64(len(newBlocks)); i++ {
if err := newchain.Accept(newBlocks[i]); err != nil {
t.Fatalf("Failed to accept block %v: %v", i, err)
}
snaptest.lastAcceptedHash = newBlocks[i].Hash()
}
chain.DrainAcceptorQueue()
// Simulate the blockchain crash
// Don't call chain.Stop here, so that no snapshot
// journal and latest state will be committed
// Restart the chain after the crash
newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
defer newchain.Stop()
snaptest.verify(t, newchain, blocks)
}
// wipeCrashSnapshotTest is the test type used to test this scenario:
// - have a complete snapshot
// - restart, insert more blocks without enabling the snapshot
@@ -418,7 +360,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
Pruning: true,
CommitInterval: 4096,
}
newchain, err = NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
_, err = NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
+115
View File
@@ -272,6 +272,7 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
t.Parallel()
tt.testFunc(t, create)
})
}
@@ -525,3 +526,117 @@ func TestUngracefulAsyncShutdown(t *testing.T) {
}
}
}
// TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted
// correctly in case reorg is called.
func TestCanonicalHashMarker(t *testing.T) {
cases := []struct {
forkA int
forkB int
}{
// ForkA: 10 blocks
// ForkB: 1 blocks
//
// reorged:
// markers [2, 10] should be deleted
// markers [1] should be updated
{10, 1},
// ForkA: 10 blocks
// ForkB: 2 blocks
//
// reorged:
// markers [3, 10] should be deleted
// markers [1, 2] should be updated
{10, 2},
// ForkA: 10 blocks
// ForkB: 10 blocks
//
// reorged:
// markers [1, 10] should be updated
{10, 10},
// ForkA: 10 blocks
// ForkB: 11 blocks
//
// reorged:
// markers [1, 11] should be updated
{10, 11},
}
for _, c := range cases {
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: params.TestChainConfig,
Alloc: GenesisAlloc{},
BaseFee: big.NewInt(params.TestInitialBaseFee),
}
genesis = gspec.MustCommit(db)
engine = dummy.NewFaker()
)
forkA, _, err := GenerateChain(params.TestChainConfig, genesis, engine, db, c.forkA, 10, func(i int, gen *BlockGen) {})
if err != nil {
t.Fatal(err)
}
forkB, _, err := GenerateChain(params.TestChainConfig, genesis, engine, db, c.forkB, 10, func(i int, gen *BlockGen) {})
if err != nil {
t.Fatal(err)
}
// Initialize test chain
diskdb := rawdb.NewMemoryDatabase()
gspec.MustCommit(diskdb)
chain, err := NewBlockChain(diskdb, DefaultCacheConfig, params.TestChainConfig, engine, vm.Config{}, common.Hash{})
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
// Insert forkA and forkB, the canonical should on forkA still
if n, err := chain.InsertChain(forkA); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}
if n, err := chain.InsertChain(forkB); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}
verify := func(head *types.Block) {
if chain.CurrentBlock().Hash() != head.Hash() {
t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash())
}
if chain.CurrentHeader().Hash() != head.Hash() {
t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash())
}
if !chain.HasState(head.Root()) {
t.Fatalf("Lost block state %v %x", head.Number(), head.Hash())
}
}
// Switch canonical chain to forkB if necessary
if len(forkA) < len(forkB) {
verify(forkB[len(forkB)-1])
} else {
verify(forkA[len(forkA)-1])
if err := chain.SetPreference(forkB[len(forkB)-1]); err != nil {
t.Fatal(err)
}
verify(forkB[len(forkB)-1])
}
// Ensure all hash markers are updated correctly
for i := 0; i < len(forkB); i++ {
block := forkB[i]
hash := chain.GetCanonicalHash(block.NumberU64())
if hash != block.Hash() {
t.Fatalf("Unexpected canonical hash %d", block.NumberU64())
}
}
if c.forkA > c.forkB {
for i := uint64(c.forkB) + 1; i <= uint64(c.forkA); i++ {
hash := chain.GetCanonicalHash(i)
if hash != (common.Hash{}) {
t.Fatalf("Unexpected canonical hash %d", i)
}
}
}
}
}
+2 -2
View File
@@ -134,13 +134,13 @@ func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes {
// testMatcherDiffBatches runs the given matches test in single-delivery and also
// in batches delivery mode, verifying that all kinds of deliveries are handled
// correctly withn.
// correctly within.
func testMatcherDiffBatches(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32) {
singleton := testMatcher(t, filter, start, blocks, intermittent, retrievals, 1)
batched := testMatcher(t, filter, start, blocks, intermittent, retrievals, 16)
if singleton != batched {
t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in signleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched)
t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in singleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched)
}
}
+6 -1
View File
@@ -41,7 +41,7 @@ type ChainContext interface {
// Engine retrieves the chain's consensus engine.
Engine() consensus.Engine
// GetHeader returns the hash corresponding to their hash.
// GetHeader returns the header corresponding to the hash/number argument pair.
GetHeader(common.Hash, uint64) *types.Header
}
@@ -89,6 +89,11 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
var cache []common.Hash
return func(n uint64) common.Hash {
if ref.Number.Uint64() <= n {
// This situation can happen if we're doing tracing and using
// block overrides.
return common.Hash{}
}
// If there's no hash cache yet, make one
if len(cache) == 0 {
cache = append(cache, ref.ParentHash)
+6 -6
View File
@@ -56,8 +56,8 @@ import (
//go:embed airdrops/011522.json
var AirdropData []byte
//go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
//go:generate gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go
//go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
//go:generate go run github.com/fjl/gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go
var errGenesisNoConfig = errors.New("genesis has no chain configuration")
@@ -171,8 +171,8 @@ func (e *GenesisMismatchError) Error() string {
//
// genesis == nil genesis != nil
// +------------------------------------------
// db has no genesis | ErrNoGenesis | genesis
// db has genesis | ErrNoGenesis | genesis (if compatible both block hash and chain config), else error
// db has no genesis | main-net default | genesis
// db has genesis | from DB | genesis (if compatible)
// The argument [genesis] must be specified and must contain a valid chain config.
// If the genesis block has already been set up, then we verify the hash matches the genesis passed in
@@ -324,7 +324,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if g.BaseFee != nil {
head.BaseFee = g.BaseFee
} else {
head.BaseFee = g.Config.FeeConfig.MinBaseFee
head.BaseFee = new(big.Int).Set(g.Config.FeeConfig.MinBaseFee)
}
}
statedb.Commit(false, false)
@@ -377,7 +377,7 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
g := Genesis{
Config: params.TestChainConfig,
Alloc: GenesisAlloc{addr: {Balance: balance}},
BaseFee: big.NewInt(params.TestMaxBaseFee.Int64()),
BaseFee: new(big.Int).Set(params.TestMaxBaseFee),
}
return g.MustCommit(db)
}
+3 -3
View File
@@ -84,8 +84,8 @@ type NumberHash struct {
Hash common.Hash
}
// ReadAllHashes retrieves all the hashes assigned to blocks at a certain heights,
// both canonical and reorged forks included.
// ReadAllHashesInRange retrieves all the hashes assigned to blocks at a certain
// heights, both canonical and reorged forks included.
// This method considers both limits to be _inclusive_.
func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash {
var (
@@ -378,7 +378,7 @@ func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Rec
}
// ReadReceipts retrieves all the transaction receipts belonging to a block, including
// its correspoinding metadata fields. If it is unable to populate these metadata
// its corresponding metadata fields. If it is unable to populate these metadata
// fields then nil is returned.
//
// The current implementation populates these metadata fields by reading the receipts'
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"bytes"
"encoding/hex"
"fmt"
"io/ioutil"
"math/big"
"os"
"reflect"
"testing"
@@ -601,7 +601,7 @@ func TestDeriveLogFields(t *testing.T) {
func BenchmarkDecodeRLPLogs(b *testing.B) {
// Encoded receipts from block 0x14ee094309fbe8f70b65f45ebcc08fb33f126942d97464aad5eb91cfd1e2d269
buf, err := ioutil.ReadFile("testdata/stored_receipts.bin")
buf, err := os.ReadFile("testdata/stored_receipts.bin")
if err != nil {
b.Fatal(err)
}
+2 -10
View File
@@ -131,12 +131,12 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com
// IterateStorageSnapshots returns an iterator for walking the entire storage
// space of a specific account.
func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator {
return db.NewIterator(storageSnapshotsKey(accountHash), nil)
return NewKeyLengthIterator(db.NewIterator(storageSnapshotsKey(accountHash), nil), len(SnapshotStoragePrefix)+2*common.HashLength)
}
// IterateAccountSnapshots returns an iterator for walking all of the accounts in the snapshot
func IterateAccountSnapshots(db ethdb.Iteratee) ethdb.Iterator {
return db.NewIterator(SnapshotAccountPrefix, nil)
return NewKeyLengthIterator(db.NewIterator(SnapshotAccountPrefix, nil), len(SnapshotAccountPrefix)+common.HashLength)
}
// ReadSnapshotGenerator retrieves the serialized snapshot generator saved at
@@ -153,11 +153,3 @@ func WriteSnapshotGenerator(db ethdb.KeyValueWriter, generator []byte) {
log.Crit("Failed to store snapshot generator", "err", err)
}
}
// DeleteSnapshotGenerator deletes the serialized snapshot generator saved at
// the last shutdown
func DeleteSnapshotGenerator(db ethdb.KeyValueWriter) {
if err := db.Delete(snapshotGeneratorKey); err != nil {
log.Crit("Failed to remove snapshot generator", "err", err)
}
}
+27 -50
View File
@@ -38,61 +38,13 @@ func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
return data
}
// WritePreimages writes the provided set of preimages to the database.
func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
for hash, preimage := range preimages {
if err := db.Put(preimageKey(hash), preimage); err != nil {
log.Crit("Failed to store trie preimage", "err", err)
}
}
preimageCounter.Inc(int64(len(preimages)))
preimageHitCounter.Inc(int64(len(preimages)))
}
// ReadCode retrieves the contract code of the provided code hash.
func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte {
// Try with the prefixed code scheme first, if not then try with legacy
// scheme.
data := ReadCodeWithPrefix(db, hash)
if len(data) != 0 {
return data
}
// TODO: we have never used the legacy scheme, so we should be able to
// remove the latter attempt.
data, _ = db.Get(hash[:])
return data
}
// ReadCodeWithPrefix retrieves the contract code of the provided code hash.
// The main difference between this function and ReadCode is this function
// will only check the existence with latest scheme(with prefix).
func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
// Try with the prefixed code scheme first and only. The legacy scheme was never used in subnet-evm.
data, _ := db.Get(codeKey(hash))
return data
}
// HasCodeWithPrefix checks if the contract code corresponding to the
// provided code hash is present in the db. This function will only check
// presence using the prefix-scheme.
func HasCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) bool {
ok, _ := db.Has(codeKey(hash))
return ok
}
// WriteCode writes the provided contract code database.
func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
if err := db.Put(codeKey(hash), code); err != nil {
log.Crit("Failed to store contract code", "err", err)
}
}
// DeleteCode deletes the specified contract code from the database.
func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(codeKey(hash)); err != nil {
log.Crit("Failed to delete contract code", "err", err)
}
}
// ReadTrieNode retrieves the trie node of the provided hash.
func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
data, _ := db.Get(hash.Bytes())
@@ -102,7 +54,7 @@ func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
// HasCode checks if the contract code corresponding to the
// provided code hash is present in the db.
func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool {
// Try with the prefixed code scheme first and only. The legacy scheme was never used in coreth.
// Try with the prefixed code scheme first and only. The legacy scheme was never used in subnet-evm.
ok, _ := db.Has(codeKey(hash))
return ok
}
@@ -113,6 +65,24 @@ func HasTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool {
return ok
}
// WritePreimages writes the provided set of preimages to the database.
func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
for hash, preimage := range preimages {
if err := db.Put(preimageKey(hash), preimage); err != nil {
log.Crit("Failed to store trie preimage", "err", err)
}
}
preimageCounter.Inc(int64(len(preimages)))
preimageHitCounter.Inc(int64(len(preimages)))
}
// WriteCode writes the provided contract code database.
func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
if err := db.Put(codeKey(hash), code); err != nil {
log.Crit("Failed to store contract code", "err", err)
}
}
// WriteTrieNode writes the provided trie node database.
func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
if err := db.Put(hash.Bytes(), node); err != nil {
@@ -120,6 +90,13 @@ func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
}
}
// DeleteCode deletes the specified contract code from the database.
func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(codeKey(hash)); err != nil {
log.Crit("Failed to delete contract code", "err", err)
}
}
// DeleteTrieNode deletes the specified trie node from the database.
func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(hash.Bytes()); err != nil {
+2 -1
View File
@@ -204,7 +204,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
var accounted bool
for _, meta := range [][]byte{
databaseVersionKey, headHeaderKey, headBlockKey,
snapshotRootKey, snapshotGeneratorKey, uncleanShutdownKey,
snapshotRootKey, snapshotBlockHashKey, snapshotGeneratorKey,
uncleanShutdownKey, syncRootKey,
} {
if bytes.Equal(key, meta) {
metadata.Add(size)
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2022 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 rawdb
import (
"encoding/binary"
"testing"
)
func TestKeyLengthIterator(t *testing.T) {
db := NewMemoryDatabase()
keyLen := 8
expectedKeys := make(map[string]struct{})
for i := 0; i < 100; i++ {
key := make([]byte, keyLen)
binary.BigEndian.PutUint64(key, uint64(i))
if err := db.Put(key, []byte{0x1}); err != nil {
t.Fatal(err)
}
expectedKeys[string(key)] = struct{}{}
longerKey := make([]byte, keyLen*2)
binary.BigEndian.PutUint64(longerKey, uint64(i))
if err := db.Put(longerKey, []byte{0x1}); err != nil {
t.Fatal(err)
}
}
it := NewKeyLengthIterator(db.NewIterator(nil, nil), keyLen)
for it.Next() {
key := it.Key()
_, exists := expectedKeys[string(key)]
if !exists {
t.Fatalf("Found unexpected key %d", binary.BigEndian.Uint64(key))
}
delete(expectedKeys, string(key))
if len(key) != keyLen {
t.Fatalf("Found unexpected key in key length iterator with length %d", len(key))
}
}
if len(expectedKeys) != 0 {
t.Fatalf("Expected all keys of length %d to be removed from expected keys during iteration", keyLen)
}
}
+5
View File
@@ -131,6 +131,11 @@ func (t *table) NewBatch() ethdb.Batch {
return &tableBatch{t.db.NewBatch(), t.prefix}
}
// NewBatchWithSize creates a write-only database batch with pre-allocated buffer.
func (t *table) NewBatchWithSize(size int) ethdb.Batch {
return &tableBatch{t.db.NewBatchWithSize(size), t.prefix}
}
// tableBatch is a wrapper around a database batch that prefixes each key access
// with a pre-configured string.
type tableBatch struct {
+2 -3
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2019 The go-ethereum Authors
// Copyright 2020 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
@@ -97,8 +97,7 @@ func TestRlpIterator(t *testing.T) {
func testRlpIterator(t *testing.T, txs, uncles, datasize int) {
desc := fmt.Sprintf("%d txs [%d datasize] and %d uncles", txs, datasize, uncles)
block := getBlock(txs, uncles, datasize).Body()
bodyRlp, _ := rlp.EncodeToBytes(block)
bodyRlp, _ := rlp.EncodeToBytes(getBlock(txs, uncles, datasize).Body())
it, err := rlp.NewListIterator(bodyRlp)
if err != nil {
t.Fatal(err)
+2 -18
View File
@@ -143,7 +143,7 @@ type cachingDB struct {
// OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
tr, err := trie.NewSecure(root, db.db)
tr, err := trie.NewSecure(common.Hash{}, root, db.db)
if err != nil {
return nil, err
}
@@ -152,7 +152,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
// OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
tr, err := trie.NewSecure(root, db.db)
tr, err := trie.NewSecure(addrHash, root, db.db)
if err != nil {
return nil, err
}
@@ -183,22 +183,6 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error
return nil, errors.New("not found")
}
// ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new**
// db scheme.
func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) {
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
return code, nil
}
code := rawdb.ReadCodeWithPrefix(db.db.DiskDB(), codeHash)
if len(code) > 0 {
db.codeCache.Set(codeHash.Bytes(), code)
db.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
return nil, errors.New("not found")
}
// ContractCodeSize retrieves a particular contracts code's size.
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
+4 -4
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2020 The go-ethereum Authors
// Copyright 2021 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
@@ -49,7 +49,7 @@ func (f stateBloomHasher) BlockSize() int { panic("not implem
func (f stateBloomHasher) Size() int { return 8 }
func (f stateBloomHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) }
// stateBloom is a bloom filter used during the state convesion(snapshot->state).
// stateBloom is a bloom filter used during the state conversion(snapshot->state).
// The keys of all generated entries will be recorded here so that in the pruning
// stage the entries belong to the specific version can be avoided for deletion.
//
@@ -100,7 +100,7 @@ func (bloom *stateBloom) Commit(filename, tempname string) error {
return err
}
// Ensure the file is synced to disk
f, err := os.OpenFile(tempname, os.O_RDWR, 0666)
f, err := os.OpenFile(tempname, os.O_RDWR, 0o666)
if err != nil {
return err
}
@@ -110,7 +110,7 @@ func (bloom *stateBloom) Commit(filename, tempname string) error {
}
f.Close()
// Move the teporary file into it's final location
// Move the temporary file into it's final location
return os.Rename(tempname, filename)
}
+3 -3
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2020 The go-ethereum Authors
// Copyright 2021 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
@@ -341,7 +341,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
if genesis == nil {
return errors.New("missing genesis block")
}
t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db))
t, err := trie.NewSecure(common.Hash{}, genesis.Root(), trie.NewDatabase(db))
if err != nil {
return err
}
@@ -361,7 +361,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
return err
}
if acc.Root != emptyRoot {
storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db))
storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db))
if err != nil {
return err
}
+4 -4
View File
@@ -53,7 +53,7 @@ type trieKV struct {
type (
// trieGeneratorFn is the interface of trie generation which can
// be implemented by different trie algorithm.
trieGeneratorFn func(db ethdb.KeyValueWriter, in chan (trieKV), out chan (common.Hash))
trieGeneratorFn func(db ethdb.KeyValueWriter, owner common.Hash, in chan (trieKV), out chan (common.Hash))
// leafCallbackFn is the callback invoked at the leaves of the trie,
// returns the subtrie root with the specified subtrie identifier.
@@ -262,7 +262,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash,
wg.Add(1)
go func() {
defer wg.Done()
generatorFn(db, in, out)
generatorFn(db, account, in, out)
}()
// Spin up a go-routine for progress logging
if report && stats != nil {
@@ -369,8 +369,8 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash,
return stop(nil)
}
func stackTrieGenerate(db ethdb.KeyValueWriter, in chan trieKV, out chan common.Hash) {
t := trie.NewStackTrie(db)
func stackTrieGenerate(db ethdb.KeyValueWriter, owner common.Hash, in chan trieKV, out chan common.Hash) {
t := trie.NewStackTrieWithOwner(db, owner)
for leaf := range in {
t.TryUpdate(leaf.key[:], leaf.value)
}
+3 -1
View File
@@ -275,6 +275,9 @@ func (dl *diffLayer) BlockHash() common.Hash {
// Parent returns the subsequent layer of a diff layer.
func (dl *diffLayer) Parent() snapshot {
dl.lock.RLock()
defer dl.lock.RUnlock()
return dl.parent
}
@@ -487,7 +490,6 @@ func (dl *diffLayer) flatten() snapshot {
for storageHash, data := range storage {
comboData[storageHash] = data
}
parent.storageData[accountHash] = comboData
}
// Return the combo parent
return &diffLayer{
-1
View File
@@ -340,7 +340,6 @@ func BenchmarkFlatten(b *testing.B) {
value := make([]byte, 32)
rand.Read(value)
accStorage[randomHash()] = value
}
storage[accountKey] = accStorage
}
+2 -2
View File
@@ -31,7 +31,6 @@ import (
"testing"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/ethdb"
"github.com/ava-labs/subnet-evm/ethdb/memorydb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
@@ -514,7 +513,8 @@ func TestDiskMidAccountPartialMerge(t *testing.T) {
// TestDiskSeek tests that seek-operations work on the disk layer
func TestDiskSeek(t *testing.T) {
// Create some accounts in the disk layer
var db ethdb.Database = rawdb.NewMemoryDatabase()
db := rawdb.NewMemoryDatabase()
defer db.Close()
// Fill even keys [0,2,4...]
for i := 0; i < 0xff; i += 2 {
+2 -2
View File
@@ -274,7 +274,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
}
}
// Create an account and state iterator pointing to the current generator marker
accTrie, err := trie.NewSecure(dl.root, dl.triedb)
accTrie, err := trie.NewSecure(common.Hash{}, dl.root, dl.triedb)
if err != nil {
// The account trie is missing (GC), surf the chain until one becomes available
stats.Info("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
@@ -328,7 +328,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
// If the iterated account is a contract, iterate through corresponding contract
// storage to generate snapshot entries.
if acc.Root != emptyRoot {
storeTrie, err := trie.NewSecure(acc.Root, dl.triedb)
storeTrie, err := trie.NewSecure(accountHash, acc.Root, dl.triedb)
if err != nil {
log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err)
abort := <-dl.genAbort
+326 -273
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2020 The go-ethereum Authors
// Copyright 2019 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
@@ -35,7 +35,6 @@ import (
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/ethdb"
"github.com/ava-labs/subnet-evm/ethdb/memorydb"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
@@ -43,53 +42,7 @@ import (
"golang.org/x/crypto/sha3"
)
// Tests that snapshot generation from an empty database.
func TestGeneration(t *testing.T) {
// We can't use statedb to make a test trie (circular dependency), so make
// a fake one manually. We're going with a small account trie of 3 accounts,
// two of which also has the same 3-slot storage trie attached.
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
stTrie.Commit(nil, false) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
root, _, _ := accTrie.Commit(nil, false) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
triedb.Commit(root, false, nil)
if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want {
t.Fatalf("have %#x want %#x", have, want)
}
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
select {
case <-snap.genPending:
// Snapshot generation succeeded
case <-time.After(250 * time.Millisecond):
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
stop := make(chan struct{})
snap.genAbort <- stop
<-stop
}
var testBlockHash = common.HexToHash("0xdeadbeef")
func hashData(input []byte) common.Hash {
hasher := sha3.NewLegacyKeccak256()
@@ -100,48 +53,25 @@ func hashData(input []byte) common.Hash {
return hash
}
// Tests that snapshot generation with existent flat state.
func TestGenerateExistentState(t *testing.T) {
// Tests that snapshot generation from an empty database.
func TestGeneration(t *testing.T) {
// We can't use statedb to make a test trie (circular dependency), so make
// a fake one manually. We're going with a small account trie of 3 accounts,
// two of which also has the same 3-slot storage trie attached.
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
stTrie.Commit(nil, false) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper := newHelper()
stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, false)
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-1")), val)
rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-3")), []byte("val-3"))
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
diskdb.Put(hashData([]byte("acc-2")).Bytes(), val)
rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-2")), val)
helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-3")), val)
rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-3")), []byte("val-3"))
root, snap := helper.CommitAndGenerate() // two of which also has the same 3-slot storage trie attached.
root, _, _ := accTrie.Commit(nil, false) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
triedb.Commit(root, false, nil)
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want {
t.Fatalf("have %#x want %#x", have, want)
}
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -150,6 +80,43 @@ func TestGenerateExistentState(t *testing.T) {
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
stop := make(chan struct{})
snap.genAbort <- stop
<-stop
}
// Tests that snapshot generation with existent flat state.
func TestGenerateExistentState(t *testing.T) {
// We can't use statedb to make a test trie (circular dependency), so make
// a fake one manually. We're going with a small account trie of 3 accounts,
// two of which also has the same 3-slot storage trie attached.
helper := newHelper()
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
root, snap := helper.CommitAndGenerate()
select {
case <-snap.genPending:
// Snapshot generation succeeded
case <-time.After(250 * time.Millisecond):
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
stop := make(chan struct{})
snap.genAbort <- stop
@@ -177,18 +144,21 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
if snapRoot != trieRoot {
t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot)
}
if err := CheckDanglingStorage(snap.diskdb); err != nil {
t.Fatalf("Detected dangling storages: %v", err)
}
}
type testHelper struct {
diskdb *memorydb.Database
diskdb ethdb.Database
triedb *trie.Database
accTrie *trie.SecureTrie
}
func newHelper() *testHelper {
diskdb := memorydb.New()
diskdb := rawdb.NewMemoryDatabase()
triedb := trie.NewDatabase(diskdb)
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
accTrie, _ := trie.NewSecure(common.Hash{}, common.Hash{}, triedb)
return &testHelper{
diskdb: diskdb,
triedb: triedb,
@@ -219,19 +189,29 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string)
}
}
func (t *testHelper) makeStorageTrie(keys []string, vals []string) []byte {
stTrie, _ := trie.NewSecure(common.Hash{}, t.triedb)
func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) []byte {
stTrie, _ := trie.NewSecure(owner, common.Hash{}, t.triedb)
for i, k := range keys {
stTrie.Update([]byte(k), []byte(vals[i]))
}
root, _, _ := stTrie.Commit(nil, false)
var root common.Hash
if !commit {
root = stTrie.Hash()
} else {
root, _, _ = stTrie.Commit(nil, false)
}
return root.Bytes()
}
func (t *testHelper) Generate() (common.Hash, *diskLayer) {
func (t *testHelper) Commit() common.Hash {
root, _, _ := t.accTrie.Commit(nil, false)
t.triedb.Commit(root, false, nil)
snap := generateSnapshot(t.diskdb, t.triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
return root
}
func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) {
root := t.Commit()
snap := generateSnapshot(t.diskdb, t.triedb, 16, testBlockHash, root, nil)
return root, snap
}
@@ -243,36 +223,41 @@ func (t *testHelper) Generate() (common.Hash, *diskLayer) {
// - miss in the beginning
// - miss in the middle
// - miss in the end
//
// - the contract(non-empty storage) has wrong storage slots
// - wrong slots in the beginning
// - wrong slots in the middle
// - wrong slots in the end
//
// - the contract(non-empty storage) has extra storage slots
// - extra slots in the beginning
// - extra slots in the middle
// - extra slots in the end
func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
helper := newHelper()
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Account one, empty root but non-empty database
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Account two, non empty root but empty database
stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
// Miss slots
{
// Account three, non empty root but misses slots in the beginning
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"})
// Account four, non empty root but misses slots in the middle
helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"})
// Account five, non empty root but misses slots in the end
helper.makeStorageTrie(hashData([]byte("acc-5")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-5", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"})
}
@@ -280,18 +265,22 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
// Wrong storage slots
{
// Account six, non empty root but wrong slots in the beginning
helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"})
// Account seven, non empty root but wrong slots in the middle
helper.makeStorageTrie(hashData([]byte("acc-7")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-7", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"})
// Account eight, non empty root but wrong slots in the end
helper.makeStorageTrie(hashData([]byte("acc-8")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-8", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"})
// Account 9, non empty root but rotated slots
helper.makeStorageTrie(hashData([]byte("acc-9")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-9", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"})
}
@@ -299,19 +288,22 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
// Extra storage slots
{
// Account 10, non empty root but extra slots in the beginning
helper.makeStorageTrie(hashData([]byte("acc-10")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-10", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"})
// Account 11, non empty root but extra slots in the middle
helper.makeStorageTrie(hashData([]byte("acc-11")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-11", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"})
// Account 12, non empty root but extra slots in the end
helper.makeStorageTrie(hashData([]byte("acc-12")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-12", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"})
}
root, snap := helper.Generate()
root, snap := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0
select {
@@ -335,7 +327,12 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
// - extra accounts
func TestGenerateExistentStateWithWrongAccounts(t *testing.T) {
helper := newHelper()
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
stRoot := helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
// Trie accounts [acc-1, acc-2, acc-3, acc-4, acc-6]
// Extra accounts [acc-0, acc-5, acc-7]
@@ -363,7 +360,7 @@ func TestGenerateExistentStateWithWrongAccounts(t *testing.T) {
helper.addSnapAccount("acc-7", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyRoot.Bytes()}) // after the end
}
root, snap := helper.Generate()
root, snap := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8
select {
@@ -387,29 +384,19 @@ func TestGenerateCorruptAccountTrie(t *testing.T) {
// We can't use statedb to make a test trie (circular dependency), so make
// a fake one manually. We're going with a small account trie of 3 accounts,
// without any storage slots to keep the test smaller.
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
tr, _ := trie.NewSecure(common.Hash{}, triedb)
acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
tr.Update([]byte("acc-1"), val) // 0x7dd654835190324640832972b7c4c6eaa0c50541e36766d054ed57721f1dc7eb
helper := newHelper()
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
tr.Update([]byte("acc-2"), val) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
acc = &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
tr.Update([]byte("acc-3"), val) // 0x515d3de35e143cd976ad476398d910aa7bf8a02e8fd7eb9e3baacddbbcbfcb41
tr.Commit(nil, false) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
root, _, _ := helper.accTrie.Commit(nil, false) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
// Delete an account trie leaf and ensure the generator chokes
triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil)
diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes())
helper.triedb.Commit(root, false, nil)
helper.diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes())
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), nil)
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil)
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -431,47 +418,32 @@ func TestGenerateMissingStorageTrie(t *testing.T) {
// We can't use statedb to make a test trie (circular dependency), so make
// a fake one manually. We're going with a small account trie of 3 accounts,
// two of which also has the same 3-slot storage trie attached.
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
stTrie.Commit(nil, false) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper := newHelper()
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
accTrie.Commit(nil, false) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
root, _, _ := helper.accTrie.Commit(nil, false)
// We can only corrupt the disk database, so flush the tries out
triedb.Reference(
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
helper.triedb.Reference(
common.BytesToHash(stRoot),
common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
true,
)
triedb.Reference(
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
helper.triedb.Reference(
common.BytesToHash(stRoot),
common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
true,
)
triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil)
helper.triedb.Commit(root, false, nil)
// Delete a storage trie root and ensure the generator chokes
diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes())
helper.diskdb.Delete(stRoot) // We can only corrupt the disk database, so flush the tries out
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil)
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil)
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -492,47 +464,33 @@ func TestGenerateCorruptStorageTrie(t *testing.T) {
// We can't use statedb to make a test trie (circular dependency), so make
// a fake one manually. We're going with a small account trie of 3 accounts,
// two of which also has the same 3-slot storage trie attached.
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
stTrie.Commit(nil, false) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper := newHelper()
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
accTrie.Commit(nil, false) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
root, _, _ := helper.accTrie.Commit(nil, false)
// We can only corrupt the disk database, so flush the tries out
triedb.Reference(
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
helper.triedb.Reference(
common.BytesToHash(stRoot),
common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
true,
)
triedb.Reference(
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
helper.triedb.Reference(
common.BytesToHash(stRoot),
common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
true,
)
triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil)
helper.triedb.Commit(root, false, nil)
// Delete a storage trie leaf and ensure the generator chokes
diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes())
helper.diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes())
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil)
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil)
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -547,56 +505,51 @@ func TestGenerateCorruptStorageTrie(t *testing.T) {
<-stop
}
func getStorageTrie(n int, triedb *trie.Database) *trie.SecureTrie {
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
for i := 0; i < n; i++ {
k := fmt.Sprintf("key-%d", i)
v := fmt.Sprintf("val-%d", i)
stTrie.Update([]byte(k), []byte(v))
}
stTrie.Commit(nil, false)
return stTrie
}
// Tests that snapshot generation when an extra account with storage exists in the snap state.
func TestGenerateWithExtraAccounts(t *testing.T) {
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
stTrie = getStorageTrie(5, triedb)
)
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
{ // Account one in the trie
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
helper := newHelper()
{
// Account one in the trie
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")),
[]string{"key-1", "key-2", "key-3", "key-4", "key-5"},
[]string{"val-1", "val-2", "val-3", "val-4", "val-5"},
true,
)
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
helper.accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
// Identical in the snap
key := hashData([]byte("acc-1"))
rawdb.WriteAccountSnapshot(diskdb, key, val)
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-4")), []byte("val-4"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-5")), []byte("val-5"))
rawdb.WriteAccountSnapshot(helper.triedb.DiskDB(), key, val)
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-3")), []byte("val-3"))
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-4")), []byte("val-4"))
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-5")), []byte("val-5"))
}
{ // Account two exists only in the snapshot
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
{
// Account two exists only in the snapshot
stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")),
[]string{"key-1", "key-2", "key-3", "key-4", "key-5"},
[]string{"val-1", "val-2", "val-3", "val-4", "val-5"},
true,
)
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
key := hashData([]byte("acc-2"))
rawdb.WriteAccountSnapshot(diskdb, key, val)
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3"))
rawdb.WriteAccountSnapshot(helper.triedb.DiskDB(), key, val)
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-1")), []byte("b-val-1"))
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-2")), []byte("b-val-2"))
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-3")), []byte("b-val-3"))
}
root, _, _ := accTrie.Commit(nil, false)
t.Logf("root: %x", root)
triedb.Commit(root, false, nil)
root := helper.Commit()
// To verify the test: If we now inspect the snap db, there should exist extraneous storage items
if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil {
if data := rawdb.ReadStorageSnapshot(helper.triedb.DiskDB(), hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil {
t.Fatalf("expected snap storage to exist")
}
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil)
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -605,12 +558,13 @@ func TestGenerateWithExtraAccounts(t *testing.T) {
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
stop := make(chan struct{})
snap.genAbort <- stop
<-stop
// If we now inspect the snap db, there should exist no extraneous storage items
if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
if data := rawdb.ReadStorageSnapshot(helper.triedb.DiskDB(), hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
t.Fatalf("expected slot to be removed, got %v", string(data))
}
}
@@ -624,37 +578,36 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) {
if false {
enableLogging()
}
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
stTrie = getStorageTrie(3, triedb)
)
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
{ // Account one in the trie
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
helper := newHelper()
{
// Account one in the trie
stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")),
[]string{"key-1", "key-2", "key-3"},
[]string{"val-1", "val-2", "val-3"},
true,
)
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
helper.accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
// Identical in the snap
key := hashData([]byte("acc-1"))
rawdb.WriteAccountSnapshot(diskdb, key, val)
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
}
{ // 100 accounts exist only in snapshot
{
// 100 accounts exist only in snapshot
for i := 0; i < 1000; i++ {
// acc := &Account{Balance: big.NewInt(int64(i)), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
acc := &Account{Balance: big.NewInt(int64(i)), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
key := hashData([]byte(fmt.Sprintf("acc-%d", i)))
rawdb.WriteAccountSnapshot(diskdb, key, val)
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
}
}
root, _, _ := accTrie.Commit(nil, false)
t.Logf("root: %x", root)
triedb.Commit(root, false, nil)
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
root, snap := helper.CommitAndGenerate()
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -682,31 +635,22 @@ func TestGenerateWithExtraBeforeAndAfter(t *testing.T) {
if false {
enableLogging()
}
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
accTrie, _ := trie.New(common.Hash{}, triedb)
helper := newHelper()
{
acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update(common.HexToHash("0x03").Bytes(), val)
accTrie.Update(common.HexToHash("0x07").Bytes(), val)
helper.accTrie.Update(common.HexToHash("0x03").Bytes(), val)
helper.accTrie.Update(common.HexToHash("0x07").Bytes(), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x01"), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x06"), val)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x07"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x01"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x02"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x03"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x04"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x05"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x06"), val)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x07"), val)
}
root, _, _ := accTrie.Commit(nil, false)
t.Logf("root: %x", root)
triedb.Commit(root, false, nil)
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
root, snap := helper.CommitAndGenerate()
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -727,29 +671,20 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) {
if false {
enableLogging()
}
var (
diskdb = memorydb.New()
triedb = trie.NewDatabase(diskdb)
)
accTrie, _ := trie.New(common.Hash{}, triedb)
helper := newHelper()
{
acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
accTrie.Update(common.HexToHash("0x03").Bytes(), val)
helper.accTrie.Update(common.HexToHash("0x03").Bytes(), val)
junk := make([]byte, 100)
copy(junk, []byte{0xde, 0xad})
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), junk)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), junk)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), junk)
rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), junk)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x02"), junk)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x03"), junk)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x04"), junk)
rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x05"), junk)
}
root, _, _ := accTrie.Commit(nil, false)
t.Logf("root: %x", root)
triedb.Commit(root, false, nil)
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil)
root, snap := helper.CommitAndGenerate()
select {
case <-snap.genPending:
// Snapshot generation succeeded
@@ -763,7 +698,7 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) {
snap.genAbort <- stop
<-stop
// If we now inspect the snap db, there should exist no extraneous storage items
if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
t.Fatalf("expected slot to be removed, got %v", string(data))
}
}
@@ -771,13 +706,13 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) {
func TestGenerateFromEmptySnap(t *testing.T) {
// enableLogging()
helper := newHelper()
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Add 1K accounts to the trie
for i := 0; i < 400; i++ {
stRoot := helper.makeStorageTrie(hashData([]byte(fmt.Sprintf("acc-%d", i))), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount(fmt.Sprintf("acc-%d", i),
&Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
}
root, snap := helper.Generate()
root, snap := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4
select {
@@ -805,12 +740,12 @@ func TestGenerateWithIncompleteStorage(t *testing.T) {
helper := newHelper()
stKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8"}
stVals := []string{"v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"}
stRoot := helper.makeStorageTrie(stKeys, stVals)
// We add 8 accounts, each one is missing exactly one of the storage slots. This means
// we don't have to order the keys and figure out exactly which hash-key winds up
// on the sensitive spots at the boundaries
for i := 0; i < 8; i++ {
accKey := fmt.Sprintf("acc-%d", i)
stRoot := helper.makeStorageTrie(hashData([]byte(accKey)), stKeys, stVals, true)
helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: emptyCode.Bytes()})
var moddedKeys []string
var moddedVals []string
@@ -822,8 +757,7 @@ func TestGenerateWithIncompleteStorage(t *testing.T) {
}
helper.addSnapStorage(accKey, moddedKeys, moddedVals)
}
root, snap := helper.Generate()
root, snap := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff
select {
@@ -839,3 +773,122 @@ func TestGenerateWithIncompleteStorage(t *testing.T) {
snap.genAbort <- stop
<-stop
}
func incKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]++
if key[i] != 0x0 {
break
}
}
return key
}
func decKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]--
if key[i] != 0xff {
break
}
}
return key
}
func populateDangling(disk ethdb.KeyValueStore) {
populate := func(accountHash common.Hash, keys []string, vals []string) {
for i, key := range keys {
rawdb.WriteStorageSnapshot(disk, accountHash, hashData([]byte(key)), []byte(vals[i]))
}
}
// Dangling storages of the "first" account
populate(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages of the "last" account
populate(common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages around the account 1
hash := decKey(hashData([]byte("acc-1")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-1")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages around the account 2
hash = decKey(hashData([]byte("acc-2")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-2")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages around the account 3
hash = decKey(hashData([]byte("acc-3")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-3")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages of the random account
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
}
// Tests that snapshot generation with dangling storages. Dangling storage means
// the storage data is existent while the corresponding account data is missing.
//
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
helper := newHelper()
stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populateDangling(helper.diskdb)
root, snap := helper.CommitAndGenerate()
select {
case <-snap.genPending:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
stop := make(chan struct{})
snap.genAbort <- stop
<-stop
}
// Tests that snapshot generation with dangling storages. Dangling storage means
// the storage data is existent while the corresponding account data is missing.
//
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
helper := newHelper()
stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
populateDangling(helper.diskdb)
root, snap := helper.CommitAndGenerate()
select {
case <-snap.genPending:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
stop := make(chan struct{})
snap.genAbort <- stop
<-stop
}
+7 -1
View File
@@ -351,10 +351,16 @@ type diskStorageIterator struct {
// is always false.
func (dl *diskLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) {
pos := common.TrimRightZeroes(seek[:])
// create prefix to be rawdb.SnapshotStoragePrefix + account[:]
prefix := make([]byte, len(rawdb.SnapshotStoragePrefix)+common.HashLength)
copy(prefix, rawdb.SnapshotStoragePrefix)
copy(prefix[len(rawdb.SnapshotStoragePrefix):], account[:])
return &diskStorageIterator{
layer: dl,
account: account,
it: dl.diskdb.NewIterator(append(rawdb.SnapshotStoragePrefix, account.Bytes()...), pos),
it: dl.diskdb.NewIterator(prefix, pos),
}, false
}
+11 -19
View File
@@ -165,12 +165,6 @@ type snapshot interface {
// Stale return whether this layer has become stale (was flattened across) or
// if it's still live.
Stale() bool
// AccountIterator creates an account iterator over an arbitrary layer.
AccountIterator(seek common.Hash) AccountIterator
// StorageIterator creates a storage iterator over an arbitrary layer.
StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool)
}
// Tree is an Ethereum state snapshot tree. It consists of one persistent base
@@ -622,20 +616,19 @@ func diffToDisk(bottom *diffLayer) (*diskLayer, bool, error) {
it := rawdb.IterateStorageSnapshots(base.diskdb, hash)
for it.Next() {
if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator
batch.Delete(key)
base.cache.Del(key[1:])
snapshotFlushStorageItemMeter.Mark(1)
key := it.Key()
batch.Delete(key)
base.cache.Del(key[1:])
snapshotFlushStorageItemMeter.Mark(1)
// Ensure we don't delete too much data blindly (contract can be
// huge). It's ok to flush, the root will go missing in case of a
// crash and we'll detect and regenerate the snapshot.
if batch.ValueSize() > ethdb.IdealBatchSize {
if err := batch.Write(); err != nil {
log.Crit("Failed to write storage deletions", "err", err)
}
batch.Reset()
// Ensure we don't delete too much data blindly (contract can be
// huge). It's ok to flush, the root will go missing in case of a
// crash and we'll detect and regenerate the snapshot.
if batch.ValueSize() > ethdb.IdealBatchSize {
if err := batch.Write(); err != nil {
log.Crit("Failed to write storage deletions", "err", err)
}
batch.Reset()
}
}
it.Release()
@@ -759,7 +752,6 @@ func (t *Tree) Rebuild(blockHash, root common.Hash) {
if stats := layer.genStats; stats != nil {
wiper = stats.wiping
}
}
// Layer should be inactive now, mark it as stale
layer.lock.Lock()
+81
View File
@@ -0,0 +1,81 @@
// (c) 2022, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2022 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 snapshot
import (
"bytes"
"fmt"
"time"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/ethdb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
// CheckDanglingStorage iterates the snap storage data, and verifies that all
// storage also has corresponding account data.
func CheckDanglingStorage(chaindb ethdb.KeyValueStore) error {
if err := checkDanglingDiskStorage(chaindb); err != nil {
log.Error("Database check error", "err", err)
return err
}
return nil
}
// checkDanglingDiskStorage checks if there is any 'dangling' storage data in the
// disk-backed snapshot layer.
func checkDanglingDiskStorage(chaindb ethdb.KeyValueStore) error {
var (
lastReport = time.Now()
start = time.Now()
lastKey []byte
it = rawdb.NewKeyLengthIterator(chaindb.NewIterator(rawdb.SnapshotStoragePrefix, nil), 1+2*common.HashLength)
)
log.Info("Checking dangling snapshot disk storage")
defer it.Release()
for it.Next() {
k := it.Key()
accKey := k[1:33]
if bytes.Equal(accKey, lastKey) {
// No need to look up for every slot
continue
}
lastKey = common.CopyBytes(accKey)
if time.Since(lastReport) > time.Second*8 {
log.Info("Iterating snap storage", "at", fmt.Sprintf("%#x", accKey), "elapsed", common.PrettyDuration(time.Since(start)))
lastReport = time.Now()
}
if data := rawdb.ReadAccountSnapshot(chaindb, common.BytesToHash(accKey)); len(data) == 0 {
log.Warn("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accKey), "storagekey", fmt.Sprintf("%#x", k))
return fmt.Errorf("dangling snapshot storage account %#x", accKey)
}
}
log.Info("Verified the snapshot disk storage", "time", common.PrettyDuration(time.Since(start)), "err", it.Error())
return nil
}
+4 -4
View File
@@ -60,7 +60,7 @@ func (s Storage) String() (str string) {
}
func (s Storage) Copy() Storage {
cpy := make(Storage)
cpy := make(Storage, len(s))
for key, value := range s {
cpy[key] = value
}
@@ -169,7 +169,7 @@ func (s *stateObject) getTrie(db Database) Trie {
if s.data.Root != emptyRoot && s.db.prefetcher != nil {
// When the miner is creating the pending state, there is no
// prefetcher
s.trie = s.db.prefetcher.trie(s.data.Root)
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
}
if s.trie == nil {
var err error
@@ -310,7 +310,7 @@ func (s *stateObject) finalise(prefetch bool) {
}
}
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != emptyRoot {
s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch)
s.db.prefetcher.prefetch(s.addrHash, s.data.Root, slotsToPrefetch)
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
@@ -367,7 +367,7 @@ func (s *stateObject) updateTrie(db Database) Trie {
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
}
if s.db.prefetcher != nil {
s.db.prefetcher.used(s.data.Root, usedStorage)
s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
}
if len(s.pendingStorage) > 0 {
s.pendingStorage = make(Storage)
+17 -10
View File
@@ -70,11 +70,14 @@ func (n *proofList) Delete(key []byte) error {
// * Contracts
// * Accounts
type StateDB struct {
db Database
prefetcher *triePrefetcher
originalRoot common.Hash // The pre-state root, before any changes were made
trie Trie
hasher crypto.KeccakState
db Database
prefetcher *triePrefetcher
trie Trie
hasher crypto.KeccakState
// originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called.
originalRoot common.Hash
snap snapshot.Snapshot
snapDestructs map[common.Hash]struct{}
@@ -670,6 +673,7 @@ func (s *StateDB) Copy() *StateDB {
state := &StateDB{
db: s.db,
trie: s.db.CopyTrie(s.trie),
originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
@@ -831,7 +835,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
}
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch)
s.prefetcher.prefetch(common.Hash{}, s.originalRoot, addressesToPrefetch)
}
// Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund()
@@ -872,7 +876,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it.
if prefetcher != nil {
if trie := prefetcher.trie(s.originalRoot); trie != nil {
if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil {
s.trie = trie
}
}
@@ -888,7 +892,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
}
if prefetcher != nil {
prefetcher.used(s.originalRoot, usedAddrs)
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
}
if len(s.stateObjectsPending) > 0 {
s.stateObjectsPending = make(map[common.Address]struct{})
@@ -905,7 +909,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
func (s *StateDB) Prepare(thash common.Hash, ti int) {
s.thash = thash
s.txIndex = ti
s.accessList = newAccessList()
}
func (s *StateDB) clearJournalAndRefund() {
@@ -969,7 +972,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
// The onleaf func is called _serially_, so we can reuse the same account
// for unmarshalling every time.
var account types.StateAccount
root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error {
root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash, _ []byte) error {
if err := rlp.DecodeBytes(leaf, &account); err != nil {
return nil
}
@@ -1007,6 +1010,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
}
s.originalRoot = root
return root, err
}
@@ -1020,6 +1024,9 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
//
// This method should only be called if Berlin/SubnetEVM/2929+2930 is applicable at the current number.
func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
// Clear out any leftover from previous executions
s.accessList = newAccessList()
s.AddAddressToAccessList(sender)
if dst != nil {
s.AddAddressToAccessList(*dst)
+52 -35
View File
@@ -34,7 +34,7 @@ import (
"github.com/ethereum/go-ethereum/log"
)
// triePrefetchMetricsPrefix is the prefix under which to publis the metrics.
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
var triePrefetchMetricsPrefix = "trie/prefetch/"
// triePrefetcher is an active prefetcher, which receives accounts or storage
@@ -43,10 +43,10 @@ var triePrefetchMetricsPrefix = "trie/prefetch/"
//
// Note, the prefetcher's API is not thread safe.
type triePrefetcher struct {
db Database // Database to fetch trie nodes through
root common.Hash // Root hash of theaccount trie for metrics
fetches map[common.Hash]Trie // Partially or fully fetcher tries
fetchers map[common.Hash]*subfetcher // Subfetchers for each trie
db Database // Database to fetch trie nodes through
root common.Hash // Root hash of the account trie for metrics
fetches map[string]Trie // Partially or fully fetcher tries
fetchers map[string]*subfetcher // Subfetchers for each trie
deliveryMissMeter metrics.Meter
accountLoadMeter metrics.Meter
@@ -59,13 +59,12 @@ type triePrefetcher struct {
storageWasteMeter metrics.Meter
}
// newTriePrefetcher
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace
p := &triePrefetcher{
db: db,
root: root,
fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
@@ -120,7 +119,7 @@ func (p *triePrefetcher) copy() *triePrefetcher {
copy := &triePrefetcher{
db: p.db,
root: p.root,
fetches: make(map[common.Hash]Trie), // Active prefetchers use the fetches map
fetches: make(map[string]Trie), // Active prefetchers use the fetches map
deliveryMissMeter: p.deliveryMissMeter,
accountLoadMeter: p.accountLoadMeter,
@@ -140,33 +139,35 @@ func (p *triePrefetcher) copy() *triePrefetcher {
return copy
}
// Otherwise we're copying an active fetcher, retrieve the current states
for root, fetcher := range p.fetchers {
copy.fetches[root] = fetcher.peek()
for id, fetcher := range p.fetchers {
copy.fetches[id] = fetcher.peek()
}
return copy
}
// prefetch schedules a batch of trie items to prefetch.
func (p *triePrefetcher) prefetch(root common.Hash, keys [][]byte) {
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, keys [][]byte) {
// If the prefetcher is an inactive one, bail out
if p.fetches != nil {
return
}
// Active fetcher, schedule the retrievals
fetcher := p.fetchers[root]
id := p.trieID(owner, root)
fetcher := p.fetchers[id]
if fetcher == nil {
fetcher = newSubfetcher(p.db, root)
p.fetchers[root] = fetcher
fetcher = newSubfetcher(p.db, owner, root)
p.fetchers[id] = fetcher
}
fetcher.schedule(keys)
}
// trie returns the trie matching the root hash, or nil if the prefetcher doesn't
// have it.
func (p *triePrefetcher) trie(root common.Hash) Trie {
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
// If the prefetcher is inactive, return from existing deep copies
id := p.trieID(owner, root)
if p.fetches != nil {
trie := p.fetches[root]
trie := p.fetches[id]
if trie == nil {
p.deliveryMissMeter.Mark(1)
return nil
@@ -174,7 +175,7 @@ func (p *triePrefetcher) trie(root common.Hash) Trie {
return p.db.CopyTrie(trie)
}
// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
fetcher := p.fetchers[root]
fetcher := p.fetchers[id]
if fetcher == nil {
p.deliveryMissMeter.Mark(1)
return nil
@@ -193,20 +194,26 @@ func (p *triePrefetcher) trie(root common.Hash) Trie {
// used marks a batch of state items used to allow creating statistics as to
// how useful or wasteful the prefetcher is.
func (p *triePrefetcher) used(root common.Hash, used [][]byte) {
if fetcher := p.fetchers[root]; fetcher != nil {
func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
fetcher.used = used
}
}
// trieID returns an unique trie identifier consists the trie owner and root hash.
func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
return string(append(owner.Bytes(), root.Bytes()...))
}
// subfetcher is a trie fetcher goroutine responsible for pulling entries for a
// single trie. It is spawned when a new root is encountered and lives until the
// main prefetcher is paused and either all requested items are processed or if
// the trie being worked on is retrieved from the prefetcher.
type subfetcher struct {
db Database // Database to load trie nodes through
root common.Hash // Root hash of the trie to prefetch
trie Trie // Trie being populated with nodes
db Database // Database to load trie nodes through
owner common.Hash // Owner of the trie, usually account hash
root common.Hash // Root hash of the trie to prefetch
trie Trie // Trie being populated with nodes
tasks [][]byte // Items queued up for retrieval
lock sync.Mutex // Lock protecting the task queue
@@ -223,15 +230,16 @@ type subfetcher struct {
// newSubfetcher creates a goroutine to prefetch state items belonging to a
// particular root hash.
func newSubfetcher(db Database, root common.Hash) *subfetcher {
func newSubfetcher(db Database, owner common.Hash, root common.Hash) *subfetcher {
sf := &subfetcher{
db: db,
root: root,
wake: make(chan struct{}, 1),
stop: make(chan struct{}),
term: make(chan struct{}),
copy: make(chan chan Trie),
seen: make(map[string]struct{}),
db: db,
owner: owner,
root: root,
wake: make(chan struct{}, 1),
stop: make(chan struct{}),
term: make(chan struct{}),
copy: make(chan chan Trie),
seen: make(map[string]struct{}),
}
go sf.loop()
return sf
@@ -287,12 +295,21 @@ func (sf *subfetcher) loop() {
defer close(sf.term)
// Start by opening the trie and stop processing if it fails
trie, err := sf.db.OpenTrie(sf.root)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
return
if sf.owner == (common.Hash{}) {
trie, err := sf.db.OpenTrie(sf.root)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
return
}
sf.trie = trie
} else {
trie, err := sf.db.OpenStorageTrie(sf.owner, sf.root)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
return
}
sf.trie = trie
}
sf.trie = trie
// Trie opened successfully, keep prefetching items
for {
+18 -18
View File
@@ -57,20 +57,20 @@ func TestCopyAndClose(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
time.Sleep(1 * time.Second)
a := prefetcher.trie(db.originalRoot)
prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
b := prefetcher.trie(db.originalRoot)
a := prefetcher.trie(common.Hash{}, db.originalRoot)
prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
b := prefetcher.trie(common.Hash{}, db.originalRoot)
cpy := prefetcher.copy()
cpy.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
cpy.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
c := cpy.trie(db.originalRoot)
cpy.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
cpy.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
c := cpy.trie(common.Hash{}, db.originalRoot)
prefetcher.close()
cpy2 := cpy.copy()
cpy2.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
d := cpy2.trie(db.originalRoot)
cpy2.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
d := cpy2.trie(common.Hash{}, db.originalRoot)
cpy.close()
cpy2.close()
if a.Hash() != b.Hash() || a.Hash() != c.Hash() || a.Hash() != d.Hash() {
@@ -82,10 +82,10 @@ func TestUseAfterClose(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
a := prefetcher.trie(db.originalRoot)
prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
a := prefetcher.trie(common.Hash{}, db.originalRoot)
prefetcher.close()
b := prefetcher.trie(db.originalRoot)
b := prefetcher.trie(common.Hash{}, db.originalRoot)
if a == nil {
t.Fatal("Prefetching before close should not return nil")
}
@@ -98,13 +98,13 @@ func TestCopyClose(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()})
prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()})
cpy := prefetcher.copy()
a := prefetcher.trie(db.originalRoot)
b := cpy.trie(db.originalRoot)
a := prefetcher.trie(common.Hash{}, db.originalRoot)
b := cpy.trie(common.Hash{}, db.originalRoot)
prefetcher.close()
c := prefetcher.trie(db.originalRoot)
d := cpy.trie(db.originalRoot)
c := prefetcher.trie(common.Hash{}, db.originalRoot)
d := cpy.trie(common.Hash{}, db.originalRoot)
if a == nil {
t.Fatal("Prefetching before close should not return nil")
}
+1 -1
View File
@@ -64,7 +64,7 @@ func TestCappedMemoryTrieWriter(t *testing.T) {
if i < int(cacheConfig.CommitInterval) {
assert.Equal(common.Hash{}, m.LastCommit, "should not have committed block on accept")
} else {
assert.Equal(block.Root(), m.LastCommit, "should have committed block after commitInterval")
assert.Equal(block.Root(), m.LastCommit, "should have committed block after CommitInterval")
m.LastCommit = common.Hash{}
}
+1 -17
View File
@@ -49,23 +49,7 @@ import (
// contain invalid transactions
func TestStateProcessorErrors(t *testing.T) {
var (
config = &params.ChainConfig{
ChainID: big.NewInt(1),
FeeConfig: params.DefaultFeeConfig,
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP150Hash: common.Hash{},
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
NetworkUpgrades: params.NetworkUpgrades{
SubnetEVMTimestamp: big.NewInt(0),
},
}
config = params.TestChainConfig
signer = types.LatestSigner(config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
)
+15 -9
View File
@@ -312,16 +312,22 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if err := st.preCheck(); err != nil {
return nil, err
}
msg := st.msg
sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber)
subnetEVM := st.evm.ChainConfig().IsSubnetEVM(st.evm.Context.Time)
if st.evm.Config.Debug {
st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
defer func() {
st.evm.Config.Tracer.CaptureTxEnd(st.gas)
}()
}
contractCreation := msg.To() == nil
var (
msg = st.msg
sender = vm.AccountRef(msg.From())
rules = st.evm.ChainConfig().AvalancheRules(st.evm.Context.BlockNumber, st.evm.Context.Time)
contractCreation = msg.To() == nil
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul)
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul)
if err != nil {
return nil, err
}
@@ -336,7 +342,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
// Set up the initial access list.
if rules := st.evm.ChainConfig().AvalancheRules(st.evm.Context.BlockNumber, st.evm.Context.Time); rules.IsSubnetEVM {
if rules.IsSubnetEVM {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}
var (
@@ -350,7 +356,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
st.refundGas(subnetEVM)
st.refundGas(rules.IsSubnetEVM)
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return &ExecutionResult{
+3 -3
View File
@@ -68,7 +68,7 @@ func newTxJournal(path string) *txJournal {
// the specified pool.
func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
// Skip the parsing if the journal file doesn't exist at all
if _, err := os.Stat(journal.path); os.IsNotExist(err) {
if !common.FileExist(journal.path) {
return nil
}
// Open the journal for loading any past transactions
@@ -148,7 +148,7 @@ func (journal *txJournal) rotate(all map[common.Address]types.Transactions) erro
journal.writer = nil
}
// Generate a new journal with the contents of the current pool
replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
@@ -168,7 +168,7 @@ func (journal *txJournal) rotate(all map[common.Address]types.Transactions) erro
if err = os.Rename(journal.path+".new", journal.path); err != nil {
return err
}
sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0644)
sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
+1 -6
View File
@@ -619,7 +619,6 @@ func (pool *TxPool) PendingSize() int {
count += len(txs)
}
return count
}
// PendingFrom returns the same set of transactions that would be returned from Pending restricted to only
@@ -1631,7 +1630,7 @@ func (pool *TxPool) truncateQueue() {
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
}
}
sort.Sort(addresses)
sort.Sort(sort.Reverse(addresses))
// Drop transactions until the total is below the limit or only locals remain
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
@@ -1821,10 +1820,6 @@ func (as *accountSet) contains(addr common.Address) bool {
return exist
}
func (as *accountSet) empty() bool {
return len(as.accounts) == 0
}
// containsTx checks if the sender of a given tx is within the set. If the sender
// cannot be derived, this method returns false.
func (as *accountSet) containsTx(tx *types.Transaction) bool {
+1 -2
View File
@@ -30,7 +30,6 @@ import (
"crypto/ecdsa"
"errors"
"fmt"
"io/ioutil"
"math/big"
"math/rand"
"os"
@@ -2293,7 +2292,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
t.Parallel()
// Create a temporary file for the journal
file, err := ioutil.TempFile("", "")
file, err := os.CreateTemp("", "")
if err != nil {
t.Fatalf("failed to create temporary journal: %v", err)
}
+2 -2
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2020 The go-ethereum Authors
// Copyright 2021 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
@@ -32,7 +32,7 @@ import (
"github.com/ethereum/go-ethereum/common"
)
//go:generate gencodec -type AccessTuple -out gen_access_tuple.go
//go:generate go run github.com/fjl/gencodec -type AccessTuple -out gen_access_tuple.go
// AccessList is an EIP-2930 access list.
type AccessList []AccessTuple
+2 -1
View File
@@ -73,7 +73,8 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
}
//go:generate gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -type Header -out gen_header_rlp.go
// Header represents a block header in the Ethereum blockchain.
type Header struct {
+7 -8
View File
@@ -66,13 +66,13 @@ func TestBloom(t *testing.T) {
// TestBloomExtensively does some more thorough tests
func TestBloomExtensively(t *testing.T) {
var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263")
exp := common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263")
var b Bloom
// Add 100 "random" things
for i := 0; i < 100; i++ {
data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i)
b.Add([]byte(data))
//b.Add(new(big.Int).SetBytes([]byte(data)))
// b.Add(new(big.Int).SetBytes([]byte(data)))
}
got := crypto.Keccak256Hash(b.Bytes())
if got != exp {
@@ -102,12 +102,11 @@ func BenchmarkBloom9Lookup(b *testing.B) {
}
func BenchmarkCreateBloom(b *testing.B) {
var txs = Transactions{
txs := Transactions{
NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil),
NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil),
}
var rSmall = Receipts{
rSmall := Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
@@ -132,7 +131,7 @@ func BenchmarkCreateBloom(b *testing.B) {
},
}
var rLarge = make(Receipts, 200)
rLarge := make(Receipts, 200)
// Fill it with 200 receipts x 2 logs
for i := 0; i < 200; i += 2 {
copy(rLarge[i:], rSmall)
@@ -144,7 +143,7 @@ func BenchmarkCreateBloom(b *testing.B) {
bl = CreateBloom(rSmall)
}
b.StopTimer()
var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
exp := common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
got := crypto.Keccak256Hash(bl.Bytes())
if got != exp {
b.Errorf("Got %x, exp %x", got, exp)
@@ -157,7 +156,7 @@ func BenchmarkCreateBloom(b *testing.B) {
bl = CreateBloom(rLarge)
}
b.StopTimer()
var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
exp := common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
got := crypto.Keccak256Hash(bl.Bytes())
if got != exp {
b.Errorf("Got %x, exp %x", got, exp)
+27
View File
@@ -0,0 +1,27 @@
// Code generated by rlpgen. DO NOT EDIT.
//go:build !norlpgen
// +build !norlpgen
package types
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *StateAccount) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteUint64(obj.Nonce)
if obj.Balance == nil {
w.Write(rlp.EmptyString)
} else {
if obj.Balance.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(obj.Balance)
}
w.WriteBytes(obj.Root[:])
w.WriteBytes(obj.CodeHash)
w.ListEnd(_tmp0)
return w.Flush()
}
+67
View File
@@ -0,0 +1,67 @@
// Code generated by rlpgen. DO NOT EDIT.
//go:build !norlpgen
// +build !norlpgen
package types
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Header) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteBytes(obj.ParentHash[:])
w.WriteBytes(obj.UncleHash[:])
w.WriteBytes(obj.Coinbase[:])
w.WriteBytes(obj.Root[:])
w.WriteBytes(obj.TxHash[:])
w.WriteBytes(obj.ReceiptHash[:])
w.WriteBytes(obj.Bloom[:])
if obj.Difficulty == nil {
w.Write(rlp.EmptyString)
} else {
if obj.Difficulty.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(obj.Difficulty)
}
if obj.Number == nil {
w.Write(rlp.EmptyString)
} else {
if obj.Number.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(obj.Number)
}
w.WriteUint64(obj.GasLimit)
w.WriteUint64(obj.GasUsed)
w.WriteUint64(obj.Time)
w.WriteBytes(obj.Extra)
w.WriteBytes(obj.MixDigest[:])
w.WriteBytes(obj.Nonce[:])
_tmp1 := obj.BaseFee != nil
_tmp2 := obj.BlockGasCost != nil
if _tmp1 || _tmp2 {
if obj.BaseFee == nil {
w.Write(rlp.EmptyString)
} else {
if obj.BaseFee.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(obj.BaseFee)
}
}
if _tmp2 {
if obj.BlockGasCost == nil {
w.Write(rlp.EmptyString)
} else {
if obj.BlockGasCost.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(obj.BlockGasCost)
}
}
w.ListEnd(_tmp0)
return w.Flush()
}
+23
View File
@@ -0,0 +1,23 @@
// Code generated by rlpgen. DO NOT EDIT.
//go:build !norlpgen
// +build !norlpgen
package types
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *rlpLog) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteBytes(obj.Address[:])
_tmp1 := w.List()
for _, _tmp2 := range obj.Topics {
w.WriteBytes(_tmp2[:])
}
w.ListEnd(_tmp1)
w.WriteBytes(obj.Data)
w.ListEnd(_tmp0)
return w.Flush()
}
+1 -1
View File
@@ -41,7 +41,7 @@ var hasherPool = sync.Pool{
New: func() interface{} { return sha3.NewLegacyKeccak256() },
}
// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.
// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.
var encodeBufferPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
+6 -5
View File
@@ -34,6 +34,7 @@ import (
mrand "math/rand"
"testing"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ethereum/go-ethereum/common"
@@ -48,7 +49,7 @@ func TestDeriveSha(t *testing.T) {
t.Fatal(err)
}
for len(txs) < 1000 {
exp := types.DeriveSha(txs, new(trie.Trie))
exp := types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())))
got := types.DeriveSha(txs, trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) {
t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp)
@@ -95,7 +96,7 @@ func BenchmarkDeriveSha200(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
exp = types.DeriveSha(txs, new(trie.Trie))
exp = types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())))
}
})
@@ -116,7 +117,7 @@ func TestFuzzDeriveSha(t *testing.T) {
rndSeed := mrand.Int()
for i := 0; i < 10; i++ {
seed := rndSeed + i
exp := types.DeriveSha(newDummy(i), new(trie.Trie))
exp := types.DeriveSha(newDummy(i), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())))
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) {
printList(newDummy(seed))
@@ -144,7 +145,7 @@ func TestDerivableList(t *testing.T) {
},
}
for i, tc := range tcs[1:] {
exp := types.DeriveSha(flatList(tc), new(trie.Trie))
exp := types.DeriveSha(flatList(tc), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())))
got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) {
t.Fatalf("case %d: got %x exp %x", i, got, exp)
@@ -206,7 +207,7 @@ func printList(l types.DerivableList) {
for i := 0; i < l.Len(); i++ {
var buf bytes.Buffer
l.EncodeIndex(i, &buf)
fmt.Printf("\"0x%x\",\n", buf.Bytes())
fmt.Printf("\"%#x\",\n", buf.Bytes())
}
fmt.Printf("},\n")
}
+1 -1
View File
@@ -8,7 +8,7 @@
//
// Much love to the original authors for their work.
// **********
// Copyright 2020 The go-ethereum Authors
// Copyright 2021 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
+10 -13
View File
@@ -34,7 +34,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
//go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go
//go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go
// Log represents a contract log event. These events are generated by the LOG opcode and
// stored/indexed by the node.
@@ -72,15 +72,14 @@ type logMarshaling struct {
Index hexutil.Uint
}
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -type rlpLog -out gen_log_rlp.go
type rlpLog struct {
Address common.Address
Topics []common.Hash
Data []byte
}
// rlpStorageLog is the storage encoding of a log.
type rlpStorageLog rlpLog
// legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields.
type legacyRlpStorageLog struct {
Address common.Address
@@ -95,7 +94,8 @@ type legacyRlpStorageLog struct {
// EncodeRLP implements rlp.Encoder.
func (l *Log) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data})
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
return rlp.Encode(w, &rl)
}
// DecodeRLP implements rlp.Decoder.
@@ -108,17 +108,14 @@ func (l *Log) DecodeRLP(s *rlp.Stream) error {
return err
}
// LogForStorage is a wrapper around a Log that flattens and parses the entire content of
// a log including non-consensus fields.
// LogForStorage is a wrapper around a Log that handles
// backward compatibility with prior storage formats.
type LogForStorage Log
// EncodeRLP implements rlp.Encoder.
func (l *LogForStorage) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, rlpStorageLog{
Address: l.Address,
Topics: l.Topics,
Data: l.Data,
})
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
return rlp.Encode(w, &rl)
}
// DecodeRLP implements rlp.Decoder.
@@ -129,7 +126,7 @@ func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error {
if err != nil {
return err
}
var dec rlpStorageLog
var dec rlpLog
err = rlp.DecodeBytes(blob, &dec)
if err == nil {
*l = LogForStorage{
+21 -31
View File
@@ -41,15 +41,14 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go
//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go
var (
receiptStatusFailedRLP = []byte{}
receiptStatusSuccessfulRLP = []byte{0x01}
)
// This error is returned when a typed receipt is decoded, but the string is empty.
var errEmptyTypedReceipt = errors.New("empty typed receipt bytes")
var errShortTypedReceipt = errors.New("typed receipt too short")
const (
// ReceiptStatusFailed is the status code of a transaction if execution failed.
@@ -192,26 +191,13 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
}
r.Type = LegacyTxType
return r.setFromRLP(dec)
case kind == rlp.String:
default:
// It's an EIP-2718 typed tx receipt.
b, err := s.Bytes()
if err != nil {
return err
}
if len(b) == 0 {
return errEmptyTypedReceipt
}
r.Type = b[0]
if r.Type == AccessListTxType || r.Type == DynamicFeeTxType {
var dec receiptRLP
if err := rlp.DecodeBytes(b[1:], &dec); err != nil {
return err
}
return r.setFromRLP(dec)
}
return ErrTxTypeNotSupported
default:
return rlp.ErrExpectedList
return r.decodeTyped(b)
}
}
@@ -234,8 +220,8 @@ func (r *Receipt) UnmarshalBinary(b []byte) error {
// decodeTyped decodes a typed receipt from the canonical format.
func (r *Receipt) decodeTyped(b []byte) error {
if len(b) == 0 {
return errEmptyTypedReceipt
if len(b) <= 1 {
return errShortTypedReceipt
}
switch b[0] {
case DynamicFeeTxType, AccessListTxType:
@@ -291,22 +277,26 @@ func (r *Receipt) Size() common.StorageSize {
return size
}
// ReceiptForStorage is a wrapper around a Receipt that flattens and parses the
// entire content of a receipt, as opposed to only the consensus fields originally.
// ReceiptForStorage is a wrapper around a Receipt with RLP serialization
// that omits the Bloom field and deserialization that re-computes it.
type ReceiptForStorage Receipt
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
// into an RLP stream.
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
enc := &storedReceiptRLP{
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
CumulativeGasUsed: r.CumulativeGasUsed,
Logs: make([]*LogForStorage, len(r.Logs)),
func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
outerList := w.List()
w.WriteBytes((*Receipt)(r).statusEncoding())
w.WriteUint64(r.CumulativeGasUsed)
logList := w.List()
for _, log := range r.Logs {
if err := rlp.Encode(w, log); err != nil {
return err
}
}
for i, log := range r.Logs {
enc.Logs[i] = (*LogForStorage)(log)
}
return rlp.Encode(w, enc)
w.ListEnd(logList)
w.ListEnd(outerList)
return w.Flush()
}
// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation
+1 -1
View File
@@ -96,7 +96,7 @@ func TestDecodeEmptyTypedReceipt(t *testing.T) {
input := []byte{0x80}
var r Receipt
err := rlp.DecodeBytes(input, &r)
if err != errEmptyTypedReceipt {
if err != errShortTypedReceipt {
t.Fatal("wrong error:", err)
}
}
+2
View File
@@ -32,6 +32,8 @@ import (
"github.com/ethereum/go-ethereum/common"
)
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -type StateAccount -out gen_account_rlp.go
// StateAccount is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type StateAccount struct {
+22 -6
View File
@@ -47,7 +47,7 @@ var (
ErrInvalidTxType = errors.New("transaction type not valid in this context")
ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
errEmptyTypedTx = errors.New("empty typed transaction bytes")
errShortTypedTx = errors.New("typed transaction too short")
)
// Transaction types.
@@ -144,7 +144,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, int(rlp.ListSize(size)))
}
return err
case kind == rlp.String:
default:
// It's an EIP-2718 typed TX envelope.
var b []byte
if b, err = s.Bytes(); err != nil {
@@ -155,8 +155,6 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(inner, len(b))
}
return err
default:
return rlp.ErrExpectedList
}
}
@@ -184,8 +182,8 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
// decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) == 0 {
return nil, errEmptyTypedTx
if len(b) <= 1 {
return nil, errShortTypedTx
}
switch b[0] {
case AccessListTxType:
@@ -454,6 +452,24 @@ func TxDifference(a, b Transactions) Transactions {
return keep
}
// HashDifference returns a new set which is the difference between a and b.
func HashDifference(a, b []common.Hash) []common.Hash {
keep := make([]common.Hash, 0, len(a))
remove := make(map[common.Hash]struct{})
for _, hash := range b {
remove[hash] = struct{}{}
}
for _, hash := range a {
if _, ok := remove[hash]; !ok {
keep = append(keep, hash)
}
}
return keep
}
// TxByNonce implements the sort interface to allow sorting a list of transactions
// by their nonces. This is usually only useful for sorting transactions from a
// single account, otherwise a nonce comparison doesn't make much sense.
-1
View File
@@ -121,7 +121,6 @@ func TestEIP155SigningVitalik(t *testing.T) {
if from != addr {
t.Errorf("%d: expected %x got %x", i, addr, from)
}
}
}
+9 -7
View File
@@ -86,7 +86,7 @@ func TestDecodeEmptyTypedTx(t *testing.T) {
input := []byte{0x80}
var tx Transaction
err := rlp.DecodeBytes(input, &tx)
if err != errEmptyTypedTx {
if err != errShortTypedTx {
t.Fatal("wrong error:", err)
}
}
@@ -124,7 +124,6 @@ func TestEIP2718TransactionSigHash(t *testing.T) {
// This test checks signature operations on access list transactions.
func TestEIP2930Signer(t *testing.T) {
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
keyAddr = crypto.PubkeyToAddress(key.PublicKey)
@@ -487,14 +486,17 @@ func TestTransactionCoding(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assertEqual(parsedTx, tx)
if err := assertEqual(parsedTx, tx); err != nil {
t.Fatal(err)
}
// JSON
parsedTx, err = encodeDecodeJSON(tx)
if err != nil {
t.Fatal(err)
}
assertEqual(parsedTx, tx)
if err := assertEqual(parsedTx, tx); err != nil {
t.Fatal(err)
}
}
}
@@ -503,7 +505,7 @@ func encodeDecodeJSON(tx *Transaction) (*Transaction, error) {
if err != nil {
return nil, fmt.Errorf("json encoding failed: %v", err)
}
var parsedTx = &Transaction{}
parsedTx := &Transaction{}
if err := json.Unmarshal(data, &parsedTx); err != nil {
return nil, fmt.Errorf("json decoding failed: %v", err)
}
@@ -515,7 +517,7 @@ func encodeDecodeBinary(tx *Transaction) (*Transaction, error) {
if err != nil {
return nil, fmt.Errorf("rlp encoding failed: %v", err)
}
var parsedTx = &Transaction{}
parsedTx := &Transaction{}
if err := parsedTx.UnmarshalBinary(data); err != nil {
return nil, fmt.Errorf("rlp decoding failed: %v", err)
}
+1 -1
View File
@@ -86,7 +86,7 @@ func codeBitmapInternal(code, bits bitvec) bitvec {
for pc := uint64(0); pc < uint64(len(code)); {
op := OpCode(code[pc])
pc++
if op < PUSH1 || op > PUSH32 {
if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false).
continue
}
numbits := op - PUSH1 + 1
+1 -2
View File
@@ -44,7 +44,6 @@ import (
"github.com/ethereum/go-ethereum/crypto/bls12381"
"github.com/ethereum/go-ethereum/crypto/bn256"
//lint:ignore SA1019 Needed for precompile
"golang.org/x/crypto/ripemd160"
)
@@ -635,7 +634,7 @@ func (c *blake2F) Run(input []byte) ([]byte, error) {
// Parse the input into the Blake2b call parameters
var (
rounds = binary.BigEndian.Uint32(input[0:4])
final = (input[212] == blake2FFinalBlockBytes)
final = input[212] == blake2FFinalBlockBytes
h [8]uint64
m [16]uint64
+4 -4
View File
@@ -30,7 +30,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
"time"
@@ -189,7 +189,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
// Keep it as uint64, multiply 100 to get two digit float later
mgasps := (100 * 1000 * gasUsed) / elapsed
bench.ReportMetric(float64(mgasps)/100, "mgas/s")
//Check if it is correct
// Check if it is correct
if err != nil {
bench.Error(err)
return
@@ -344,7 +344,7 @@ func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJsonFail("blsMapG
func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJsonFail("blsMapG2", "12", t) }
func loadJson(name string) ([]precompiledTest, error) {
data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name))
data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name))
if err != nil {
return nil, err
}
@@ -354,7 +354,7 @@ func loadJson(name string) ([]precompiledTest, error) {
}
func loadJsonFail(name string) ([]precompiledFailureTest, error) {
data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name))
data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name))
if err != nil {
return nil, err
}
+18
View File
@@ -35,6 +35,7 @@ import (
)
var activators = map[int]func(*JumpTable){
3855: enable3855,
3198: enable3198,
2929: enable2929,
2200: enable2200,
@@ -175,3 +176,20 @@ func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
scope.Stack.push(baseFee)
return nil, nil
}
// enable3855 applies EIP-3855 (PUSH0 opcode)
func enable3855(jt *JumpTable) {
// New opcode
jt[PUSH0] = &operation{
execute: opPush0,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
}
// opPush0 implements the PUSH0 opcode
func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int))
return nil, nil
}
+35
View File
@@ -0,0 +1,35 @@
// (c) 2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package vm
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
)
func TestIsProhibited(t *testing.T) {
// reserved addresses (coreth)
assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000000")))
assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000010")))
assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000f0")))
assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000ff")))
// reserved addresses (subnet-evm)
assert.True(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000000")))
assert.True(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000010")))
assert.True(t, IsProhibited(common.HexToAddress("0x02000000000000000000000000000000000000f0")))
assert.True(t, IsProhibited(common.HexToAddress("0x02000000000000000000000000000000000000ff")))
// reserved addresses (custom precompiles)
assert.True(t, IsProhibited(common.HexToAddress("0x0300000000000000000000000000000000000000")))
assert.True(t, IsProhibited(common.HexToAddress("0x0300000000000000000000000000000000000010")))
assert.True(t, IsProhibited(common.HexToAddress("0x03000000000000000000000000000000000000f0")))
assert.True(t, IsProhibited(common.HexToAddress("0x03000000000000000000000000000000000000ff")))
// allowed for use
assert.False(t, IsProhibited(common.HexToAddress("0x00000000000000000000000000000000000000ff")))
assert.False(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000100")))
assert.False(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000100")))
assert.False(t, IsProhibited(common.HexToAddress("0x0300000000000000000000000000000000000100")))
}
+38 -32
View File
@@ -30,8 +30,8 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"os"
"testing"
"github.com/ava-labs/subnet-evm/params"
@@ -240,38 +240,38 @@ func TestAddMod(t *testing.T) {
}
}
// getResult is a convenience function to generate the expected values
func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
interpreter = env.interpreter
)
result := make([]TwoOperandTestcase, len(args))
for i, param := range args {
x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x))
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x)
stack.push(y)
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
}
return result
}
// utility function to fill the json-file with testcases
// Enable this test to generate the 'testcases_xx.json' files
func TestWriteExpectedValues(t *testing.T) {
t.Skip("Enable this test to create json test cases.")
// getResult is a convenience function to generate the expected values
getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
interpreter = env.interpreter
)
result := make([]TwoOperandTestcase, len(args))
for i, param := range args {
x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x))
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x)
stack.push(y)
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
}
return result
}
for name, method := range twoOpMethods {
data, err := json.Marshal(getResult(commonParams, method))
if err != nil {
t.Fatal(err)
}
_ = ioutil.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644)
_ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0o644)
if err != nil {
t.Fatal(err)
}
@@ -281,7 +281,7 @@ func TestWriteExpectedValues(t *testing.T) {
// TestJsonTestcases runs through all the testcases defined as json-files
func TestJsonTestcases(t *testing.T) {
for name := range twoOpMethods {
data, err := ioutil.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name))
data, err := os.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name))
if err != nil {
t.Fatal("Failed to read file", err)
}
@@ -295,26 +295,33 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
scope = &ScopeContext{nil, stack, nil}
evmInterpreter = NewEVMInterpreter(env, env.Config)
)
env.interpreter = evmInterpreter
// convert args
byteArgs := make([][]byte, len(args))
intArgs := make([]*uint256.Int, len(args))
for i, arg := range args {
byteArgs[i] = common.Hex2Bytes(arg)
intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
}
pc := uint64(0)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
for _, arg := range byteArgs {
a := new(uint256.Int)
a.SetBytes(arg)
stack.push(a)
for _, arg := range intArgs {
stack.push(arg)
}
op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
op(&pc, evmInterpreter, scope)
stack.pop()
}
bench.StopTimer()
for i, arg := range args {
want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
if have := intArgs[i]; !want.Eq(have) {
bench.Fatalf("input #%d mutated, have %x want %x", i, have, want)
}
}
}
func BenchmarkOpAdd64(b *testing.B) {
@@ -650,7 +657,6 @@ func TestCreate2Addreses(t *testing.T) {
expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0",
},
} {
origin := common.BytesToAddress(common.FromHex(tt.origin))
salt := common.BytesToHash(common.FromHex(tt.salt))
code := common.FromHex(tt.code)

Some files were not shown because too many files have changed in this diff Show More