mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
coreth 0.8.4
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '44 11 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
@@ -27,7 +27,5 @@ jobs:
|
||||
go-version: ${{ matrix.go }}
|
||||
- run: go mod download
|
||||
shell: bash
|
||||
- run: ./scripts/build.sh evm
|
||||
shell: bash
|
||||
- run: ./scripts/build_test.sh
|
||||
shell: bash
|
||||
|
||||
+7
-4
@@ -5,13 +5,16 @@
|
||||
ARG AVALANCHE_VERSION
|
||||
|
||||
# ============= Compilation Stage ================
|
||||
FROM golang:1.17.1-buster AS builder
|
||||
FROM golang:1.17.4-buster AS builder
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends bash=5.0-4 git=1:2.20.1-2+deb10u3 make=4.2.1-1.2 gcc=4:8.3.0-1 musl-dev=1.1.21-2 ca-certificates=20200601~deb10u2 linux-headers-amd64
|
||||
|
||||
WORKDIR /build
|
||||
# Copy and download avalanche dependencies using go mod
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
|
||||
# Copy avalanche dependencies first (intermediate docker image caching)
|
||||
# Copy avalanchego directory if present (for manual CI case, which uses local dependency)
|
||||
COPY go.mod go.sum avalanchego* ./
|
||||
|
||||
# Download avalanche dependencies using go mod
|
||||
RUN go mod download
|
||||
|
||||
# Copy the code into the container
|
||||
|
||||
@@ -110,7 +110,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis
|
||||
genesis := core.Genesis{Config: cpcfg, GasLimit: gasLimit, Alloc: alloc}
|
||||
genesis.MustCommit(database)
|
||||
cacheConfig := &core.CacheConfig{}
|
||||
blockchain, _ := core.NewBlockChain(database, cacheConfig, genesis.Config, dummy.NewEngine(), vm.Config{}, common.Hash{})
|
||||
blockchain, _ := core.NewBlockChain(database, cacheConfig, genesis.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
|
||||
backend := &SimulatedBackend{
|
||||
database: database,
|
||||
@@ -163,7 +163,7 @@ func (b *SimulatedBackend) Rollback() {
|
||||
}
|
||||
|
||||
func (b *SimulatedBackend) rollback(parent *types.Block) {
|
||||
blocks, _, _ := core.GenerateChain(b.config, parent, dummy.NewEngine(), b.database, 1, 10, func(int, *core.BlockGen) {})
|
||||
blocks, _, _ := core.GenerateChain(b.config, parent, dummy.NewFaker(), b.database, 1, 10, func(int, *core.BlockGen) {})
|
||||
|
||||
b.acceptedBlock = blocks[0]
|
||||
b.acceptedState, _ = state.New(b.acceptedBlock.Root(), b.blockchain.StateCache(), nil)
|
||||
@@ -499,6 +499,9 @@ func (b *SimulatedBackend) AcceptedNonceAt(ctx context.Context, account common.A
|
||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if b.acceptedBlock.Header().BaseFee != nil {
|
||||
return b.acceptedBlock.Header().BaseFee, nil
|
||||
}
|
||||
@@ -814,7 +817,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||
return errors.New("Could not adjust time on non-empty block")
|
||||
}
|
||||
|
||||
blocks, _, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), dummy.NewEngine(), b.database, 1, 10, func(number int, block *core.BlockGen) {
|
||||
blocks, _, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), dummy.NewFaker(), b.database, 1, 10, func(number int, block *core.BlockGen) {
|
||||
block.OffsetTime(int64(adjustment.Seconds()))
|
||||
})
|
||||
stateDB, _ := b.blockchain.State()
|
||||
|
||||
@@ -514,7 +514,7 @@ func TestEstimateGas(t *testing.T) {
|
||||
GasPrice: big.NewInt(0),
|
||||
Value: nil,
|
||||
Data: common.Hex2Bytes("b9b046f9"),
|
||||
}, 0, errors.New("invalid opcode: opcode 0xfe not defined"), nil},
|
||||
}, 0, errors.New("invalid opcode: INVALID"), nil},
|
||||
|
||||
{"Valid", interfaces.CallMsg{
|
||||
From: addr,
|
||||
|
||||
@@ -98,6 +98,13 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||
transactIdentifiers = make(map[string]bool)
|
||||
eventIdentifiers = make(map[string]bool)
|
||||
)
|
||||
|
||||
for _, input := range evmABI.Constructor.Inputs {
|
||||
if hasStruct(input.Type) {
|
||||
bindStructType[lang](input.Type, structs)
|
||||
}
|
||||
}
|
||||
|
||||
for _, original := range evmABI.Methods {
|
||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||
normalized := original
|
||||
|
||||
@@ -1935,6 +1935,49 @@ var bindTests = []struct {
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
name: `ConstructorWithStructParam`,
|
||||
contract: `
|
||||
pragma solidity >=0.8.0 <0.9.0;
|
||||
|
||||
contract ConstructorWithStructParam {
|
||||
struct StructType {
|
||||
uint256 field;
|
||||
}
|
||||
|
||||
constructor(StructType memory st) {}
|
||||
}
|
||||
`,
|
||||
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
|
||||
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
|
||||
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 (
|
||||
key, _ = crypto.GenerateKey()
|
||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, 10000000)
|
||||
)
|
||||
defer sim.Close()
|
||||
|
||||
_, tx, _, err := DeployConstructorWithStructParam(user, sim, ConstructorWithStructParamStructType{Field: big.NewInt(42)})
|
||||
if err != nil {
|
||||
t.Fatalf("DeployConstructorWithStructParam() 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)
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
// The binding tests have been modified to run in two separate test
|
||||
@@ -1968,30 +2011,31 @@ func golangBindings(t *testing.T, overload bool) {
|
||||
}
|
||||
// Generate the test suite for all the contracts
|
||||
for i, tt := range bindTests {
|
||||
// Skip the "Overload" test if [!overload]
|
||||
if !overload && tt.name == "Overload" {
|
||||
continue
|
||||
}
|
||||
// Skip all tests except for "Overload" if [overload]
|
||||
if overload && tt.name != "Overload" {
|
||||
continue
|
||||
}
|
||||
var types []string
|
||||
if tt.types != nil {
|
||||
types = tt.types
|
||||
} else {
|
||||
types = []string{tt.name}
|
||||
}
|
||||
// Generate the binding and create a Go source file in the workspace
|
||||
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases)
|
||||
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), 0600); err != nil {
|
||||
t.Fatalf("test %d: failed to write binding: %v", i, err)
|
||||
}
|
||||
// Generate the test file with the injected test code
|
||||
code := fmt.Sprintf(`
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip the "Overload" test if [!overload]
|
||||
if !overload && tt.name == "Overload" {
|
||||
return
|
||||
}
|
||||
// Skip all tests except for "Overload" if [overload]
|
||||
if overload && tt.name != "Overload" {
|
||||
return
|
||||
}
|
||||
var types []string
|
||||
if tt.types != nil {
|
||||
types = tt.types
|
||||
} else {
|
||||
types = []string{tt.name}
|
||||
}
|
||||
// Generate the binding and create a Go source file in the workspace
|
||||
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases)
|
||||
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), 0600); err != nil {
|
||||
t.Fatalf("test %d: failed to write binding: %v", i, err)
|
||||
}
|
||||
// Generate the test file with the injected test code
|
||||
code := fmt.Sprintf(`
|
||||
package bindtest
|
||||
|
||||
import (
|
||||
@@ -2003,9 +2047,10 @@ 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), 0600); err != nil {
|
||||
t.Fatalf("test %d: failed to write tests: %v", i, err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil {
|
||||
t.Fatalf("test %d: failed to write tests: %v", i, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
// Convert the package to go modules and use the current source for go-ethereum
|
||||
moder := exec.Command(gocmd, "mod", "init", "bindtest")
|
||||
|
||||
@@ -125,7 +125,6 @@ func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||
|
||||
reflect.Copy(array, reflect.ValueOf(word[0:t.Size]))
|
||||
return array.Interface(), nil
|
||||
|
||||
}
|
||||
|
||||
// forEachUnpack iteratively unpack elements.
|
||||
@@ -300,7 +299,7 @@ func tuplePointsTo(index int, output []byte) (start int, err error) {
|
||||
offset := big.NewInt(0).SetBytes(output[index : index+32])
|
||||
outputLen := big.NewInt(int64(len(output)))
|
||||
|
||||
if offset.Cmp(big.NewInt(int64(len(output)))) > 0 {
|
||||
if offset.Cmp(outputLen) > 0 {
|
||||
return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen)
|
||||
}
|
||||
if offset.BitLen() > 63 {
|
||||
|
||||
@@ -186,7 +186,7 @@ type Backend interface {
|
||||
// TextHash is a helper function that calculates a hash for the given message that can be
|
||||
// safely used to calculate a signature from.
|
||||
//
|
||||
// The hash is calulcated as
|
||||
// The hash is calculated as
|
||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||
//
|
||||
// This gives context to the signed message and prevents signing of transactions.
|
||||
@@ -198,7 +198,7 @@ func TextHash(data []byte) []byte {
|
||||
// TextAndHash is a helper function that calculates a hash for the given message that can be
|
||||
// safely used to calculate a signature from.
|
||||
//
|
||||
// The hash is calulcated as
|
||||
// The hash is calculated as
|
||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||
//
|
||||
// This gives context to the signed message and prevents signing of transactions.
|
||||
|
||||
+23
-6
@@ -165,16 +165,33 @@ func (self *ETHChain) NewRPCHandler(maximumDuration time.Duration) *rpc.Server {
|
||||
return rpc.NewServer(maximumDuration)
|
||||
}
|
||||
|
||||
func (self *ETHChain) AttachEthService(handler *rpc.Server, namespaces []string) {
|
||||
nsmap := make(map[string]bool)
|
||||
for _, ns := range namespaces {
|
||||
nsmap[ns] = true
|
||||
// AttachEthService registers the backend RPC services provided by Ethereum
|
||||
// to the provided handler under their assigned namespaces.
|
||||
func (self *ETHChain) AttachEthService(handler *rpc.Server, names []string) error {
|
||||
enabledServicesSet := make(map[string]struct{})
|
||||
for _, ns := range names {
|
||||
enabledServicesSet[ns] = struct{}{}
|
||||
}
|
||||
|
||||
apiSet := make(map[string]rpc.API)
|
||||
for _, api := range self.backend.APIs() {
|
||||
if nsmap[api.Namespace] {
|
||||
handler.RegisterName(api.Namespace, api.Service)
|
||||
if existingAPI, exists := apiSet[api.Name]; exists {
|
||||
return fmt.Errorf("duplicated API name: %s, namespaces %s and %s", api.Name, api.Namespace, existingAPI.Namespace)
|
||||
}
|
||||
apiSet[api.Name] = api
|
||||
}
|
||||
|
||||
for name := range enabledServicesSet {
|
||||
api, exists := apiSet[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("API service %s not found", name)
|
||||
}
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ETHChain) GetTxSubmitCh() <-chan core.NewTxsEvent {
|
||||
|
||||
@@ -72,8 +72,7 @@ type Engine interface {
|
||||
Author(header *types.Header) (common.Address, error)
|
||||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules of a
|
||||
// given engine. Verifying the seal may be done optionally here, or explicitly
|
||||
// via the VerifySeal method.
|
||||
// given engine.
|
||||
//
|
||||
// NOTE: VerifyHeader does not validate the correctness of fields that rely
|
||||
// on the contents of the block (as opposed to the current and/or parent
|
||||
@@ -84,10 +83,6 @@ type Engine interface {
|
||||
// rules of a given engine.
|
||||
VerifyUncles(chain ChainReader, block *types.Block) error
|
||||
|
||||
// VerifySeal checks whether the crypto seal on a header is valid according to
|
||||
// the consensus rules of the given engine.
|
||||
VerifySeal(chain ChainHeaderReader, header *types.Header) error
|
||||
|
||||
// Prepare initializes the consensus fields of a block header according to the
|
||||
// rules of a particular engine. The changes are executed inline.
|
||||
Prepare(chain ChainHeaderReader, header *types.Header) error
|
||||
|
||||
@@ -28,17 +28,32 @@ var (
|
||||
errBaseFeeNil = errors.New("base fee is nil")
|
||||
)
|
||||
|
||||
type DummyEngine struct {
|
||||
ethFaker bool
|
||||
}
|
||||
type Mode uint
|
||||
|
||||
func NewEngine() *DummyEngine {
|
||||
return &DummyEngine{}
|
||||
}
|
||||
const (
|
||||
ModeSkipHeader Mode = 1 // Skip over header verification
|
||||
ModeSkipBlockFee Mode = 2 // Skip block fee verification
|
||||
)
|
||||
|
||||
type (
|
||||
DummyEngine struct {
|
||||
consensusMode Mode
|
||||
}
|
||||
)
|
||||
|
||||
func NewETHFaker() *DummyEngine {
|
||||
return &DummyEngine{
|
||||
ethFaker: true,
|
||||
consensusMode: ModeSkipBlockFee,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFaker() *DummyEngine {
|
||||
return &DummyEngine{}
|
||||
}
|
||||
|
||||
func NewFullFaker() *DummyEngine {
|
||||
return &DummyEngine{
|
||||
consensusMode: ModeSkipHeader,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +61,8 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade
|
||||
timestamp := new(big.Int).SetUint64(header.Time)
|
||||
|
||||
// Verify that the gas limit is <= 2^63-1
|
||||
cap := uint64(0x7fffffffffffffff)
|
||||
if header.GasLimit > cap {
|
||||
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
|
||||
if header.GasLimit > params.MaxGasLimit {
|
||||
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
|
||||
}
|
||||
// Verify that the gasUsed is <= gasLimit
|
||||
if header.GasUsed > header.GasLimit {
|
||||
@@ -163,8 +177,7 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header
|
||||
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
|
||||
return consensus.ErrInvalidNumber
|
||||
}
|
||||
// Verify the engine specific seal securing the block
|
||||
return self.VerifySeal(chain, header)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *DummyEngine) Author(header *types.Header) (common.Address, error) {
|
||||
@@ -172,6 +185,10 @@ func (self *DummyEngine) Author(header *types.Header) (common.Address, error) {
|
||||
}
|
||||
|
||||
func (self *DummyEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
// If we're running a full engine faking, accept any input as valid
|
||||
if self.consensusMode == ModeSkipHeader {
|
||||
return nil
|
||||
}
|
||||
// Short circuit if the header is known, or it's parent not
|
||||
number := header.Number.Uint64()
|
||||
if chain.GetHeader(header.Hash(), number) != nil {
|
||||
@@ -192,10 +209,6 @@ func (self *DummyEngine) VerifyUncles(chain consensus.ChainReader, block *types.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *DummyEngine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *DummyEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
header.Difficulty = big.NewInt(1)
|
||||
return nil
|
||||
@@ -207,7 +220,7 @@ func (self *DummyEngine) verifyBlockFee(
|
||||
txs []*types.Transaction,
|
||||
receipts []*types.Receipt,
|
||||
) error {
|
||||
if self.ethFaker {
|
||||
if self.consensusMode == ModeSkipBlockFee {
|
||||
return nil
|
||||
}
|
||||
if baseFee == nil || baseFee.Sign() <= 0 {
|
||||
|
||||
@@ -127,7 +127,7 @@ func TestVerifyBlockFee(t *testing.T) {
|
||||
test.parentBlockGasCost,
|
||||
test.parentTime, test.currentTime,
|
||||
)
|
||||
engine := NewEngine()
|
||||
engine := NewFaker()
|
||||
if err := engine.verifyBlockFee(test.baseFee, blockGasCost, test.txs, test.receipts); err != nil {
|
||||
if !test.shouldErr {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
|
||||
@@ -79,7 +79,6 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin
|
||||
common.Big1,
|
||||
)
|
||||
|
||||
// Gas price is increasing, so ensure it does not increase past the maximum
|
||||
baseFee.Add(baseFee, baseFeeDelta)
|
||||
} else {
|
||||
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
// (c) 2019-2021, 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 2015 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 core
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func BenchmarkInsertChain_empty_memdb(b *testing.B) {
|
||||
benchInsertChain(b, false, nil)
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_empty_diskdb(b *testing.B) {
|
||||
benchInsertChain(b, true, nil)
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_valueTx_memdb(b *testing.B) {
|
||||
benchInsertChain(b, false, genValueTx(0))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_valueTx_diskdb(b *testing.B) {
|
||||
benchInsertChain(b, true, genValueTx(0))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_valueTx_100kB_memdb(b *testing.B) {
|
||||
benchInsertChain(b, false, genValueTx(100*1024))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_valueTx_100kB_diskdb(b *testing.B) {
|
||||
benchInsertChain(b, true, genValueTx(100*1024))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_ring200_memdb(b *testing.B) {
|
||||
benchInsertChain(b, false, genTxRing(200))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_ring200_diskdb(b *testing.B) {
|
||||
benchInsertChain(b, true, genTxRing(200))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_ring1000_memdb(b *testing.B) {
|
||||
benchInsertChain(b, false, genTxRing(1000))
|
||||
}
|
||||
|
||||
func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) {
|
||||
benchInsertChain(b, true, genTxRing(1000))
|
||||
}
|
||||
|
||||
var (
|
||||
// This is the content of the genesis block used by the benchmarks.
|
||||
benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey)
|
||||
benchRootFunds = math.BigPow(2, 100)
|
||||
)
|
||||
|
||||
// genValueTx returns a block generator that includes a single
|
||||
// value-transfer transaction with n bytes of extra data in each
|
||||
// block.
|
||||
func genValueTx(nbytes int) func(int, *BlockGen) {
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
data := make([]byte, nbytes)
|
||||
gas, _ := IntrinsicGas(data, nil, false, false, false)
|
||||
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, big.NewInt(225000000000), data), types.HomesteadSigner{}, benchRootKey)
|
||||
gen.AddTx(tx)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ringKeys = make([]*ecdsa.PrivateKey, 1000)
|
||||
ringAddrs = make([]common.Address, len(ringKeys))
|
||||
)
|
||||
|
||||
func init() {
|
||||
ringKeys[0] = benchRootKey
|
||||
ringAddrs[0] = benchRootAddr
|
||||
for i := 1; i < len(ringKeys); i++ {
|
||||
ringKeys[i], _ = crypto.GenerateKey()
|
||||
ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
// genTxRing returns a block generator that sends ether in a ring
|
||||
// among n accounts. This is creates n entries in the state database
|
||||
// and fills the blocks with many small transactions.
|
||||
func genTxRing(naccounts int) func(int, *BlockGen) {
|
||||
from := 0
|
||||
fee := big.NewInt(0).SetUint64(params.TxGas * 225000000000)
|
||||
amount := big.NewInt(0).Set(benchRootFunds)
|
||||
return func(i int, gen *BlockGen) {
|
||||
block := gen.PrevBlock(i - 1)
|
||||
gas := block.GasLimit()
|
||||
for {
|
||||
gas -= params.TxGas
|
||||
if gas < params.TxGas {
|
||||
break
|
||||
}
|
||||
to := (from + 1) % naccounts
|
||||
tx := types.NewTransaction(
|
||||
gen.TxNonce(ringAddrs[from]),
|
||||
ringAddrs[to],
|
||||
amount.Sub(amount, fee),
|
||||
params.TxGas,
|
||||
big.NewInt(225000000000),
|
||||
nil,
|
||||
)
|
||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, ringKeys[from])
|
||||
gen.AddTx(tx)
|
||||
from = to
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false)
|
||||
if err != nil {
|
||||
b.Fatalf("cannot create temporary database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
// Generate a chain of b.N blocks using the supplied block
|
||||
// generator function.
|
||||
gspec := Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
|
||||
}
|
||||
genesis := gspec.MustCommit(db)
|
||||
chain, _, _ := GenerateChain(gspec.Config, genesis, dummy.NewFaker(), db, b.N, 10, gen)
|
||||
|
||||
// Time the insertion of the new chain.
|
||||
// State and blocks are stored in the same DB.
|
||||
chainman, _ := NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
defer chainman.Stop()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
if i, err := chainman.InsertChain(chain); err != nil {
|
||||
b.Fatalf("insert error (block %d): %v\n", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainRead_header_10k(b *testing.B) {
|
||||
benchReadChain(b, false, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkChainRead_full_10k(b *testing.B) {
|
||||
benchReadChain(b, true, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkChainRead_header_100k(b *testing.B) {
|
||||
benchReadChain(b, false, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkChainRead_full_100k(b *testing.B) {
|
||||
benchReadChain(b, true, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkChainRead_header_500k(b *testing.B) {
|
||||
benchReadChain(b, false, 500000)
|
||||
}
|
||||
|
||||
func BenchmarkChainRead_full_500k(b *testing.B) {
|
||||
benchReadChain(b, true, 500000)
|
||||
}
|
||||
|
||||
func BenchmarkChainWrite_header_10k(b *testing.B) {
|
||||
benchWriteChain(b, false, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkChainWrite_full_10k(b *testing.B) {
|
||||
benchWriteChain(b, true, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkChainWrite_header_100k(b *testing.B) {
|
||||
benchWriteChain(b, false, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkChainWrite_full_100k(b *testing.B) {
|
||||
benchWriteChain(b, true, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkChainWrite_header_500k(b *testing.B) {
|
||||
benchWriteChain(b, false, 500000)
|
||||
}
|
||||
|
||||
func BenchmarkChainWrite_full_500k(b *testing.B) {
|
||||
benchWriteChain(b, true, 500000)
|
||||
}
|
||||
|
||||
// makeChainForBench writes a given number of headers or empty blocks/receipts
|
||||
// into a database.
|
||||
func makeChainForBench(db ethdb.Database, full bool, count uint64) {
|
||||
var hash common.Hash
|
||||
for n := uint64(0); n < count; n++ {
|
||||
header := &types.Header{
|
||||
Coinbase: common.Address{},
|
||||
Number: big.NewInt(int64(n)),
|
||||
ParentHash: hash,
|
||||
Difficulty: big.NewInt(1),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: types.EmptyRootHash,
|
||||
}
|
||||
hash = header.Hash()
|
||||
|
||||
rawdb.WriteHeader(db, header)
|
||||
rawdb.WriteCanonicalHash(db, hash, n)
|
||||
|
||||
if full || n == 0 {
|
||||
block := types.NewBlockWithHeader(header)
|
||||
rawdb.WriteBody(db, hash, n, block.Body())
|
||||
rawdb.WriteReceipts(db, hash, n, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
|
||||
if err != nil {
|
||||
b.Fatalf("error opening database at %v: %v", dir, err)
|
||||
}
|
||||
makeChainForBench(db, full, count)
|
||||
db.Close()
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
|
||||
if err != nil {
|
||||
b.Fatalf("error opening database at %v: %v", dir, err)
|
||||
}
|
||||
makeChainForBench(db, full, count)
|
||||
db.Close()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
|
||||
if err != nil {
|
||||
b.Fatalf("error opening database at %v: %v", dir, err)
|
||||
}
|
||||
chain, err := NewBlockChain(db, DefaultCacheConfig, params.TestChainConfig, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
if err != nil {
|
||||
b.Fatalf("error creating chain: %v", err)
|
||||
}
|
||||
|
||||
for n := uint64(0); n < count; n++ {
|
||||
header := chain.GetHeaderByNumber(n)
|
||||
if full {
|
||||
hash := header.Hash()
|
||||
rawdb.ReadBody(db, hash, n)
|
||||
rawdb.ReadReceipts(db, hash, n, chain.Config())
|
||||
}
|
||||
}
|
||||
chain.Stop()
|
||||
db.Close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// (c) 2019-2021, 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 2015 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 core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalcGasLimit(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
pGasLimit uint64
|
||||
max uint64
|
||||
min uint64
|
||||
}{
|
||||
{20000000, 20019530, 19980470},
|
||||
{40000000, 40039061, 39960939},
|
||||
} {
|
||||
// Increase
|
||||
if have, want := CalcGasLimit(0, tc.pGasLimit, 2*tc.pGasLimit, 2*tc.pGasLimit), tc.max; have != want {
|
||||
t.Errorf("test %d: have %d want <%d", i, have, want)
|
||||
}
|
||||
// Decrease
|
||||
if have, want := CalcGasLimit(0, tc.pGasLimit, 0, 0), tc.min; have != want {
|
||||
t.Errorf("test %d: have %d want >%d", i, have, want)
|
||||
}
|
||||
// Small decrease
|
||||
if have, want := CalcGasLimit(0, tc.pGasLimit, tc.pGasLimit-1, tc.pGasLimit-1), tc.pGasLimit-1; have != want {
|
||||
t.Errorf("test %d: have %d want %d", i, have, want)
|
||||
}
|
||||
// Small increase
|
||||
if have, want := CalcGasLimit(0, tc.pGasLimit, tc.pGasLimit+1, tc.pGasLimit+1), tc.pGasLimit+1; have != want {
|
||||
t.Errorf("test %d: have %d want %d", i, have, want)
|
||||
}
|
||||
// No change
|
||||
if have, want := CalcGasLimit(0, tc.pGasLimit, tc.pGasLimit, tc.pGasLimit), tc.pGasLimit; have != want {
|
||||
t.Errorf("test %d: have %d want %d", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
-142
@@ -231,11 +231,12 @@ func NewBlockChain(
|
||||
var nilBlock *types.Block
|
||||
bc.currentBlock.Store(nilBlock)
|
||||
|
||||
// Create the state manager
|
||||
bc.stateManager = NewTrieWriter(bc.stateCache.TrieDB(), cacheConfig)
|
||||
|
||||
if err := bc.loadLastState(lastAcceptedHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the state manager
|
||||
bc.stateManager = NewTrieWriter(bc.stateCache.TrieDB(), cacheConfig)
|
||||
|
||||
// Make sure the state associated with the block is available
|
||||
head := bc.CurrentBlock()
|
||||
@@ -264,17 +265,6 @@ func (bc *BlockChain) SenderCacher() *TxSenderCacher {
|
||||
return bc.senderCacher
|
||||
}
|
||||
|
||||
// empty returns an indicator whether the blockchain is empty.
|
||||
func (bc *BlockChain) empty() bool {
|
||||
genesis := bc.genesisBlock.Hash()
|
||||
for _, hash := range []common.Hash{rawdb.ReadHeadBlockHash(bc.db), rawdb.ReadHeadHeaderHash(bc.db), rawdb.ReadHeadFastBlockHash(bc.db)} {
|
||||
if hash != genesis {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// loadLastState loads the last known chain state from the database. This method
|
||||
// assumes that the chain manager mutex is held.
|
||||
func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error {
|
||||
@@ -305,11 +295,8 @@ func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error {
|
||||
}
|
||||
bc.hc.SetCurrentHeader(currentHeader)
|
||||
|
||||
headerTd := bc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())
|
||||
blockTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
|
||||
|
||||
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
|
||||
log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
|
||||
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
|
||||
log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
|
||||
|
||||
// Otherwise, set the last accepted block and perform a re-org.
|
||||
bc.lastAccepted = bc.GetBlockByHash(lastAcceptedHash)
|
||||
@@ -339,10 +326,10 @@ func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error {
|
||||
return fmt.Errorf("failed to set preference to last accepted block while loading last state: %w", err)
|
||||
}
|
||||
|
||||
// reprocessState as necessary to ensure that the last accepted state is
|
||||
// reprocessState is necessary to ensure that the last accepted state is
|
||||
// available. The state may not be available if it was not committed due
|
||||
// to an unclean shutdown.
|
||||
return bc.reprocessState(bc.lastAccepted, 2*commitInterval, true)
|
||||
return bc.reprocessState(bc.lastAccepted, 2*commitInterval)
|
||||
}
|
||||
|
||||
// removeIndices removes all transaction lookup entries for the transactions contained in the canonical chain
|
||||
@@ -369,7 +356,6 @@ func (bc *BlockChain) removeIndices(from, to uint64) (int, error) {
|
||||
func (bc *BlockChain) loadGenesisState() error {
|
||||
// Prepare the genesis block and reinitialise the chain
|
||||
batch := bc.db.NewBatch()
|
||||
rawdb.WriteTd(batch, bc.genesisBlock.Hash(), bc.genesisBlock.NumberU64(), bc.genesisBlock.Difficulty())
|
||||
rawdb.WriteBlock(batch, bc.genesisBlock)
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to write genesis block", "err", err)
|
||||
@@ -418,32 +404,24 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
|
||||
|
||||
// writeHeadBlock injects a new head block into the current block chain. This method
|
||||
// assumes that the block is indeed a true head. It will also reset the head
|
||||
// header and the head fast sync block to this very same block if they are older
|
||||
// or if they are on a different side chain.
|
||||
// header to this very same block if they are older or if they are on a different side chain.
|
||||
//
|
||||
// Note, this function assumes that the `mu` mutex is held!
|
||||
func (bc *BlockChain) writeHeadBlock(block *types.Block) {
|
||||
// If the block is on a side chain or an unknown one, force other heads onto it too
|
||||
updateHeads := rawdb.ReadCanonicalHash(bc.db, block.NumberU64()) != block.Hash()
|
||||
|
||||
// Add the block to the canonical chain number scheme and mark as the head
|
||||
batch := bc.db.NewBatch()
|
||||
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
|
||||
rawdb.WriteHeadBlockHash(batch, block.Hash())
|
||||
|
||||
// If the block is better than our head or is on a different chain, force update heads
|
||||
if updateHeads {
|
||||
rawdb.WriteHeadHeaderHash(batch, block.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(batch, block.Hash())
|
||||
}
|
||||
rawdb.WriteHeadBlockHash(batch, block.Hash())
|
||||
rawdb.WriteHeadHeaderHash(batch, block.Hash())
|
||||
|
||||
// Flush the whole batch into the disk, exit the node if failed
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to update chain indexes and markers", "err", err)
|
||||
}
|
||||
// Update all in-memory chain markers in the last step
|
||||
if updateHeads {
|
||||
bc.hc.SetCurrentHeader(block.Header())
|
||||
}
|
||||
bc.hc.SetCurrentHeader(block.Header())
|
||||
bc.currentBlock.Store(block)
|
||||
}
|
||||
|
||||
@@ -561,15 +539,6 @@ func (bc *BlockChain) Stop() {
|
||||
log.Info("Blockchain stopped")
|
||||
}
|
||||
|
||||
// WriteStatus status of write
|
||||
type WriteStatus byte
|
||||
|
||||
const (
|
||||
NonStatTy WriteStatus = iota
|
||||
CanonStatTy
|
||||
SideStatTy
|
||||
)
|
||||
|
||||
// SetPreference attempts to update the head block to be the provided block and
|
||||
// emits a ChainHeadEvent if successful. This function will handle all reorg
|
||||
// side effects, if necessary.
|
||||
@@ -607,7 +576,7 @@ func (bc *BlockChain) setPreference(block *types.Block) error {
|
||||
return fmt.Errorf("unable to invoke writeKnownBlock: %w", err)
|
||||
}
|
||||
|
||||
// Send an ChainHeadEvent if we end up altering
|
||||
// Send a ChainHeadEvent if we end up altering
|
||||
// the head block. Many internal aysnc processes rely on
|
||||
// receiving these events (i.e. the TxPool).
|
||||
bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})
|
||||
@@ -752,30 +721,38 @@ func (bc *BlockChain) newTip(block *types.Block) bool {
|
||||
return block.ParentHash() == bc.CurrentBlock().Hash()
|
||||
}
|
||||
|
||||
// writeBlockAndSetHead persists the block and associated state to the database
|
||||
// and optimistically updates the canonical chain if [block] extends the current
|
||||
// canonical chain.
|
||||
// writeBlockAndSetHead expects to be the last verification step during InsertBlock
|
||||
// since it creates a reference that will only be cleaned up by Accept/Reject.
|
||||
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
|
||||
if err := bc.writeBlockWithState(block, receipts, logs, state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If [block] represents a new tip of the canonical chain, we optimistically add it before
|
||||
// setPreference is called. Otherwise, we consider it a side chain block.
|
||||
if bc.newTip(block) {
|
||||
bc.writeCanonicalBlockWithLogs(block, logs)
|
||||
} else {
|
||||
bc.chainSideFeed.Send(ChainSideEvent{Block: block})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeBlockWithState writes the block and all associated state to the database,
|
||||
// but it expects the chain mutex to be held.
|
||||
// writeBlockWithState expects to be the last verification step during InsertBlock
|
||||
// since it creates a reference that will only be cleaned up by Accept/Reject.
|
||||
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) (WriteStatus, error) {
|
||||
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
|
||||
bc.wg.Add(1)
|
||||
defer bc.wg.Done()
|
||||
|
||||
// Calculate the total difficulty of the block
|
||||
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
|
||||
if ptd == nil {
|
||||
return NonStatTy, consensus.ErrUnknownAncestor
|
||||
}
|
||||
// Make sure no inconsistent state is leaked during insertion
|
||||
// currentBlock := bc.CurrentBlock()
|
||||
// localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
|
||||
externTd := new(big.Int).Add(block.Difficulty(), ptd)
|
||||
|
||||
// Irrelevant of the canonical status, write the block itself to the database.
|
||||
//
|
||||
// Note all the components of block(td, hash->number map, header, body, receipts)
|
||||
// should be written atomically. BlockBatch is used for containing all components.
|
||||
blockBatch := bc.db.NewBatch()
|
||||
rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
|
||||
rawdb.WriteBlock(blockBatch, block)
|
||||
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
||||
rawdb.WritePreimages(blockBatch, state.Preimages())
|
||||
@@ -792,7 +769,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
_, err = state.CommitWithSnap(bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash())
|
||||
}
|
||||
if err != nil {
|
||||
return NonStatTy, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Note: if InsertTrie must be the last step in verification that can return an error.
|
||||
@@ -807,18 +784,10 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
log.Debug("failed to discard snapshot after being unable to insert block trie", "block", block.Hash(), "root", block.Root())
|
||||
}
|
||||
}
|
||||
return NonStatTy, err
|
||||
return err
|
||||
}
|
||||
|
||||
// If [block] represents a new tip of the canonical chain, we optimistically add it before
|
||||
// setPreference is called. Otherwise, we consider it a side chain block.
|
||||
if bc.newTip(block) {
|
||||
bc.writeCanonicalBlockWithLogs(block, logs)
|
||||
return CanonStatTy, nil
|
||||
}
|
||||
|
||||
bc.chainSideFeed.Send(ChainSideEvent{Block: block})
|
||||
return SideStatTy, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertChain attempts to insert the given batch of blocks in to the canonical
|
||||
@@ -992,42 +961,46 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error {
|
||||
}
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
// writeBlockWithState creates a reference that will be cleaned up in Accept/Reject
|
||||
// so we need to ensure an error cannot occur later in verification, since that would
|
||||
// cause the referenced root to never be dereferenced.
|
||||
status, err := bc.writeBlockWithState(block, receipts, logs, statedb)
|
||||
if err != nil {
|
||||
// writeBlockWithState (called within writeBlockAndSethead) creates a reference that
|
||||
// will be cleaned up in Accept/Reject so we need to ensure an error cannot occur
|
||||
// later in verification, since that would cause the referenced root to never be dereferenced.
|
||||
if err := bc.writeBlockAndSetHead(block, receipts, logs, statedb); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
|
||||
"parentHash", block.ParentHash(),
|
||||
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
|
||||
"elapsed", common.PrettyDuration(time.Since(start)),
|
||||
"root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(),
|
||||
)
|
||||
|
||||
switch status {
|
||||
case CanonStatTy:
|
||||
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
|
||||
"parentHash", block.ParentHash(),
|
||||
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
|
||||
"elapsed", common.PrettyDuration(time.Since(start)),
|
||||
"root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(),
|
||||
)
|
||||
// Only count canonical blocks for GC processing time
|
||||
case SideStatTy:
|
||||
log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(),
|
||||
"parentHash", block.ParentHash(),
|
||||
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
|
||||
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
|
||||
"root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(),
|
||||
)
|
||||
default:
|
||||
// This in theory is impossible, but lets be nice to our future selves and leave
|
||||
// a log, instead of trying to track down blocks imports that don't emit logs.
|
||||
log.Warn("Inserted block with unknown status", "number", block.Number(), "hash", block.Hash(),
|
||||
"parentHash", block.ParentHash(),
|
||||
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
|
||||
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
|
||||
"root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectLogs collects the logs that were generated or removed during
|
||||
// the processing of the block that corresponds with the given hash.
|
||||
// These logs are later announced as deleted or reborn.
|
||||
func (bc *BlockChain) collectLogs(hash common.Hash, removed bool) []*types.Log {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
return bc.gatherBlockLogs(hash, *number, removed)
|
||||
}
|
||||
|
||||
return err
|
||||
// mergeLogs returns a merged log slice with specified sort order.
|
||||
func mergeLogs(logs [][]*types.Log, reverse bool) []*types.Log {
|
||||
var ret []*types.Log
|
||||
if reverse {
|
||||
for i := len(logs) - 1; i >= 0; i-- {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(logs); i++ {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// reorg takes two blocks, an old chain and a new chain and will reconstruct the
|
||||
@@ -1044,45 +1017,17 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
|
||||
deletedLogs [][]*types.Log
|
||||
rebirthLogs [][]*types.Log
|
||||
|
||||
// collectLogs collects the logs that were generated or removed during
|
||||
// the processing of the block that corresponds with the given hash.
|
||||
// These logs are later announced as deleted or reborn
|
||||
collectLogs = func(hash common.Hash, removed bool) {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return
|
||||
}
|
||||
logs := bc.gatherBlockLogs(hash, *number, removed)
|
||||
if len(logs) > 0 {
|
||||
if removed {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
} else {
|
||||
rebirthLogs = append(rebirthLogs, logs)
|
||||
}
|
||||
}
|
||||
}
|
||||
// mergeLogs returns a merged log slice with specified sort order.
|
||||
mergeLogs = func(logs [][]*types.Log, reverse bool) []*types.Log {
|
||||
var ret []*types.Log
|
||||
if reverse {
|
||||
for i := len(logs) - 1; i >= 0; i-- {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(logs); i++ {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
)
|
||||
// Reduce the longer chain to the same number as the shorter one
|
||||
if oldBlock.NumberU64() > newBlock.NumberU64() {
|
||||
// Old chain is longer, gather all transactions and logs as deleted ones
|
||||
for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) {
|
||||
oldChain = append(oldChain, oldBlock)
|
||||
collectLogs(oldBlock.Hash(), true)
|
||||
// Collect deleted logs for notification
|
||||
logs := bc.collectLogs(oldBlock.Hash(), true)
|
||||
if len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// New chain is longer, stash all blocks away for subsequent insertion
|
||||
@@ -1106,7 +1051,11 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
}
|
||||
// Remove an old block as well as stash away a new block
|
||||
oldChain = append(oldChain, oldBlock)
|
||||
collectLogs(oldBlock.Hash(), true)
|
||||
// Collect deleted logs for notification
|
||||
logs := bc.collectLogs(oldBlock.Hash(), true)
|
||||
if len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
}
|
||||
|
||||
newChain = append(newChain, newBlock)
|
||||
|
||||
@@ -1148,7 +1097,10 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
bc.writeHeadBlock(newChain[i])
|
||||
|
||||
// Collect reborn logs due to chain reorg
|
||||
collectLogs(newChain[i].Hash(), false)
|
||||
logs := bc.collectLogs(newChain[i].Hash(), false)
|
||||
if len(logs) > 0 {
|
||||
rebirthLogs = append(rebirthLogs, logs)
|
||||
}
|
||||
}
|
||||
// Delete any canonical number assignments above the new head
|
||||
indexesBatch := bc.db.NewBatch()
|
||||
@@ -1255,7 +1207,7 @@ func (bc *BlockChain) RemoveRejectedBlocks(start, end uint64) error {
|
||||
// it reaches a block with a state committed to the database. reprocessState does not use
|
||||
// snapshots since the disk layer for snapshots will most likely be above the last committed
|
||||
// state that reprocessing will start from.
|
||||
func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64, report bool) error {
|
||||
func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error {
|
||||
origin := current.NumberU64()
|
||||
// If the state is already available, skip re-processing
|
||||
statedb, err := state.New(current.Root(), bc.stateCache, nil)
|
||||
@@ -1298,7 +1250,7 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64, report
|
||||
log.Info("Re-executing blocks to generate state for last accepted block", "from", current.NumberU64()+1, "to", origin)
|
||||
for current.NumberU64() < origin {
|
||||
// Print progress logs if long enough time elapsed
|
||||
if time.Since(logged) > 8*time.Second && report {
|
||||
if time.Since(logged) > 8*time.Second {
|
||||
log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64(), "elapsed", time.Since(start))
|
||||
logged = time.Now()
|
||||
}
|
||||
@@ -1333,12 +1285,11 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64, report
|
||||
}
|
||||
previousRoot = root
|
||||
}
|
||||
if report {
|
||||
nodes, imgs := triedb.Size()
|
||||
log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
|
||||
}
|
||||
|
||||
nodes, imgs := triedb.Size()
|
||||
log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
|
||||
if previousRoot != (common.Hash{}) {
|
||||
return triedb.Commit(previousRoot, report, nil)
|
||||
return triedb.Commit(previousRoot, true, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
@@ -208,12 +206,6 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) *rawdb.LegacyTxLook
|
||||
return lookup
|
||||
}
|
||||
|
||||
// GetTd retrieves a block's total difficulty in the canonical chain from the
|
||||
// database by hash and number, caching it if found.
|
||||
func (bc *BlockChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
return bc.hc.GetTd(hash, number)
|
||||
}
|
||||
|
||||
// HasState checks if state trie is fully present in the database or not.
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
_, err := bc.stateCache.OpenTrie(hash)
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
// (c) 2019-2021, 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 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
|
||||
// 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/>.
|
||||
|
||||
// Tests that abnormal program termination (i.e.crash) and restart doesn't leave
|
||||
// the database in some strange state with gaps in the chain, nor with block data
|
||||
// dangling in the future.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// rewindTest is a test case for chain rollback upon user request.
|
||||
type rewindTest struct {
|
||||
canonicalBlocks int // Number of blocks to generate for the canonical chain (heavier)
|
||||
sidechainBlocks int // Number of blocks to generate for the side chain (lighter)
|
||||
commitBlock uint64 // Block number for which to commit the state to disk
|
||||
|
||||
expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis)
|
||||
expSidechainBlocks int // Number of sidechain blocks expected to remain in the database (excl. genesis)
|
||||
expHeadBlock uint64 // Block number of the expected head full block
|
||||
}
|
||||
|
||||
// Tests a recovery for a short canonical chain where a recent block was already
|
||||
// committed to disk and then the process crashed. In this case we expect the full
|
||||
// chain to be rolled back to the committed block, but the chain data itself left
|
||||
// in the database for replaying.
|
||||
func TestShortRepair(t *testing.T) { testShortRepair(t, false) }
|
||||
func TestShortRepairWithSnapshots(t *testing.T) { testShortRepair(t, true) }
|
||||
|
||||
func testShortRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 8,
|
||||
sidechainBlocks: 0,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expSidechainBlocks: 0,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a short canonical chain and a shorter side chain, where a
|
||||
// recent block was already committed to disk and then the process crashed. In this
|
||||
// test scenario the side chain is below the committed block. In this case we expect
|
||||
// the canonical chain to be rolled back to the committed block, but the chain data
|
||||
// itself left in the database for replaying.
|
||||
func TestShortOldForkedRepair(t *testing.T) { testShortOldForkedRepair(t, false) }
|
||||
func TestShortOldForkedRepairWithSnapshots(t *testing.T) { testShortOldForkedRepair(t, true) }
|
||||
|
||||
func testShortOldForkedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
// └->S1->S2->S3
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 8,
|
||||
sidechainBlocks: 3,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expSidechainBlocks: 3,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a short canonical chain and a shorter side chain, where a
|
||||
// recent block was already committed to disk and then the process crashed. In this
|
||||
// test scenario the side chain reaches above the committed block. In this case we
|
||||
// expect the canonical chain to be rolled back to the committed block, but the
|
||||
// chain data itself left in the database for replaying.
|
||||
func TestShortNewlyForkedRepair(t *testing.T) { testShortNewlyForkedRepair(t, false) }
|
||||
func TestShortNewlyForkedRepairWithSnapshots(t *testing.T) { testShortNewlyForkedRepair(t, true) }
|
||||
|
||||
func testShortNewlyForkedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
// └->S1->S2->S3->S4->S5->S6
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 8,
|
||||
sidechainBlocks: 6,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expSidechainBlocks: 6,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a short canonical chain and a longer side chain, where a
|
||||
// recent block was already committed to disk and then the process crashed. In this
|
||||
// case we expect the canonical chain to be rolled back to the committed block, but
|
||||
// the chain data itself left in the database for replaying.
|
||||
func TestShortReorgedRepair(t *testing.T) { testShortReorgedRepair(t, false) }
|
||||
func TestShortReorgedRepairWithSnapshots(t *testing.T) { testShortReorgedRepair(t, true) }
|
||||
|
||||
func testShortReorgedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 8,
|
||||
sidechainBlocks: 10,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expSidechainBlocks: 10,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain where a recent block was already
|
||||
// committed to disk and then the process crashed. In this case we expect the chain
|
||||
// to be rolled back to the committed block, but the chain data itself left in the
|
||||
// database for replaying.
|
||||
func TestLongShallowRepair(t *testing.T) { testLongShallowRepair(t, false) }
|
||||
func TestLongShallowRepairWithSnapshots(t *testing.T) { testLongShallowRepair(t, true) }
|
||||
|
||||
func testLongShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 18,
|
||||
sidechainBlocks: 0,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 18,
|
||||
expSidechainBlocks: 0,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain where a recent block was already committed
|
||||
// to disk and then the process crashed. In this case we expect the chain to be rolled
|
||||
// back to the committed block, but the chain data itself left in the database for replaying.
|
||||
func TestLongDeepRepair(t *testing.T) { testLongDeepRepair(t, false) }
|
||||
func TestLongDeepRepairWithSnapshots(t *testing.T) { testLongDeepRepair(t, true) }
|
||||
|
||||
func testLongDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb: none
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 24,
|
||||
sidechainBlocks: 0,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 24,
|
||||
expSidechainBlocks: 0,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain with a shorter side chain, where a recent
|
||||
// block was already committed to disk and then the process crashed. In this test scenario
|
||||
// the side chain is below the committed block. In this case we expect the chain to be
|
||||
// rolled back to the committed block, but the chain data itself left in the database
|
||||
// for replaying.
|
||||
func TestLongOldForkedShallowRepair(t *testing.T) {
|
||||
testLongOldForkedShallowRepair(t, false)
|
||||
}
|
||||
|
||||
func TestLongOldForkedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
|
||||
// └->S1->S2->S3
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 18,
|
||||
sidechainBlocks: 3,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 18,
|
||||
expSidechainBlocks: 3,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain a shorter side chain, where a recent block
|
||||
// was already committed to disk and then the process crashed. In this test scenario the side
|
||||
// chain is below the committed block. In this case we expect the canonical chain to be
|
||||
// rolled back to the committed block, but the chain data itself left in the database for replaying.
|
||||
func TestLongOldForkedDeepRepair(t *testing.T) { testLongOldForkedDeepRepair(t, false) }
|
||||
func TestLongOldForkedDeepRepairWithSnapshots(t *testing.T) { testLongOldForkedDeepRepair(t, true) }
|
||||
|
||||
func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
|
||||
// └->S1->S2->S3
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 24,
|
||||
sidechainBlocks: 3,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 24,
|
||||
expSidechainBlocks: 3,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain with a shorter side chain, where a recent
|
||||
// block was already committed to disk and then the process crashed. In this test scenario
|
||||
// the side chain is above the committed block. In this case we expect the chain to be
|
||||
// rolled back to the committed block, but the chain data itself left in the database for replaying.
|
||||
func TestLongNewerForkedShallowRepair(t *testing.T) {
|
||||
testLongNewerForkedShallowRepair(t, false)
|
||||
}
|
||||
|
||||
func TestLongNewerForkedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 18,
|
||||
sidechainBlocks: 12,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 18,
|
||||
expSidechainBlocks: 12,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain with a shorter side chain, where a recent block
|
||||
// was already committed to disk and then the process crashed. In this test scenario the side
|
||||
// chain is above the committed block. In this case we expect the canonical chain to be rolled
|
||||
// back to the committed block, but the chain data itself left in the database for replaying.
|
||||
func TestLongNewerForkedDeepRepair(t *testing.T) { testLongNewerForkedDeepRepair(t, false) }
|
||||
func TestLongNewerForkedDeepRepairWithSnapshots(t *testing.T) { testLongNewerForkedDeepRepair(t, true) }
|
||||
|
||||
func testLongNewerForkedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 24,
|
||||
sidechainBlocks: 12,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 24,
|
||||
expSidechainBlocks: 12,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain with a longer side chain, where a recent block
|
||||
// was already committed to disk and then the process crashed. In this case we expect the chain to be
|
||||
// rolled back to the committed block, but the chain data itself left in the database for replaying.
|
||||
func TestLongReorgedShallowRepair(t *testing.T) { testLongReorgedShallowRepair(t, false) }
|
||||
func TestLongReorgedShallowRepairWithSnapshots(t *testing.T) { testLongReorgedShallowRepair(t, true) }
|
||||
|
||||
func testLongReorgedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 18,
|
||||
sidechainBlocks: 26,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 18,
|
||||
expSidechainBlocks: 26,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
// Tests a recovery for a long canonical chain with a longer side chain, where a recent block
|
||||
// was already committed to disk and then the process crashed. In this case we expect the canonical
|
||||
// chains to be rolled back to the committed block, but the chain data itself left in the database
|
||||
// for replaying.
|
||||
func TestLongReorgedDeepRepair(t *testing.T) { testLongReorgedDeepRepair(t, false) }
|
||||
func TestLongReorgedDeepRepairWithSnapshots(t *testing.T) { testLongReorgedDeepRepair(t, true) }
|
||||
|
||||
func testLongReorgedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
//
|
||||
// Commit: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
//
|
||||
// Expected head block : C4 (C0 with no snapshots)
|
||||
rt := &rewindTest{
|
||||
canonicalBlocks: 24,
|
||||
sidechainBlocks: 26,
|
||||
commitBlock: 4,
|
||||
expCanonicalBlocks: 24,
|
||||
expSidechainBlocks: 26,
|
||||
expHeadBlock: 0,
|
||||
}
|
||||
if snapshots {
|
||||
rt.expHeadBlock = 4
|
||||
}
|
||||
testRepair(t, rt, snapshots)
|
||||
}
|
||||
|
||||
func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
// 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(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)
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent database: %v", err)
|
||||
}
|
||||
defer db.Close() // Might double close, should be fine
|
||||
|
||||
// Initialize a fresh chain
|
||||
var (
|
||||
genesis = (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.TestInitialBaseFee)}).MustCommit(db)
|
||||
engine = dummy.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
SnapshotLimit: 0, // Disable snapshot by default
|
||||
}
|
||||
)
|
||||
if snapshots {
|
||||
config.SnapshotLimit = 256
|
||||
}
|
||||
chain, err := NewBlockChain(db, config, params.TestChainConfig, engine, vm.Config{}, common.Hash{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create chain: %v", err)
|
||||
}
|
||||
lastAcceptedHash := chain.GetBlockByNumber(0).Hash()
|
||||
|
||||
// If sidechain blocks are needed, make a light chain and import it
|
||||
var sideblocks types.Blocks
|
||||
if tt.sidechainBlocks > 0 {
|
||||
sideblocks, _, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, 10, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x01})
|
||||
})
|
||||
if _, err := chain.InsertChain(sideblocks); err != nil {
|
||||
t.Fatalf("Failed to import side chain: %v", err)
|
||||
}
|
||||
}
|
||||
canonblocks, _, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, 10, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x02})
|
||||
b.SetDifficulty(big.NewInt(1000000))
|
||||
})
|
||||
if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil {
|
||||
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||
}
|
||||
if tt.commitBlock > 0 {
|
||||
if snapshots {
|
||||
for i := uint64(0); i < tt.commitBlock; i++ {
|
||||
if err := chain.Accept(canonblocks[i]); err != nil {
|
||||
t.Fatalf("Failed to accept block %v: %v", i, err)
|
||||
}
|
||||
lastAcceptedHash = canonblocks[i].Hash()
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
|
||||
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
||||
}
|
||||
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
db.Close()
|
||||
|
||||
// Start a new blockchain back up and see where the repait leads us
|
||||
db, err = rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
chain, err = NewBlockChain(db, DefaultCacheConfig, params.TestChainConfig, engine, vm.Config{}, lastAcceptedHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// Iterate over all the remaining blocks and ensure there are no gaps
|
||||
verifyNoGaps(t, chain, true, canonblocks)
|
||||
verifyNoGaps(t, chain, false, sideblocks)
|
||||
verifyCutoff(t, chain, true, canonblocks, tt.expCanonicalBlocks)
|
||||
verifyCutoff(t, chain, false, sideblocks, tt.expSidechainBlocks)
|
||||
|
||||
if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadBlock {
|
||||
t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadBlock)
|
||||
}
|
||||
if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock {
|
||||
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// (c) 2019-2021, 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 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
|
||||
// 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/>.
|
||||
|
||||
// Tests that setting the chain head backwards doesn't leave the database in some
|
||||
// strange state with gaps in the chain, nor with block data dangling in the future.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
)
|
||||
|
||||
// verifyNoGaps checks that there are no gaps after the initial set of blocks in
|
||||
// the database and errors if found.
|
||||
func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) {
|
||||
t.Helper()
|
||||
|
||||
var end uint64
|
||||
for i := uint64(0); i <= uint64(len(inserted)); i++ {
|
||||
header := chain.GetHeaderByNumber(i)
|
||||
if header == nil && end == 0 {
|
||||
end = i
|
||||
}
|
||||
if header != nil && end > 0 {
|
||||
if canonical {
|
||||
t.Errorf("Canonical header gap between #%d-#%d", end, i-1)
|
||||
} else {
|
||||
t.Errorf("Sidechain header gap between #%d-#%d", end, i-1)
|
||||
}
|
||||
end = 0 // Reset for further gap detection
|
||||
}
|
||||
}
|
||||
end = 0
|
||||
for i := uint64(0); i <= uint64(len(inserted)); i++ {
|
||||
block := chain.GetBlockByNumber(i)
|
||||
if block == nil && end == 0 {
|
||||
end = i
|
||||
}
|
||||
if block != nil && end > 0 {
|
||||
if canonical {
|
||||
t.Errorf("Canonical block gap between #%d-#%d", end, i-1)
|
||||
} else {
|
||||
t.Errorf("Sidechain block gap between #%d-#%d", end, i-1)
|
||||
}
|
||||
end = 0 // Reset for further gap detection
|
||||
}
|
||||
}
|
||||
end = 0
|
||||
for i := uint64(1); i <= uint64(len(inserted)); i++ {
|
||||
receipts := chain.GetReceiptsByHash(inserted[i-1].Hash())
|
||||
if receipts == nil && end == 0 {
|
||||
end = i
|
||||
}
|
||||
if receipts != nil && end > 0 {
|
||||
if canonical {
|
||||
t.Errorf("Canonical receipt gap between #%d-#%d", end, i-1)
|
||||
} else {
|
||||
t.Errorf("Sidechain receipt gap between #%d-#%d", end, i-1)
|
||||
}
|
||||
end = 0 // Reset for further gap detection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyCutoff checks that there are no chain data available in the chain after
|
||||
// the specified limit, but that it is available before.
|
||||
func verifyCutoff(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks, head int) {
|
||||
t.Helper()
|
||||
|
||||
for i := 1; i <= len(inserted); i++ {
|
||||
if i <= head {
|
||||
if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header == nil {
|
||||
if canonical {
|
||||
t.Errorf("Canonical header #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
} else {
|
||||
t.Errorf("Sidechain header #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
}
|
||||
}
|
||||
if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block == nil {
|
||||
if canonical {
|
||||
t.Errorf("Canonical block #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
} else {
|
||||
t.Errorf("Sidechain block #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
}
|
||||
}
|
||||
if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts == nil {
|
||||
if canonical {
|
||||
t.Errorf("Canonical receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
} else {
|
||||
t.Errorf("Sidechain receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header != nil {
|
||||
if canonical {
|
||||
t.Errorf("Canonical header #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
} else {
|
||||
t.Errorf("Sidechain header #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
}
|
||||
}
|
||||
if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block != nil {
|
||||
if canonical {
|
||||
t.Errorf("Canonical block #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
} else {
|
||||
t.Errorf("Sidechain block #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
}
|
||||
}
|
||||
if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts != nil {
|
||||
if canonical {
|
||||
t.Errorf("Canonical receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
} else {
|
||||
t.Errorf("Sidechain receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
// (c) 2019-2021, 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 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
|
||||
// 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/>.
|
||||
|
||||
// Tests that abnormal program termination (i.e.crash) and restart can recovery
|
||||
// the snapshot properly if the snapshot is enabled.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// snapshotTestBasic wraps the common testing fields in the snapshot tests.
|
||||
type snapshotTestBasic struct {
|
||||
chainBlocks int // Number of blocks to generate for the canonical chain
|
||||
snapshotBlock uint64 // Block number of the relevant snapshot disk layer
|
||||
|
||||
expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis)
|
||||
expHeadBlock uint64 // Block number of the expected head full block
|
||||
expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer
|
||||
|
||||
// share fields, set in runtime
|
||||
datadir string
|
||||
db ethdb.Database
|
||||
gendb ethdb.Database
|
||||
engine consensus.Engine
|
||||
|
||||
lastAcceptedHash common.Hash
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent database: %v", err)
|
||||
}
|
||||
// Initialize a fresh chain
|
||||
var (
|
||||
genesis = (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.TestInitialBaseFee)}).MustCommit(db)
|
||||
engine = dummy.NewFullFaker()
|
||||
gendb = rawdb.NewMemoryDatabase()
|
||||
|
||||
// Snapshot is enabled, the first snapshot is created from the Genesis.
|
||||
// The snapshot memory allowance is 256MB, it means no snapshot flush
|
||||
// will happen during the block insertion.
|
||||
cacheConfig = DefaultCacheConfig
|
||||
)
|
||||
chain, err := NewBlockChain(db, cacheConfig, params.TestChainConfig, engine, vm.Config{}, common.Hash{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create chain: %v", err)
|
||||
}
|
||||
blocks, _, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, 10, func(i int, b *BlockGen) {})
|
||||
|
||||
// genesis as last accepted
|
||||
basic.lastAcceptedHash = chain.GetBlockByNumber(0).Hash()
|
||||
|
||||
// Insert the blocks with configured settings.
|
||||
var breakpoints []uint64
|
||||
breakpoints = append(breakpoints, basic.snapshotBlock)
|
||||
var startPoint uint64
|
||||
for _, point := range breakpoints {
|
||||
if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil {
|
||||
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||
}
|
||||
startPoint = point
|
||||
|
||||
if basic.snapshotBlock > 0 && basic.snapshotBlock == point {
|
||||
// Flushing from 0 to snapshotBlock into the disk
|
||||
for i := uint64(0); i < point; i++ {
|
||||
if err := chain.Accept(blocks[i]); err != nil {
|
||||
t.Fatalf("Failed to accept block %v: %v", i, err)
|
||||
}
|
||||
basic.lastAcceptedHash = blocks[i].Hash()
|
||||
}
|
||||
|
||||
diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root()
|
||||
if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) {
|
||||
t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := chain.InsertChain(blocks[startPoint:]); err != nil {
|
||||
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
||||
}
|
||||
|
||||
// Set runtime fields
|
||||
basic.datadir = datadir
|
||||
basic.db = db
|
||||
basic.gendb = gendb
|
||||
basic.engine = engine
|
||||
return chain, blocks
|
||||
}
|
||||
|
||||
func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) {
|
||||
// Iterate over all the remaining blocks and ensure there are no gaps
|
||||
verifyNoGaps(t, chain, true, blocks)
|
||||
verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks)
|
||||
|
||||
if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadBlock {
|
||||
t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadBlock)
|
||||
}
|
||||
if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock {
|
||||
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock)
|
||||
}
|
||||
|
||||
// Check the disk layer, ensure they are matched
|
||||
block := chain.GetBlockByNumber(basic.expSnapshotBottom)
|
||||
if block == nil {
|
||||
t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
|
||||
} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
|
||||
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
|
||||
} else if len(chain.snaps.Snapshots(block.Hash(), -1, false)) != 1 {
|
||||
t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
|
||||
}
|
||||
|
||||
// Check the snapshot, ensure it's integrated
|
||||
if err := chain.snaps.Verify(block.Root()); err != nil {
|
||||
t.Errorf("The disk layer is not integrated %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (basic *snapshotTestBasic) dump() string {
|
||||
buffer := new(strings.Builder)
|
||||
|
||||
fmt.Fprint(buffer, "Chain:\n G")
|
||||
for i := 0; i < basic.chainBlocks; i++ {
|
||||
fmt.Fprintf(buffer, "->C%d", i+1)
|
||||
}
|
||||
fmt.Fprint(buffer, " (HEAD)\n\n")
|
||||
|
||||
fmt.Fprintf(buffer, "Snapshot: G")
|
||||
if basic.snapshotBlock > 0 {
|
||||
fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock)
|
||||
}
|
||||
fmt.Fprint(buffer, "\n")
|
||||
|
||||
//if crash {
|
||||
// fmt.Fprintf(buffer, "\nCRASH\n\n")
|
||||
//} else {
|
||||
// fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", basic.setHead)
|
||||
//}
|
||||
fmt.Fprintf(buffer, "------------------------------\n\n")
|
||||
|
||||
fmt.Fprint(buffer, "Expected in leveldb:\n G")
|
||||
for i := 0; i < basic.expCanonicalBlocks; i++ {
|
||||
fmt.Fprintf(buffer, "->C%d", i+1)
|
||||
}
|
||||
fmt.Fprintf(buffer, "\n\n")
|
||||
fmt.Fprintf(buffer, "Expected head header : C%d\n", basic.expHeadBlock)
|
||||
if basic.expHeadBlock == 0 {
|
||||
fmt.Fprintf(buffer, "Expected head block : G\n")
|
||||
} else {
|
||||
fmt.Fprintf(buffer, "Expected head block : C%d\n", basic.expHeadBlock)
|
||||
}
|
||||
if basic.expSnapshotBottom == 0 {
|
||||
fmt.Fprintf(buffer, "Expected snapshot disk : G\n")
|
||||
} else {
|
||||
fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", basic.expSnapshotBottom)
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (basic *snapshotTestBasic) teardown() {
|
||||
basic.db.Close()
|
||||
basic.gendb.Close()
|
||||
os.RemoveAll(basic.datadir)
|
||||
}
|
||||
|
||||
// snapshotTest is a test case type for normal snapshot recovery.
|
||||
// It can be used for testing that restart Geth normally.
|
||||
type snapshotTest struct {
|
||||
snapshotTestBasic
|
||||
}
|
||||
|
||||
func (snaptest *snapshotTest) 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)
|
||||
|
||||
// Restart the chain normally
|
||||
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)
|
||||
}
|
||||
defer newchain.Stop()
|
||||
|
||||
snaptest.verify(t, newchain, blocks)
|
||||
}
|
||||
|
||||
// crashSnapshotTest is a test case type for innormal snapshot recovery.
|
||||
// It can be used for testing that restart Geth after the crash.
|
||||
type crashSnapshotTest struct {
|
||||
snapshotTestBasic
|
||||
}
|
||||
|
||||
func (snaptest *crashSnapshotTest) 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)
|
||||
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
db := chain.db
|
||||
db.Close()
|
||||
|
||||
// Start a new blockchain back up and see where the repair leads us
|
||||
newdb, err := rawdb.NewLevelDBDatabase(snaptest.datadir, 0, 0, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||
}
|
||||
defer newdb.Close()
|
||||
|
||||
// The interesting thing is: instead of starting the blockchain after
|
||||
// the crash, we do restart twice here: one after the crash and one
|
||||
// after the normal stop. It's used to ensure the broken snapshot
|
||||
// can be detected all the time.
|
||||
newchain, err := NewBlockChain(newdb, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
newchain.Stop()
|
||||
|
||||
newchain, err = NewBlockChain(newdb, 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)
|
||||
}
|
||||
|
||||
// gappedSnapshotTest is a test type used to test this scenario:
|
||||
// - have a complete snapshot
|
||||
// - restart without enabling the snapshot
|
||||
// - insert a few blocks
|
||||
// - restart with enabling the snapshot again
|
||||
type gappedSnapshotTest struct {
|
||||
snapshotTestBasic
|
||||
gapped int // Number of blocks to insert without enabling snapshot
|
||||
}
|
||||
|
||||
func (snaptest *gappedSnapshotTest) 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)
|
||||
|
||||
// Insert blocks without enabling snapshot if gapping is required.
|
||||
chain.Stop()
|
||||
gappedBlocks, _, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, 10, func(i int, b *BlockGen) {})
|
||||
|
||||
// Insert a few more blocks without enabling snapshot
|
||||
cacheConfig := &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
SnapshotLimit: 0,
|
||||
}
|
||||
newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
newchain.InsertChain(gappedBlocks)
|
||||
newchain.Stop()
|
||||
|
||||
// Restart the chain with enabling the snapshot
|
||||
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)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
// - restart again with enabling the snapshot
|
||||
// - crash
|
||||
type wipeCrashSnapshotTest struct {
|
||||
snapshotTestBasic
|
||||
newBlocks int
|
||||
}
|
||||
|
||||
func (snaptest *wipeCrashSnapshotTest) 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()
|
||||
|
||||
config := &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
SnapshotLimit: 0,
|
||||
}
|
||||
newchain, err := NewBlockChain(snaptest.db, config, 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)
|
||||
newchain.Stop()
|
||||
|
||||
// Restart the chain, the wiper should starts working
|
||||
config = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
SnapshotLimit: 256,
|
||||
}
|
||||
newchain, err = NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
// Simulate the blockchain 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)
|
||||
}
|
||||
snaptest.verify(t, newchain, blocks)
|
||||
}
|
||||
|
||||
// Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot
|
||||
// journal will be persisted correctly. In this case no snapshot recovery is
|
||||
// required.
|
||||
func TestRestartWithNewSnapshot(t *testing.T) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Snapshot: G
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
//
|
||||
// Expected head header : C8
|
||||
// Expected head block : C4
|
||||
// Expected snapshot disk : C4
|
||||
test := &snapshotTest{
|
||||
snapshotTestBasic{
|
||||
chainBlocks: 8,
|
||||
snapshotBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expHeadBlock: 4,
|
||||
expSnapshotBottom: 4, // Initial disk layer built from genesis
|
||||
},
|
||||
}
|
||||
test.test(t)
|
||||
test.teardown()
|
||||
}
|
||||
|
||||
// Tests a Geth was crashed and restarts with a broken snapshot. In this case the
|
||||
// chain head should be rewound to the point with available state. And also the
|
||||
// new head should must be lower than disk layer. But there is no committed point
|
||||
// so the chain should be rewound to genesis and the disk layer should be left
|
||||
// for recovery.
|
||||
func TestNoCommitCrashWithNewSnapshot(t *testing.T) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Snapshot: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
//
|
||||
// Expected head block : C4
|
||||
// Expected snapshot disk : C4
|
||||
test := &crashSnapshotTest{
|
||||
snapshotTestBasic{
|
||||
chainBlocks: 8,
|
||||
snapshotBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expHeadBlock: 4,
|
||||
expSnapshotBottom: 4, // Last committed disk layer, wait recovery
|
||||
},
|
||||
}
|
||||
test.test(t)
|
||||
test.teardown()
|
||||
}
|
||||
|
||||
// Tests a Geth was crashed and restarts with a broken snapshot. In this case the
|
||||
// chain head should be rewound to the point with available state. And also the
|
||||
// new head should must be lower than disk layer. But there is only a low committed
|
||||
// point so the chain should be rewound to committed point and the disk layer
|
||||
// should be left for recovery.
|
||||
func TestLowCommitCrashWithNewSnapshot(t *testing.T) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Snapshot: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
//
|
||||
// Expected head block : C4
|
||||
// Expected snapshot disk : C4
|
||||
test := &crashSnapshotTest{
|
||||
snapshotTestBasic{
|
||||
chainBlocks: 8,
|
||||
snapshotBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expHeadBlock: 4,
|
||||
expSnapshotBottom: 4, // Last committed disk layer, wait recovery
|
||||
},
|
||||
}
|
||||
test.test(t)
|
||||
test.teardown()
|
||||
}
|
||||
|
||||
// Tests a Geth was crashed and restarts with a broken snapshot. In this case
|
||||
// the chain head should be rewound to the point with available state. And also
|
||||
// the new head should must be lower than disk layer. But there is only a high
|
||||
// committed point so the chain should be rewound to genesis and the disk layer
|
||||
// should be left for recovery.
|
||||
func TestHighCommitCrashWithNewSnapshot(t *testing.T) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Snapshot: G, C4
|
||||
//
|
||||
// CRASH
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||
//
|
||||
// Expected head block : C4
|
||||
// Expected snapshot disk : C4
|
||||
test := &crashSnapshotTest{
|
||||
snapshotTestBasic{
|
||||
chainBlocks: 8,
|
||||
snapshotBlock: 4,
|
||||
expCanonicalBlocks: 8,
|
||||
expHeadBlock: 4,
|
||||
expSnapshotBottom: 4, // Last committed disk layer, wait recovery
|
||||
},
|
||||
}
|
||||
test.test(t)
|
||||
test.teardown()
|
||||
}
|
||||
|
||||
// Tests a Geth was running with snapshot enabled. Then restarts without
|
||||
// enabling snapshot and after that re-enable the snapshot again. In this
|
||||
// case the snapshot should be rebuilt with latest chain head.
|
||||
func TestGappedNewSnapshot(t *testing.T) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Snapshot: G
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10
|
||||
//
|
||||
// Expected head block : G
|
||||
// Expected snapshot disk : G
|
||||
test := &gappedSnapshotTest{
|
||||
snapshotTestBasic: snapshotTestBasic{
|
||||
chainBlocks: 8,
|
||||
snapshotBlock: 0,
|
||||
expCanonicalBlocks: 10,
|
||||
expHeadBlock: 0,
|
||||
expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD
|
||||
},
|
||||
gapped: 2,
|
||||
}
|
||||
test.test(t)
|
||||
test.teardown()
|
||||
}
|
||||
|
||||
// Tests the Geth was running with a complete snapshot and then imports a few
|
||||
// more new blocks on top without enabling the snapshot. After the restart,
|
||||
// crash happens. Check everything is ok after the restart.
|
||||
func TestRecoverSnapshotFromWipingCrash(t *testing.T) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
// Snapshot: G
|
||||
//
|
||||
// ------------------------------
|
||||
//
|
||||
// Expected in leveldb:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10
|
||||
//
|
||||
// Expected head block : C4
|
||||
// Expected snapshot disk : C4
|
||||
test := &wipeCrashSnapshotTest{
|
||||
snapshotTestBasic: snapshotTestBasic{
|
||||
chainBlocks: 8,
|
||||
snapshotBlock: 4,
|
||||
expCanonicalBlocks: 10,
|
||||
expHeadBlock: 4,
|
||||
expSnapshotBottom: 4,
|
||||
},
|
||||
newBlocks: 2,
|
||||
}
|
||||
test.test(t)
|
||||
test.teardown()
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func TestArchiveBlockChain(t *testing.T) {
|
||||
SnapshotLimit: 256,
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -51,7 +51,7 @@ func TestArchiveBlockChainSnapsDisabled(t *testing.T) {
|
||||
SnapshotLimit: 0, // Disable snapshots
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -76,7 +76,7 @@ func TestPruningBlockChain(t *testing.T) {
|
||||
SnapshotLimit: 256,
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -101,7 +101,7 @@ func TestPruningBlockChainSnapsDisabled(t *testing.T) {
|
||||
SnapshotLimit: 0, // Disable snapshots
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -132,7 +132,7 @@ func TestPruningBlockChainUngracefulShutdown(t *testing.T) {
|
||||
SnapshotLimit: 256,
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -164,7 +164,7 @@ func TestPruningBlockChainUngracefulShutdownSnapsDisabled(t *testing.T) {
|
||||
SnapshotLimit: 0, // Disable snapshots
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -198,7 +198,7 @@ func TestEnableSnapshots(t *testing.T) {
|
||||
SnapshotLimit: snapLimit, // Disable snapshots
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
@@ -232,7 +232,7 @@ func TestCorruptSnapshots(t *testing.T) {
|
||||
SnapshotLimit: 256, // Disable snapshots
|
||||
},
|
||||
chainConfig,
|
||||
dummy.NewEngine(),
|
||||
dummy.NewFaker(),
|
||||
vm.Config{},
|
||||
lastAcceptedHash,
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ func ExampleGenerateChain() {
|
||||
// each block and adds different features to gen based on the
|
||||
// block index.
|
||||
signer := types.HomesteadSigner{}
|
||||
chain, _, err := GenerateChain(gspec.Config, genesis, dummy.NewEngine(), db, 3, 10, func(i int, gen *BlockGen) {
|
||||
chain, _, err := GenerateChain(gspec.Config, genesis, dummy.NewFaker(), db, 3, 10, func(i int, gen *BlockGen) {
|
||||
switch i {
|
||||
case 0:
|
||||
// In block 1, addr1 sends addr2 some ether.
|
||||
@@ -82,7 +82,7 @@ func ExampleGenerateChain() {
|
||||
}
|
||||
|
||||
// Import the chain. This runs all block validation rules.
|
||||
blockchain, _ := NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewEngine(), vm.Config{}, common.Hash{})
|
||||
blockchain, _ := NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
defer blockchain.Stop()
|
||||
|
||||
if i, err := blockchain.InsertChain(chain); err != nil {
|
||||
|
||||
@@ -56,6 +56,10 @@ var (
|
||||
// next one expected based on the local chain.
|
||||
ErrNonceTooHigh = errors.New("nonce too high")
|
||||
|
||||
// ErrNonceMax is returned if the nonce of a transaction sender account has
|
||||
// maximum allowed value and would become invalid if incremented.
|
||||
ErrNonceMax = errors.New("nonce has max value")
|
||||
|
||||
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
|
||||
// by a transaction is higher than what's left in the block.
|
||||
ErrGasLimitReached = errors.New("gas limit reached")
|
||||
|
||||
+10
-2
@@ -323,12 +323,10 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
||||
if err := config.CheckConfigForkOrder(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty)
|
||||
rawdb.WriteBlock(db, block)
|
||||
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
|
||||
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
|
||||
rawdb.WriteHeadBlockHash(db, block.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(db, block.Hash())
|
||||
rawdb.WriteHeadHeaderHash(db, block.Hash())
|
||||
rawdb.WriteChainConfig(db, block.Hash(), config)
|
||||
return block, nil
|
||||
@@ -343,3 +341,13 @@ func (g *Genesis) MustCommit(db ethdb.Database) *types.Block {
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
// GenesisBlockForTesting creates and writes a block in which addr has the given wei balance.
|
||||
func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block {
|
||||
g := Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{addr: {Balance: balance}},
|
||||
BaseFee: big.NewInt(params.TestMaxBaseFee.Int64()),
|
||||
}
|
||||
return g.MustCommit(db)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// (c) 2019-2021, 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 2017 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 core
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func setupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
|
||||
conf, err := SetupGenesisBlock(db, genesis)
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
return conf, stored, err
|
||||
}
|
||||
|
||||
func TestGenesisBlockForTesting(t *testing.T) {
|
||||
genesisBlockForTestingHash := common.HexToHash("0x114ce61b50051f70768f982f7b59e82dd73b7bbd768e310c9d9f508d492e687b")
|
||||
block := GenesisBlockForTesting(rawdb.NewMemoryDatabase(), common.Address{1}, big.NewInt(1))
|
||||
if block.Hash() != genesisBlockForTestingHash {
|
||||
t.Errorf("wrong testing genesis hash, got %v, want %v", block.Hash(), genesisBlockForTestingHash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupGenesis(t *testing.T) {
|
||||
var (
|
||||
customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50")
|
||||
customg = Genesis{
|
||||
Config: ¶ms.ChainConfig{
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
SubnetEVMTimestamp: big.NewInt(time.Date(2021, time.July, 31, 14, 0, 0, 0, time.UTC).Unix()),
|
||||
},
|
||||
Alloc: GenesisAlloc{
|
||||
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
|
||||
},
|
||||
}
|
||||
oldcustomg = customg
|
||||
)
|
||||
oldcustomg.Config = ¶ms.ChainConfig{
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
SubnetEVMTimestamp: big.NewInt(time.Date(2021, time.March, 31, 14, 0, 0, 0, time.UTC).Unix()),
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error)
|
||||
wantConfig *params.ChainConfig
|
||||
wantHash common.Hash
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "genesis without ChainConfig",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
return setupGenesisBlock(db, new(Genesis))
|
||||
},
|
||||
wantErr: errGenesisNoConfig,
|
||||
wantConfig: nil,
|
||||
},
|
||||
{
|
||||
name: "no block in DB, genesis == nil",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
return setupGenesisBlock(db, nil)
|
||||
},
|
||||
wantErr: ErrNoGenesis,
|
||||
wantConfig: nil,
|
||||
},
|
||||
{
|
||||
name: "custom block in DB, genesis == nil",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
customg.MustCommit(db)
|
||||
return setupGenesisBlock(db, nil)
|
||||
},
|
||||
wantErr: ErrNoGenesis,
|
||||
wantHash: customghash,
|
||||
wantConfig: nil,
|
||||
},
|
||||
{
|
||||
name: "compatible config in DB",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
oldcustomg.MustCommit(db)
|
||||
return setupGenesisBlock(db, &customg)
|
||||
},
|
||||
wantHash: customghash,
|
||||
wantConfig: customg.Config,
|
||||
},
|
||||
{
|
||||
name: "incompatible config in DB",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
genesis := oldcustomg.MustCommit(db)
|
||||
bc, _ := NewBlockChain(db, DefaultCacheConfig, oldcustomg.Config, dummy.NewFullFaker(), vm.Config{}, common.Hash{})
|
||||
defer bc.Stop()
|
||||
blocks, _, _ := GenerateChain(oldcustomg.Config, genesis, dummy.NewFaker(), db, 4, 10, nil)
|
||||
bc.InsertChain(blocks)
|
||||
bc.CurrentBlock()
|
||||
return setupGenesisBlock(db, &customg)
|
||||
},
|
||||
wantHash: customghash,
|
||||
wantConfig: customg.Config,
|
||||
wantErr: ¶ms.ConfigCompatError{
|
||||
What: "SubnetEVM fork block",
|
||||
StoredConfig: big.NewInt(1617199200),
|
||||
NewConfig: big.NewInt(1627740000),
|
||||
RewindTo: 1617199199,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
config, hash, err := test.fn(db)
|
||||
// Check the return values.
|
||||
if !reflect.DeepEqual(err, test.wantErr) {
|
||||
spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true}
|
||||
t.Errorf("%s: returned error %#v, want %#v", test.name, spew.NewFormatter(err), spew.NewFormatter(test.wantErr))
|
||||
}
|
||||
if !reflect.DeepEqual(config, test.wantConfig) {
|
||||
t.Errorf("%s:\nreturned %v\nwant %v", test.name, config, test.wantConfig)
|
||||
}
|
||||
if hash != test.wantHash {
|
||||
t.Errorf("%s: returned hash %s, want %s", test.name, hash.Hex(), test.wantHash.Hex())
|
||||
} else if err == nil {
|
||||
// Check database content.
|
||||
stored := rawdb.ReadBlock(db, test.wantHash, 0)
|
||||
if stored.Hash() != test.wantHash {
|
||||
t.Errorf("%s: block in DB has hash %s, want %s", test.name, stored.Hash(), test.wantHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-19
@@ -55,9 +55,9 @@ const (
|
||||
// HeaderChain is responsible for maintaining the header chain including the
|
||||
// header query and updating.
|
||||
//
|
||||
// The components maintained by headerchain includes: (1) total difficult
|
||||
// (2) header (3) block hash -> number mapping (4) canonical number -> hash mapping
|
||||
// and (5) head header flag.
|
||||
// The components maintained by headerchain includes:
|
||||
// (1) header (2) block hash -> number mapping (3) canonical number -> hash mapping
|
||||
// and (4) head header flag.
|
||||
//
|
||||
// It is not thread safe either, the encapsulating chain structures should do
|
||||
// the necessary mutex locking/unlocking.
|
||||
@@ -131,22 +131,6 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
|
||||
return number
|
||||
}
|
||||
|
||||
// GetTd retrieves a block's total difficulty in the canonical chain from the
|
||||
// database by hash and number, caching it if found.
|
||||
func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
// Short circuit if the td's already in the cache, retrieve otherwise
|
||||
if cached, ok := hc.tdCache.Get(hash); ok {
|
||||
return cached.(*big.Int)
|
||||
}
|
||||
td := rawdb.ReadTd(hc.chainDb, hash, number)
|
||||
if td == nil {
|
||||
return nil
|
||||
}
|
||||
// Cache the found body for next time and return
|
||||
hc.tdCache.Add(hash, td)
|
||||
return td
|
||||
}
|
||||
|
||||
// GetHeader retrieves a block header from the database by hash and number,
|
||||
// caching it if found.
|
||||
func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// (c) 2019-2021, 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 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
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
func verifyUnbrokenCanonchain(bc *BlockChain) error {
|
||||
h := bc.hc.CurrentHeader()
|
||||
for {
|
||||
canonHash := rawdb.ReadCanonicalHash(bc.hc.chainDb, h.Number.Uint64())
|
||||
if exp := h.Hash(); canonHash != exp {
|
||||
return fmt.Errorf("Canon hash chain broken, block %d got %x, expected %x",
|
||||
h.Number, canonHash[:8], exp[:8])
|
||||
}
|
||||
if h.Number.Uint64() == 0 {
|
||||
break
|
||||
}
|
||||
h = bc.hc.GetHeader(h.ParentHash, h.Number.Uint64()-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testInsert(t *testing.T, bc *BlockChain, chain []*types.Block, wantErr error) {
|
||||
t.Helper()
|
||||
|
||||
_, err := bc.InsertChain(chain)
|
||||
// Always verify that the header chain is unbroken
|
||||
if err := verifyUnbrokenCanonchain(bc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !errors.Is(err, wantErr) {
|
||||
t.Fatalf("unexpected error from InsertHeaderChain: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks status reporting of InsertHeaderChain.
|
||||
func TestHeaderInsertion(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
genesis = (&Genesis{
|
||||
BaseFee: big.NewInt(params.TestInitialBaseFee),
|
||||
Config: params.TestChainConfig,
|
||||
}).MustCommit(db)
|
||||
)
|
||||
chain, err := NewBlockChain(db, DefaultCacheConfig, params.TestChainConfig, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// chain A: G->A1->A2...A128
|
||||
chainA, _, _ := GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(genesis.Header()), dummy.NewFaker(), db, 128, 10, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0: byte(10), 19: byte(i)})
|
||||
})
|
||||
// chain B: G->A1->B2...B128
|
||||
chainB, _, _ := GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(chainA[0].Header()), dummy.NewFaker(), db, 128, 10, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0: byte(10), 19: byte(i)})
|
||||
})
|
||||
log.Root().SetHandler(log.StdoutHandler)
|
||||
|
||||
// Inserting 64 headers on an empty chain
|
||||
testInsert(t, chain, chainA[:64], nil)
|
||||
|
||||
// Inserting 64 identical headers
|
||||
testInsert(t, chain, chainA[:64], nil)
|
||||
|
||||
// Inserting the same some old, some new headers
|
||||
testInsert(t, chain, chainA[32:96], nil)
|
||||
|
||||
// Inserting side blocks, but not overtaking the canon chain
|
||||
testInsert(t, chain, chainB[0:32], nil)
|
||||
|
||||
// Inserting more side blocks, but we don't have the parent
|
||||
testInsert(t, chain, chainB[34:36], consensus.ErrUnknownAncestor)
|
||||
|
||||
// Inserting more sideblocks, overtaking the canon chain
|
||||
testInsert(t, chain, chainB[32:97], nil)
|
||||
|
||||
// Inserting more A-headers, taking back the canonicality
|
||||
testInsert(t, chain, chainA[90:100], nil)
|
||||
|
||||
// And B becomes canon again
|
||||
testInsert(t, chain, chainB[97:107], nil)
|
||||
|
||||
// And B becomes even longer
|
||||
testInsert(t, chain, chainB[107:128], nil)
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
@@ -202,77 +201,6 @@ func WriteHeadBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeadFastBlockHash retrieves the hash of the current fast-sync head block.
|
||||
func ReadHeadFastBlockHash(db ethdb.KeyValueReader) common.Hash {
|
||||
data, _ := db.Get(headFastBlockKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// WriteHeadFastBlockHash stores the hash of the current fast-sync head block.
|
||||
func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
if err := db.Put(headFastBlockKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last fast block's hash", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFastTrieProgress retrieves the number of tries nodes fast synced to allow
|
||||
// reporting correct numbers across restarts.
|
||||
func ReadFastTrieProgress(db ethdb.KeyValueReader) uint64 {
|
||||
data, _ := db.Get(fastTrieProgressKey)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
return new(big.Int).SetBytes(data).Uint64()
|
||||
}
|
||||
|
||||
// WriteFastTrieProgress stores the fast sync trie process counter to support
|
||||
// retrieving it across restarts.
|
||||
func WriteFastTrieProgress(db ethdb.KeyValueWriter, count uint64) {
|
||||
if err := db.Put(fastTrieProgressKey, new(big.Int).SetUint64(count).Bytes()); err != nil {
|
||||
log.Crit("Failed to store fast sync trie progress", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTxIndexTail retrieves the number of oldest indexed block
|
||||
// whose transaction indices has been indexed. If the corresponding entry
|
||||
// is non-existent in database it means the indexing has been finished.
|
||||
func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(txIndexTailKey)
|
||||
if len(data) != 8 {
|
||||
return nil
|
||||
}
|
||||
number := binary.BigEndian.Uint64(data)
|
||||
return &number
|
||||
}
|
||||
|
||||
// WriteTxIndexTail stores the number of oldest indexed block
|
||||
// into database.
|
||||
func WriteTxIndexTail(db ethdb.KeyValueWriter, number uint64) {
|
||||
if err := db.Put(txIndexTailKey, encodeBlockNumber(number)); err != nil {
|
||||
log.Crit("Failed to store the transaction index tail", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFastTxLookupLimit retrieves the tx lookup limit used in fast sync.
|
||||
func ReadFastTxLookupLimit(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(fastTxLookupLimitKey)
|
||||
if len(data) != 8 {
|
||||
return nil
|
||||
}
|
||||
number := binary.BigEndian.Uint64(data)
|
||||
return &number
|
||||
}
|
||||
|
||||
// WriteFastTxLookupLimit stores the txlookup limit used in fast sync into database.
|
||||
func WriteFastTxLookupLimit(db ethdb.KeyValueWriter, number uint64) {
|
||||
if err := db.Put(fastTxLookupLimitKey, encodeBlockNumber(number)); err != nil {
|
||||
log.Crit("Failed to store transaction lookup limit for fast sync", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeaderRLP retrieves a block header in its raw RLP database encoding.
|
||||
func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
// Then try to look up the data in leveldb.
|
||||
@@ -408,48 +336,6 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTdRLP retrieves a block's total difficulty corresponding to the hash in RLP encoding.
|
||||
func ReadTdRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
// Then try to look up the data in leveldb.
|
||||
data, _ := db.Get(headerTDKey(number, hash))
|
||||
if len(data) > 0 {
|
||||
return data
|
||||
}
|
||||
return nil // Can't find the data anywhere.
|
||||
}
|
||||
|
||||
// ReadTd retrieves a block's total difficulty corresponding to the hash.
|
||||
func ReadTd(db ethdb.Reader, hash common.Hash, number uint64) *big.Int {
|
||||
data := ReadTdRLP(db, hash, number)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
td := new(big.Int)
|
||||
if err := rlp.Decode(bytes.NewReader(data), td); err != nil {
|
||||
log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return td
|
||||
}
|
||||
|
||||
// WriteTd stores the total difficulty of a block into the database.
|
||||
func WriteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64, td *big.Int) {
|
||||
data, err := rlp.EncodeToBytes(td)
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode block total difficulty", "err", err)
|
||||
}
|
||||
if err := db.Put(headerTDKey(number, hash), data); err != nil {
|
||||
log.Crit("Failed to store block total difficulty", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTd removes all block total difficulty data associated with a hash.
|
||||
func DeleteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||
if err := db.Delete(headerTDKey(number, hash)); err != nil {
|
||||
log.Crit("Failed to delete block total difficulty", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// HasReceipts verifies the existence of all the transaction receipts belonging
|
||||
// to a block.
|
||||
func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
||||
@@ -653,7 +539,6 @@ func DeleteBlock(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||
DeleteReceipts(db, hash, number)
|
||||
DeleteHeader(db, hash, number)
|
||||
DeleteBody(db, hash, number)
|
||||
DeleteTd(db, hash, number)
|
||||
}
|
||||
|
||||
// DeleteBlockWithoutNumber removes all block data associated with a hash, except
|
||||
@@ -662,7 +547,6 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number
|
||||
DeleteReceipts(db, hash, number)
|
||||
deleteHeaderWithoutNumber(db, hash, number)
|
||||
DeleteBody(db, hash, number)
|
||||
DeleteTd(db, hash, number)
|
||||
}
|
||||
|
||||
// FindCommonAncestor returns the last common ancestor of two block headers
|
||||
|
||||
@@ -187,29 +187,6 @@ func TestPartialBlockStorage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests block total difficulty storage and retrieval operations.
|
||||
func TestTdStorage(t *testing.T) {
|
||||
db := NewMemoryDatabase()
|
||||
|
||||
// Create a test TD to move around the database and make sure it's really new
|
||||
hash, td := common.Hash{}, big.NewInt(314)
|
||||
if entry := ReadTd(db, hash, 0); entry != nil {
|
||||
t.Fatalf("Non existent TD returned: %v", entry)
|
||||
}
|
||||
// Write and verify the TD in the database
|
||||
WriteTd(db, hash, 0, td)
|
||||
if entry := ReadTd(db, hash, 0); entry == nil {
|
||||
t.Fatalf("Stored TD not found")
|
||||
} else if entry.Cmp(td) != 0 {
|
||||
t.Fatalf("Retrieved TD mismatch: have %v, want %v", entry, td)
|
||||
}
|
||||
// Delete the TD and verify the execution
|
||||
DeleteTd(db, hash, 0)
|
||||
if entry := ReadTd(db, hash, 0); entry != nil {
|
||||
t.Fatalf("Deleted TD returned: %v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that canonical numbers can be mapped to hashes and retrieved.
|
||||
func TestCanonicalMappingStorage(t *testing.T) {
|
||||
db := NewMemoryDatabase()
|
||||
@@ -239,7 +216,6 @@ func TestHeadStorage(t *testing.T) {
|
||||
|
||||
blockHead := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block header")})
|
||||
blockFull := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block full")})
|
||||
blockFast := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block fast")})
|
||||
|
||||
// Check that no head entries are in a pristine database
|
||||
if entry := ReadHeadHeaderHash(db); entry != (common.Hash{}) {
|
||||
@@ -248,13 +224,9 @@ func TestHeadStorage(t *testing.T) {
|
||||
if entry := ReadHeadBlockHash(db); entry != (common.Hash{}) {
|
||||
t.Fatalf("Non head block entry returned: %v", entry)
|
||||
}
|
||||
if entry := ReadHeadFastBlockHash(db); entry != (common.Hash{}) {
|
||||
t.Fatalf("Non fast head block entry returned: %v", entry)
|
||||
}
|
||||
// Assign separate entries for the head header and block
|
||||
WriteHeadHeaderHash(db, blockHead.Hash())
|
||||
WriteHeadBlockHash(db, blockFull.Hash())
|
||||
WriteHeadFastBlockHash(db, blockFast.Hash())
|
||||
|
||||
// Check that both heads are present, and different (i.e. two heads maintained)
|
||||
if entry := ReadHeadHeaderHash(db); entry != blockHead.Hash() {
|
||||
@@ -263,9 +235,6 @@ func TestHeadStorage(t *testing.T) {
|
||||
if entry := ReadHeadBlockHash(db); entry != blockFull.Hash() {
|
||||
t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash())
|
||||
}
|
||||
if entry := ReadHeadFastBlockHash(db); entry != blockFast.Hash() {
|
||||
t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that receipts associated with a single block can be stored and retrieved.
|
||||
@@ -393,7 +362,6 @@ func TestCanonicalHashIteration(t *testing.T) {
|
||||
// Fill database with testing data.
|
||||
for i := uint64(1); i <= 8; i++ {
|
||||
WriteCanonicalHash(db, common.Hash{}, i)
|
||||
WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data
|
||||
}
|
||||
for i, c := range cases {
|
||||
numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit)
|
||||
|
||||
@@ -28,6 +28,7 @@ package rawdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
@@ -89,3 +90,83 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
|
||||
log.Crit("Failed to store chain config", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the
|
||||
// database
|
||||
type crashList struct {
|
||||
Discarded uint64 // how many ucs have we deleted
|
||||
Recent []uint64 // unix timestamps of 10 latest unclean shutdowns
|
||||
}
|
||||
|
||||
const crashesToKeep = 10
|
||||
|
||||
// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns
|
||||
// the previous data
|
||||
// - a list of timestamps
|
||||
// - a count of how many old unclean-shutdowns have been discarded
|
||||
func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) {
|
||||
var uncleanShutdowns crashList
|
||||
// Read old data
|
||||
if data, err := db.Get(uncleanShutdownKey); err != nil {
|
||||
log.Warn("Error reading unclean shutdown markers", "error", err)
|
||||
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
discarded := uncleanShutdowns.Discarded
|
||||
previous := make([]uint64, len(uncleanShutdowns.Recent))
|
||||
copy(previous, uncleanShutdowns.Recent)
|
||||
// Add a new (but cap it)
|
||||
uncleanShutdowns.Recent = append(uncleanShutdowns.Recent, uint64(time.Now().Unix()))
|
||||
if count := len(uncleanShutdowns.Recent); count > crashesToKeep+1 {
|
||||
numDel := count - (crashesToKeep + 1)
|
||||
uncleanShutdowns.Recent = uncleanShutdowns.Recent[numDel:]
|
||||
uncleanShutdowns.Discarded += uint64(numDel)
|
||||
}
|
||||
// And save it again
|
||||
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
|
||||
if err := db.Put(uncleanShutdownKey, data); err != nil {
|
||||
log.Warn("Failed to write unclean-shutdown marker", "err", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return previous, discarded, nil
|
||||
}
|
||||
|
||||
// PopUncleanShutdownMarker removes the last unclean shutdown marker
|
||||
func PopUncleanShutdownMarker(db ethdb.KeyValueStore) {
|
||||
var uncleanShutdowns crashList
|
||||
// Read old data
|
||||
if data, err := db.Get(uncleanShutdownKey); err != nil {
|
||||
log.Warn("Error reading unclean shutdown markers", "error", err)
|
||||
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
|
||||
log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen
|
||||
}
|
||||
if l := len(uncleanShutdowns.Recent); l > 0 {
|
||||
uncleanShutdowns.Recent = uncleanShutdowns.Recent[:l-1]
|
||||
}
|
||||
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
|
||||
if err := db.Put(uncleanShutdownKey, data); err != nil {
|
||||
log.Warn("Failed to clear unclean-shutdown marker", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUncleanShutdownMarker updates the last marker's timestamp to now.
|
||||
func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) {
|
||||
var uncleanShutdowns crashList
|
||||
// Read old data
|
||||
if data, err := db.Get(uncleanShutdownKey); err != nil {
|
||||
log.Warn("Error reading unclean shutdown markers", "error", err)
|
||||
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
|
||||
log.Warn("Error decoding unclean shutdown markers", "error", err)
|
||||
}
|
||||
// This shouldn't happen because we push a marker on Backend instantiation
|
||||
count := len(uncleanShutdowns.Recent)
|
||||
if count == 0 {
|
||||
log.Warn("No unclean shutdown marker to update")
|
||||
return
|
||||
}
|
||||
uncleanShutdowns.Recent[count-1] = uint64(time.Now().Unix())
|
||||
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
|
||||
if err := db.Put(uncleanShutdownKey, data); err != nil {
|
||||
log.Warn("Failed to write unclean-shutdown marker", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -158,56 +156,3 @@ func DeleteSnapshotGenerator(db ethdb.KeyValueWriter) {
|
||||
log.Crit("Failed to remove snapshot generator", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSnapshotRecoveryNumber retrieves the block number of the last persisted
|
||||
// snapshot layer.
|
||||
func ReadSnapshotRecoveryNumber(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(snapshotRecoveryKey)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(data) != 8 {
|
||||
return nil
|
||||
}
|
||||
number := binary.BigEndian.Uint64(data)
|
||||
return &number
|
||||
}
|
||||
|
||||
// WriteSnapshotRecoveryNumber stores the block number of the last persisted
|
||||
// snapshot layer.
|
||||
func WriteSnapshotRecoveryNumber(db ethdb.KeyValueWriter, number uint64) {
|
||||
var buf [8]byte
|
||||
binary.BigEndian.PutUint64(buf[:], number)
|
||||
if err := db.Put(snapshotRecoveryKey, buf[:]); err != nil {
|
||||
log.Crit("Failed to store snapshot recovery number", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSnapshotRecoveryNumber deletes the block number of the last persisted
|
||||
// snapshot layer.
|
||||
func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(snapshotRecoveryKey); err != nil {
|
||||
log.Crit("Failed to remove snapshot recovery number", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSnapshotSyncStatus retrieves the serialized sync status saved at shutdown.
|
||||
func ReadSnapshotSyncStatus(db ethdb.KeyValueReader) []byte {
|
||||
data, _ := db.Get(snapshotSyncStatusKey)
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteSnapshotSyncStatus stores the serialized sync status to save at shutdown.
|
||||
func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) {
|
||||
if err := db.Put(snapshotSyncStatusKey, status); err != nil {
|
||||
log.Crit("Failed to store snapshot sync status", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSnapshotSyncStatus deletes the serialized sync status saved at the last
|
||||
// shutdown
|
||||
func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(snapshotSyncStatusKey); err != nil {
|
||||
log.Crit("Failed to remove snapshot sync status", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -33,6 +33,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/ethdb/leveldb"
|
||||
"github.com/ava-labs/subnet-evm/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -63,6 +64,16 @@ func NewMemoryDatabaseWithCap(size int) ethdb.Database {
|
||||
return NewDatabase(memorydb.NewWithCap(size))
|
||||
}
|
||||
|
||||
// NewLevelDBDatabase creates a persistent key-value database without a freezer
|
||||
// moving immutable chain segments into cold storage.
|
||||
func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
db, err := leveldb.New(file, cache, handles, namespace, readonly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDatabase(db), nil
|
||||
}
|
||||
|
||||
type counter uint64
|
||||
|
||||
func (c counter) String() string {
|
||||
@@ -108,7 +119,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
headers stat
|
||||
bodies stat
|
||||
receipts stat
|
||||
tds stat
|
||||
numHashPairings stat
|
||||
hashNumPairings stat
|
||||
tries stat
|
||||
@@ -145,8 +155,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
bodies.Add(size)
|
||||
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
|
||||
receipts.Add(size)
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
|
||||
tds.Add(size)
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
|
||||
numHashPairings.Add(size)
|
||||
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
|
||||
@@ -182,10 +190,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
default:
|
||||
var accounted bool
|
||||
for _, meta := range [][]byte{
|
||||
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey,
|
||||
fastTrieProgressKey, snapshotDisabledKey, snapshotRootKey, snapshotJournalKey,
|
||||
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
||||
uncleanShutdownKey, badBlockKey,
|
||||
databaseVersionKey, headHeaderKey, headBlockKey,
|
||||
snapshotRootKey, snapshotGeneratorKey, uncleanShutdownKey,
|
||||
} {
|
||||
if bytes.Equal(key, meta) {
|
||||
metadata.Add(size)
|
||||
@@ -208,7 +214,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
{"Key-Value store", "Headers", headers.Size(), headers.Count()},
|
||||
{"Key-Value store", "Bodies", bodies.Size(), bodies.Count()},
|
||||
{"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()},
|
||||
{"Key-Value store", "Difficulties", tds.Size(), tds.Count()},
|
||||
{"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()},
|
||||
{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
|
||||
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
|
||||
|
||||
@@ -46,51 +46,20 @@ var (
|
||||
// headBlockKey tracks the latest known full block's hash.
|
||||
headBlockKey = []byte("LastBlock")
|
||||
|
||||
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
|
||||
headFastBlockKey = []byte("LastFast")
|
||||
|
||||
// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
|
||||
lastPivotKey = []byte("LastPivot")
|
||||
|
||||
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
|
||||
fastTrieProgressKey = []byte("TrieSync")
|
||||
|
||||
// snapshotDisabledKey flags that the snapshot should not be maintained due to initial sync.
|
||||
snapshotDisabledKey = []byte("SnapshotDisabled")
|
||||
|
||||
// snapshotRootKey tracks the hash of the last snapshot.
|
||||
snapshotRootKey = []byte("SnapshotRoot")
|
||||
|
||||
// snapshotBlockHashKey tracks the block hash of the last snapshot.
|
||||
snapshotBlockHashKey = []byte("SnapshotBlockHash")
|
||||
|
||||
// snapshotJournalKey tracks the in-memory diff layers across restarts.
|
||||
snapshotJournalKey = []byte("SnapshotJournal")
|
||||
|
||||
// snapshotGeneratorKey tracks the snapshot generation marker across restarts.
|
||||
snapshotGeneratorKey = []byte("SnapshotGenerator")
|
||||
|
||||
// snapshotRecoveryKey tracks the snapshot recovery marker across restarts.
|
||||
snapshotRecoveryKey = []byte("SnapshotRecovery")
|
||||
|
||||
// snapshotSyncStatusKey tracks the snapshot sync status across restarts.
|
||||
snapshotSyncStatusKey = []byte("SnapshotSyncStatus")
|
||||
|
||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||
txIndexTailKey = []byte("TransactionIndexTail")
|
||||
|
||||
// fastTxLookupLimitKey tracks the transaction lookup limit during fast sync.
|
||||
fastTxLookupLimitKey = []byte("FastTransactionLookupLimit")
|
||||
|
||||
// badBlockKey tracks the list of bad blocks seen by local
|
||||
badBlockKey = []byte("InvalidBlock")
|
||||
|
||||
// uncleanShutdownKey tracks the list of local crashes
|
||||
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
|
||||
|
||||
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
|
||||
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
|
||||
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td
|
||||
headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash
|
||||
headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian)
|
||||
|
||||
@@ -138,11 +107,6 @@ func headerKey(number uint64, hash common.Hash) []byte {
|
||||
return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
}
|
||||
|
||||
// headerTDKey = headerPrefix + num (uint64 big endian) + hash + headerTDSuffix
|
||||
func headerTDKey(number uint64, hash common.Hash) []byte {
|
||||
return append(headerKey(number, hash), headerTDSuffix...)
|
||||
}
|
||||
|
||||
// headerHashKey = headerPrefix + num (uint64 big endian) + headerHashSuffix
|
||||
func headerHashKey(number uint64) []byte {
|
||||
return append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...)
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
// (c) 2019-2021, 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 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
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
||||
var (
|
||||
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
// Generate a canonical chain to act as the main dataset
|
||||
engine = dummy.NewFaker()
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
// A sender who makes transactions, has some funds
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(50000 * 225000000000 * 200)
|
||||
gspec = &Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{address: {Balance: funds}},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
)
|
||||
|
||||
// We need to generate as many blocks +1 as uncles
|
||||
blocks, _, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, uncles+1, 10,
|
||||
func(n int, b *BlockGen) {
|
||||
if n == uncles {
|
||||
// Add transactions and stuff on the last block
|
||||
for i := 0; i < transactions; i++ {
|
||||
tx, _ := types.SignTx(types.NewTransaction(uint64(i), aa,
|
||||
big.NewInt(0), 50000, b.header.BaseFee, make([]byte, dataSize)), types.LatestSigner(params.TestChainConfig), key)
|
||||
b.AddTx(tx)
|
||||
}
|
||||
for i := 0; i < uncles; i++ {
|
||||
b.AddUncle(&types.Header{ParentHash: b.PrevBlock(n - 1 - i).Hash(), Number: big.NewInt(int64(n - i))})
|
||||
}
|
||||
}
|
||||
})
|
||||
block := blocks[len(blocks)-1]
|
||||
return block
|
||||
}
|
||||
|
||||
// TestRlpIterator tests that individual transactions can be picked out
|
||||
// from blocks without full unmarshalling/marshalling
|
||||
func TestRlpIterator(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
txs int
|
||||
uncles int
|
||||
datasize int
|
||||
}{
|
||||
{0, 0, 0},
|
||||
{0, 2, 0},
|
||||
{10, 0, 0},
|
||||
{10, 2, 0},
|
||||
{10, 2, 50},
|
||||
} {
|
||||
testRlpIterator(t, tt.txs, tt.uncles, tt.datasize)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
it, err := rlp.NewListIterator(bodyRlp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check that txs exist
|
||||
if !it.Next() {
|
||||
t.Fatal("expected two elems, got zero")
|
||||
}
|
||||
txdata := it.Value()
|
||||
// Check that uncles exist
|
||||
if !it.Next() {
|
||||
t.Fatal("expected two elems, got one")
|
||||
}
|
||||
// No more after that
|
||||
if it.Next() {
|
||||
t.Fatal("expected only two elems, got more")
|
||||
}
|
||||
txIt, err := rlp.NewListIterator(txdata)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var gotHashes []common.Hash
|
||||
var expHashes []common.Hash
|
||||
for txIt.Next() {
|
||||
gotHashes = append(gotHashes, crypto.Keccak256Hash(txIt.Value()))
|
||||
}
|
||||
|
||||
var expBody types.Body
|
||||
err = rlp.DecodeBytes(bodyRlp, &expBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tx := range expBody.Transactions {
|
||||
expHashes = append(expHashes, tx.Hash())
|
||||
}
|
||||
if gotLen, expLen := len(gotHashes), len(expHashes); gotLen != expLen {
|
||||
t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, expLen)
|
||||
}
|
||||
// also sanity check against input
|
||||
if gotLen := len(gotHashes); gotLen != txs {
|
||||
t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, txs)
|
||||
}
|
||||
for i, got := range gotHashes {
|
||||
if exp := expHashes[i]; got != exp {
|
||||
t.Errorf("testcase %v: hash wrong, got %x, exp %x", desc, got, exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkHashing compares the speeds of hashing a rlp raw data directly
|
||||
// without the unmarshalling/marshalling step
|
||||
func BenchmarkHashing(b *testing.B) {
|
||||
// Make a pretty fat block
|
||||
var (
|
||||
bodyRlp []byte
|
||||
blockRlp []byte
|
||||
)
|
||||
{
|
||||
block := getBlock(200, 2, 50)
|
||||
bodyRlp, _ = rlp.EncodeToBytes(block.Body())
|
||||
blockRlp, _ = rlp.EncodeToBytes(block)
|
||||
}
|
||||
var got common.Hash
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
b.Run("iteratorhashing", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var hash common.Hash
|
||||
it, err := rlp.NewListIterator(bodyRlp)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
it.Next()
|
||||
txs := it.Value()
|
||||
txIt, err := rlp.NewListIterator(txs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for txIt.Next() {
|
||||
hasher.Reset()
|
||||
hasher.Write(txIt.Value())
|
||||
hasher.Sum(hash[:0])
|
||||
got = hash
|
||||
}
|
||||
}
|
||||
})
|
||||
var exp common.Hash
|
||||
b.Run("fullbodyhashing", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var body types.Body
|
||||
rlp.DecodeBytes(bodyRlp, &body)
|
||||
for _, tx := range body.Transactions {
|
||||
exp = tx.Hash()
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run("fullblockhashing", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var block types.Block
|
||||
rlp.DecodeBytes(blockRlp, &block)
|
||||
for _, tx := range block.Transactions() {
|
||||
tx.Hash()
|
||||
}
|
||||
}
|
||||
})
|
||||
if got != exp {
|
||||
b.Fatalf("hash wrong, got %x exp %x", got, exp)
|
||||
}
|
||||
}
|
||||
@@ -737,10 +737,6 @@ func (t *Tree) Rebuild(blockHash, root common.Hash) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
// Firstly delete any recovery flag in the database. Because now we are
|
||||
// building a brand new snapshot. Also reenable the snapshot feature.
|
||||
rawdb.DeleteSnapshotRecoveryNumber(t.diskdb)
|
||||
|
||||
// Track whether there's a wipe currently running and keep it alive if so
|
||||
var wiper chan struct{}
|
||||
|
||||
|
||||
@@ -40,57 +40,35 @@ import (
|
||||
func TestWipe(t *testing.T) {
|
||||
// Create a database with some random snapshot data
|
||||
db := memorydb.New()
|
||||
|
||||
for i := 0; i < 128; i++ {
|
||||
account := randomHash()
|
||||
rawdb.WriteAccountSnapshot(db, account, randomHash().Bytes())
|
||||
for j := 0; j < 1024; j++ {
|
||||
rawdb.WriteStorageSnapshot(db, account, randomHash(), randomHash().Bytes())
|
||||
}
|
||||
rawdb.WriteAccountSnapshot(db, randomHash(), randomHash().Bytes())
|
||||
}
|
||||
rawdb.WriteSnapshotBlockHash(db, randomHash())
|
||||
rawdb.WriteSnapshotRoot(db, randomHash())
|
||||
|
||||
// Add some random non-snapshot data too to make wiping harder
|
||||
for i := 0; i < 65536; i++ {
|
||||
// Generate a key that's the wrong length for a state snapshot item
|
||||
var keysize int
|
||||
for keysize == 0 || keysize == 32 || keysize == 64 {
|
||||
keysize = 8 + rand.Intn(64) // +8 to ensure we will "never" randomize duplicates
|
||||
}
|
||||
// Randomize the suffix, dedup and inject it under the snapshot namespace
|
||||
keysuffix := make([]byte, keysize)
|
||||
for i := 0; i < 500; i++ {
|
||||
// Generate keys with wrong length for a state snapshot item
|
||||
keysuffix := make([]byte, 31)
|
||||
rand.Read(keysuffix)
|
||||
|
||||
if rand.Int31n(2) == 0 {
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
} else {
|
||||
db.Put(append(rawdb.SnapshotStoragePrefix, keysuffix...), randomHash().Bytes())
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
keysuffix = make([]byte, 33)
|
||||
rand.Read(keysuffix)
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
}
|
||||
count := func() (items int) {
|
||||
it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
if len(it.Key()) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
items++
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
// Sanity check that all the keys are present
|
||||
var items int
|
||||
|
||||
it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
items++
|
||||
}
|
||||
}
|
||||
it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength {
|
||||
items++
|
||||
}
|
||||
}
|
||||
if items != 128+128*1024 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128+128*1024)
|
||||
if items := count(); items != 128 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128)
|
||||
}
|
||||
if hash := rawdb.ReadSnapshotBlockHash(db); hash == (common.Hash{}) {
|
||||
t.Errorf("snapshot block hash marker mismatch: have %#x, want <not-nil>", hash)
|
||||
@@ -102,40 +80,24 @@ func TestWipe(t *testing.T) {
|
||||
<-wipeSnapshot(db, true)
|
||||
|
||||
// Iterate over the database end ensure no snapshot information remains
|
||||
it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
t.Errorf("snapshot entry remained after wipe: %x", key)
|
||||
}
|
||||
if items := count(); items != 0 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 0)
|
||||
}
|
||||
it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
|
||||
// Iterate over the database and ensure miscellaneous items are present
|
||||
items := 0
|
||||
it := db.NewIterator(nil, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength {
|
||||
t.Errorf("snapshot entry remained after wipe: %x", key)
|
||||
}
|
||||
items++
|
||||
}
|
||||
if items != 1000 {
|
||||
t.Fatalf("misc item count mismatch: have %d, want %d", items, 1000)
|
||||
}
|
||||
|
||||
if hash := rawdb.ReadSnapshotBlockHash(db); hash != (common.Hash{}) {
|
||||
t.Errorf("snapshot block hash marker remained after wipe: %#x", hash)
|
||||
}
|
||||
if hash := rawdb.ReadSnapshotRoot(db); hash != (common.Hash{}) {
|
||||
t.Errorf("snapshot block root marker remained after wipe: %#x", hash)
|
||||
}
|
||||
// Iterate over the database and ensure miscellaneous items are present
|
||||
items = 0
|
||||
|
||||
it = db.NewIterator(nil, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
items++
|
||||
}
|
||||
if items != 65536 {
|
||||
t.Fatalf("misc item count mismatch: have %d, want %d", items, 65536)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
// (c) 2019-2021, 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 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
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/consensus"
|
||||
"github.com/ava-labs/subnet-evm/consensus/dummy"
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// TestStateProcessorErrors tests the output from the 'core' errors
|
||||
// as defined in core/error.go. These errors are generated when the
|
||||
// blockchain imports bad blocks, meaning blocks which have valid headers but
|
||||
// contain invalid transactions
|
||||
func TestStateProcessorErrors(t *testing.T) {
|
||||
var (
|
||||
config = ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
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),
|
||||
SubnetEVMTimestamp: big.NewInt(0),
|
||||
FeeConfig: params.DefaultFeeConfig,
|
||||
}
|
||||
signer = types.LatestSigner(config)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
)
|
||||
makeTx := func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey)
|
||||
return tx
|
||||
}
|
||||
mkDynamicTx := func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Gas: gasLimit,
|
||||
To: &to,
|
||||
Value: big.NewInt(0),
|
||||
}), signer, testKey)
|
||||
return tx
|
||||
}
|
||||
config.FeeConfig.MinBaseFee = params.TestMaxBaseFee
|
||||
{ // Tests against a 'recent' chain definition
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{
|
||||
Config: config,
|
||||
Alloc: GenesisAlloc{
|
||||
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
|
||||
Balance: big.NewInt(2000000000000000000), // 2 ether
|
||||
Nonce: 0,
|
||||
},
|
||||
},
|
||||
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
|
||||
tooBigNumber := new(big.Int).Set(bigNumber)
|
||||
tooBigNumber.Add(tooBigNumber, common.Big1)
|
||||
for i, tt := range []struct {
|
||||
txs []*types.Transaction
|
||||
want string
|
||||
}{
|
||||
{ // ErrNonceTooLow
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 1 [0x734d821c990099c6ae42d78072aadd3931c35328cf03ef4cf5b2a4ac9c398522]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1",
|
||||
},
|
||||
{ // ErrNonceTooHigh
|
||||
txs: []*types.Transaction{
|
||||
makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x0df36254cfbef8ed6961b38fc68aecc777177166144c8a56bc8919e23a559bf4]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0",
|
||||
},
|
||||
{ // ErrGasLimitReached
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), 8000001, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0xfbe38b817aaa760c2766b56c019fcdba506560a28fd41c69ae96bdaa4569e317]: gas limit reached",
|
||||
},
|
||||
{ // ErrInsufficientFundsForTransfer
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(2000000000000000000), params.TxGas, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0xae1601ef55b676ebb824ee7e16a0d14af725b7f9cf5ec79e21f14833c26b5b35]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 2000000000000000000 want 2004725000000000000",
|
||||
},
|
||||
{ // ErrInsufficientFunds
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x4a69690c4b0cd85e64d0d9ea06302455b01e10a83db964d60281739752003440]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 2000000000000000000 want 18900000000000000000000",
|
||||
},
|
||||
// ErrGasUintOverflow
|
||||
// One missing 'core' error is ErrGasUintOverflow: "gas uint64 overflow",
|
||||
// In order to trigger that one, we'd have to allocate a _huge_ chunk of data, such that the
|
||||
// multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment
|
||||
{ // ErrIntrinsicGas
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x2fc3e3b5cc26917d413e26983fe189475f47d4f0757e32aaa5561fcb9c9dc432]: intrinsic gas too low: have 20000, want 21000",
|
||||
},
|
||||
{ // ErrGasLimitReached
|
||||
txs: []*types.Transaction{
|
||||
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas*381, big.NewInt(225000000000), nil),
|
||||
},
|
||||
want: "could not apply tx 0 [0x9ee548e001369418ae53aaa11b5d823f081cc7fa9c9a7ee71a978ae17a2aece0]: gas limit reached",
|
||||
},
|
||||
{ // ErrFeeCapTooLow
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(0), big.NewInt(0)),
|
||||
},
|
||||
want: "could not apply tx 0 [0xc4ab868fef0c82ae0387b742aee87907f2d0fc528fc6ea0a021459fb0fc4a4a8]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 0 baseFee: 225000000000",
|
||||
},
|
||||
{ // ErrTipVeryHigh
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas, tooBigNumber, big.NewInt(1)),
|
||||
},
|
||||
want: "could not apply tx 0 [0x15b8391b9981f266b32f3ab7da564bbeb3d6c21628364ea9b32a21139f89f712]: max priority fee per gas higher than 2^256-1: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxPriorityFeePerGas bit length: 257",
|
||||
},
|
||||
{ // ErrFeeCapVeryHigh
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(1), tooBigNumber),
|
||||
},
|
||||
want: "could not apply tx 0 [0x48bc299b83fdb345c57478f239e89814bb3063eb4e4b49f3b6057a69255c16bd]: max fee per gas higher than 2^256-1: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas bit length: 257",
|
||||
},
|
||||
{ // ErrTipAboveFeeCap
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(2), big.NewInt(1)),
|
||||
},
|
||||
want: "could not apply tx 0 [0xf987a31ff0c71895780a7612f965a0c8b056deb54e020bb44fa478092f14c9b4]: max priority fee per gas higher than max fee per gas: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxPriorityFeePerGas: 2, maxFeePerGas: 1",
|
||||
},
|
||||
{ // ErrInsufficientFunds
|
||||
// Available balance: 1000000000000000000
|
||||
// Effective cost: 18375000021000
|
||||
// FeeCap * gas: 1050000000000000000
|
||||
// This test is designed to have the effective cost be covered by the balance, but
|
||||
// the extended requirement on FeeCap*gas < balance to fail
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(100000000000000)),
|
||||
},
|
||||
want: "could not apply tx 0 [0x3388378ed60640e75d2edf728d5528a305f599997abc4f23ec46b351b6197499]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 2000000000000000000 want 2100000000000000000",
|
||||
},
|
||||
{ // Another ErrInsufficientFunds, this one to ensure that feecap/tip of max u256 is allowed
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas, bigNumber, bigNumber),
|
||||
},
|
||||
want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 2000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, dummy.NewFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
}
|
||||
if have, want := err.Error(), tt.want; have != want {
|
||||
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrTxTypeNotSupported, For this, we need an older chain
|
||||
{
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{
|
||||
Config: ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
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),
|
||||
SubnetEVMTimestamp: big.NewInt(0),
|
||||
FeeConfig: params.DefaultFeeConfig,
|
||||
},
|
||||
Alloc: GenesisAlloc{
|
||||
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
|
||||
Balance: big.NewInt(1000000000000000000), // 1 ether
|
||||
Nonce: 0,
|
||||
},
|
||||
},
|
||||
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
for i, tt := range []struct {
|
||||
txs []*types.Transaction
|
||||
want string
|
||||
}{
|
||||
// { // ErrTxTypeNotSupported
|
||||
// txs: []*types.Transaction{
|
||||
// mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
|
||||
// },
|
||||
// want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported",
|
||||
// },
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, dummy.NewFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
}
|
||||
if have, want := err.Error(), tt.want; have != want {
|
||||
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrSenderNoEOA, for this we need the sender to have contract code
|
||||
{
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{
|
||||
Config: config,
|
||||
Alloc: GenesisAlloc{
|
||||
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
|
||||
Balance: big.NewInt(1000000000000000000), // 1 ether
|
||||
Nonce: 0,
|
||||
Code: common.FromHex("0xB0B0FACE"),
|
||||
},
|
||||
},
|
||||
GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(),
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
for i, tt := range []struct {
|
||||
txs []*types.Transaction
|
||||
want string
|
||||
}{
|
||||
{ // ErrSenderNoEOA
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
|
||||
},
|
||||
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, dummy.NewFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
}
|
||||
if have, want := err.Error(), tt.want; have != want {
|
||||
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be
|
||||
// valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently
|
||||
// valid to be considered for import:
|
||||
// - valid pow (fake), ancestry, difficulty, gaslimit etc
|
||||
func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block {
|
||||
header := &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Coinbase: parent.Coinbase(),
|
||||
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
|
||||
Number: parent.Number(),
|
||||
Time: parent.Time(),
|
||||
Difficulty: parent.Difficulty(),
|
||||
UncleHash: parent.UncleHash(),
|
||||
}),
|
||||
GasLimit: parent.GasLimit(),
|
||||
Number: new(big.Int).Add(parent.Number(), common.Big1),
|
||||
Time: parent.Time() + 10,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
}
|
||||
|
||||
if config.IsSubnetEVM(new(big.Int).SetUint64(header.Time)) {
|
||||
header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, parent.Header(), header.Time)
|
||||
header.BlockGasCost = big.NewInt(0)
|
||||
}
|
||||
var receipts []*types.Receipt
|
||||
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
|
||||
// Preferably something unique. So let's use a combo of blocknum + txhash
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
hasher.Write(header.Number.Bytes())
|
||||
var cumulativeGas uint64
|
||||
for _, tx := range txs {
|
||||
txh := tx.Hash()
|
||||
hasher.Write(txh[:])
|
||||
receipt := types.NewReceipt(nil, false, cumulativeGas+tx.Gas())
|
||||
receipt.TxHash = tx.Hash()
|
||||
receipt.GasUsed = tx.Gas()
|
||||
receipts = append(receipts, receipt)
|
||||
cumulativeGas += tx.Gas()
|
||||
}
|
||||
header.Root = common.BytesToHash(hasher.Sum(nil))
|
||||
// Assemble and return the final block for sealing
|
||||
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
|
||||
}
|
||||
@@ -232,13 +232,15 @@ func (st *StateTransition) preCheck() error {
|
||||
} else if stNonce > msgNonce {
|
||||
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
|
||||
st.msg.From().Hex(), msgNonce, stNonce)
|
||||
} else if stNonce+1 < stNonce {
|
||||
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
|
||||
st.msg.From().Hex(), stNonce)
|
||||
}
|
||||
// Make sure the sender is an EOA
|
||||
if codeHash := st.state.GetCodeHash(st.msg.From()); codeHash != emptyCodeHash && codeHash != (common.Hash{}) {
|
||||
return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
|
||||
st.msg.From().Hex(), codeHash)
|
||||
}
|
||||
// Make sure the sender is not the Blackhole
|
||||
if st.msg.From() == st.evm.Context.Coinbase {
|
||||
return fmt.Errorf("%w: address %v", vm.ErrNoSenderBlackhole, st.msg.From())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
// (c) 2019-2021, 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 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
|
||||
+1
-5
@@ -153,10 +153,6 @@ type Block struct {
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
|
||||
// Td is used by package core to store the total difficulty
|
||||
// of the chain up to and including the block.
|
||||
td *big.Int
|
||||
}
|
||||
|
||||
// "external" block encoding. used for eth protocol, etc.
|
||||
@@ -176,7 +172,7 @@ type extblock struct {
|
||||
func NewBlock(
|
||||
header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher,
|
||||
) *Block {
|
||||
b := &Block{header: CopyHeader(header), td: new(big.Int)}
|
||||
b := &Block{header: CopyHeader(header)}
|
||||
|
||||
// TODO: panic if len(txs) != len(receipts)
|
||||
if len(txs) == 0 {
|
||||
|
||||
@@ -35,8 +35,8 @@ import (
|
||||
type DynamicFeeTx struct {
|
||||
ChainID *big.Int
|
||||
Nonce uint64
|
||||
GasTipCap *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
|
||||
GasFeeCap *big.Int // a.k.a. maxFeePerGas
|
||||
Gas uint64
|
||||
To *common.Address `rlp:"nil"` // nil means contract creation
|
||||
Value *big.Int
|
||||
|
||||
+13
-19
@@ -27,12 +27,12 @@
|
||||
package vm
|
||||
|
||||
const (
|
||||
set2BitsMask = uint16(0b1100_0000_0000_0000)
|
||||
set3BitsMask = uint16(0b1110_0000_0000_0000)
|
||||
set4BitsMask = uint16(0b1111_0000_0000_0000)
|
||||
set5BitsMask = uint16(0b1111_1000_0000_0000)
|
||||
set6BitsMask = uint16(0b1111_1100_0000_0000)
|
||||
set7BitsMask = uint16(0b1111_1110_0000_0000)
|
||||
set2BitsMask = uint16(0b11)
|
||||
set3BitsMask = uint16(0b111)
|
||||
set4BitsMask = uint16(0b1111)
|
||||
set5BitsMask = uint16(0b1_1111)
|
||||
set6BitsMask = uint16(0b11_1111)
|
||||
set7BitsMask = uint16(0b111_1111)
|
||||
)
|
||||
|
||||
// bitvec is a bit vector which maps bytes in a program.
|
||||
@@ -40,32 +40,26 @@ const (
|
||||
// it's data (i.e. argument of PUSHxx).
|
||||
type bitvec []byte
|
||||
|
||||
var lookup = [8]byte{
|
||||
0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1,
|
||||
}
|
||||
|
||||
func (bits bitvec) set1(pos uint64) {
|
||||
bits[pos/8] |= lookup[pos%8]
|
||||
bits[pos/8] |= 1 << (pos % 8)
|
||||
}
|
||||
|
||||
func (bits bitvec) setN(flag uint16, pos uint64) {
|
||||
a := flag >> (pos % 8)
|
||||
bits[pos/8] |= byte(a >> 8)
|
||||
if b := byte(a); b != 0 {
|
||||
// If the bit-setting affects the neighbouring byte, we can assign - no need to OR it,
|
||||
// since it's the first write to that byte
|
||||
a := flag << (pos % 8)
|
||||
bits[pos/8] |= byte(a)
|
||||
if b := byte(a >> 8); b != 0 {
|
||||
bits[pos/8+1] = b
|
||||
}
|
||||
}
|
||||
|
||||
func (bits bitvec) set8(pos uint64) {
|
||||
a := byte(0xFF >> (pos % 8))
|
||||
a := byte(0xFF << (pos % 8))
|
||||
bits[pos/8] |= a
|
||||
bits[pos/8+1] = ^a
|
||||
}
|
||||
|
||||
func (bits bitvec) set16(pos uint64) {
|
||||
a := byte(0xFF >> (pos % 8))
|
||||
a := byte(0xFF << (pos % 8))
|
||||
bits[pos/8] |= a
|
||||
bits[pos/8+1] = 0xFF
|
||||
bits[pos/8+2] = ^a
|
||||
@@ -73,7 +67,7 @@ func (bits bitvec) set16(pos uint64) {
|
||||
|
||||
// codeSegment checks if the position is in a code segment.
|
||||
func (bits *bitvec) codeSegment(pos uint64) bool {
|
||||
return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0
|
||||
return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0
|
||||
}
|
||||
|
||||
// codeBitmap collects data locations in code.
|
||||
|
||||
+23
-18
@@ -27,6 +27,7 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -38,24 +39,27 @@ func TestJumpDestAnalysis(t *testing.T) {
|
||||
exp byte
|
||||
which int
|
||||
}{
|
||||
{[]byte{byte(PUSH1), 0x01, 0x01, 0x01}, 0x40, 0},
|
||||
{[]byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)}, 0x50, 0},
|
||||
{[]byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01}, 0x7F, 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x80, 1},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), byte(PUSH2), byte(PUSH2), 0x01, 0x01, 0x01}, 0x03, 0},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01}, 0x00, 1},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x74, 0},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x00, 1},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x3F, 0},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0xC0, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x7F, 0},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0xFF, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x80, 2},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0x7f, 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0xA0, 1},
|
||||
{[]byte{byte(PUSH32)}, 0x7F, 0},
|
||||
{[]byte{byte(PUSH32)}, 0xFF, 1},
|
||||
{[]byte{byte(PUSH32)}, 0xFF, 2},
|
||||
{[]byte{byte(PUSH1), 0x01, 0x01, 0x01}, 0b0000_0010, 0},
|
||||
{[]byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)}, 0b0000_1010, 0},
|
||||
{[]byte{0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1)}, 0b0101_0100, 0},
|
||||
{[]byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01}, bits.Reverse8(0x7F), 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 1},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), byte(PUSH2), byte(PUSH2), 0x01, 0x01, 0x01}, 0b1100_0000, 0},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0010_1110, 0},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1100, 0},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0011, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1110, 0},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1111, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 2},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b1111_1110, 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b0000_0101, 1},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1110, 0},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1111, 1},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1111, 2},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1111, 3},
|
||||
{[]byte{byte(PUSH32)}, 0b0000_0001, 4},
|
||||
}
|
||||
for i, test := range tests {
|
||||
ret := codeBitmap(test.code)
|
||||
@@ -77,6 +81,7 @@ func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) {
|
||||
}
|
||||
bench.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
|
||||
// 4 ms
|
||||
code := make([]byte, analysisCodeSize)
|
||||
|
||||
+2
-7
@@ -153,16 +153,11 @@ func (c *Contract) AsDelegate() *Contract {
|
||||
|
||||
// GetOp returns the n'th element in the contract's byte array
|
||||
func (c *Contract) GetOp(n uint64) OpCode {
|
||||
return OpCode(c.GetByte(n))
|
||||
}
|
||||
|
||||
// GetByte returns the n'th byte in the contract's byte array
|
||||
func (c *Contract) GetByte(n uint64) byte {
|
||||
if n < uint64(len(c.Code)) {
|
||||
return c.Code[n]
|
||||
return OpCode(c.Code[n])
|
||||
}
|
||||
|
||||
return 0
|
||||
return STOP
|
||||
}
|
||||
|
||||
// Caller returns the caller of the contract.
|
||||
|
||||
@@ -45,7 +45,12 @@ var (
|
||||
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
|
||||
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
||||
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
|
||||
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
||||
ErrNoSenderBlackhole = errors.New("blackhole address cannot be used as sender")
|
||||
|
||||
// errStopToken is an internal token indicating interpreter loop termination,
|
||||
// never returned to outside callers.
|
||||
errStopToken = errors.New("stop token")
|
||||
)
|
||||
|
||||
// ErrStackUnderflow wraps an evm error when the items on the stack less
|
||||
|
||||
+4
-17
@@ -175,9 +175,6 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
|
||||
// the necessary steps to create accounts and reverses the state in case of an
|
||||
// execution error or failed value transfer.
|
||||
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -264,9 +261,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
// CallCode differs from Call in the sense that it executes the given address'
|
||||
// code with the caller as context.
|
||||
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -315,9 +309,6 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
||||
// code with the caller as context and the caller is set to the caller of the caller.
|
||||
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -357,9 +348,6 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
// Opcodes that attempt to perform such modifications will result in exceptions
|
||||
// instead of performing the modifications.
|
||||
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -439,6 +427,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
return nil, common.Address{}, gas, ErrNoSenderBlackhole
|
||||
}
|
||||
nonce := evm.StateDB.GetNonce(caller.Address())
|
||||
if nonce+1 < nonce {
|
||||
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
||||
}
|
||||
evm.StateDB.SetNonce(caller.Address(), nonce+1)
|
||||
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
|
||||
// the access-list change should not be rolled back
|
||||
@@ -463,10 +454,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, address, gas, nil
|
||||
}
|
||||
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
|
||||
@@ -530,7 +517,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
|
||||
// Create2 creates a new contract using code as deployment code.
|
||||
//
|
||||
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
||||
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
|
||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
codeAndHash := &codeAndHash{code: code}
|
||||
|
||||
@@ -257,7 +257,7 @@ func makeGasLog(n uint64) gasFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -266,7 +266,7 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||
@@ -300,7 +300,7 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||
|
||||
+47
-10
@@ -27,6 +27,8 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -241,7 +243,7 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
offset, size := scope.Stack.pop(), scope.Stack.peek()
|
||||
data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
|
||||
@@ -526,6 +528,9 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
|
||||
}
|
||||
|
||||
func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
loc := scope.Stack.pop()
|
||||
val := scope.Stack.pop()
|
||||
interpreter.evm.StateDB.SetState(scope.Contract.Address(),
|
||||
@@ -534,23 +539,27 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
}
|
||||
|
||||
func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if atomic.LoadInt32(&interpreter.evm.abort) != 0 {
|
||||
return nil, errStopToken
|
||||
}
|
||||
pos := scope.Stack.pop()
|
||||
if !scope.Contract.validJumpdest(&pos) {
|
||||
return nil, ErrInvalidJump
|
||||
}
|
||||
*pc = pos.Uint64()
|
||||
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if atomic.LoadInt32(&interpreter.evm.abort) != 0 {
|
||||
return nil, errStopToken
|
||||
}
|
||||
pos, cond := scope.Stack.pop(), scope.Stack.pop()
|
||||
if !cond.IsZero() {
|
||||
if !scope.Contract.validJumpdest(&pos) {
|
||||
return nil, ErrInvalidJump
|
||||
}
|
||||
*pc = pos.Uint64()
|
||||
} else {
|
||||
*pc++
|
||||
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -575,6 +584,9 @@ func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
|
||||
}
|
||||
|
||||
func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
value = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
@@ -610,12 +622,17 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
interpreter.returnData = res // set REVERT data to return data buffer
|
||||
return res, nil
|
||||
}
|
||||
interpreter.returnData = nil // clear dirty return data buffer
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
endowment = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
@@ -646,8 +663,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
interpreter.returnData = res // set REVERT data to return data buffer
|
||||
return res, nil
|
||||
}
|
||||
interpreter.returnData = nil // clear dirty return data buffer
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -663,6 +682,9 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
||||
// Get the arguments from the memory.
|
||||
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||
|
||||
if interpreter.readOnly && !value.IsZero() {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
bigVal := big0
|
||||
// TODO: use uint256.Int instead of converting with toBig()
|
||||
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
|
||||
@@ -686,6 +708,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -721,6 +744,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -749,6 +773,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -777,6 +802,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -784,21 +810,29 @@ func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
||||
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
|
||||
return ret, nil
|
||||
return ret, errStopToken
|
||||
}
|
||||
|
||||
func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
||||
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
|
||||
return ret, nil
|
||||
interpreter.returnData = ret
|
||||
return ret, ErrExecutionReverted
|
||||
}
|
||||
|
||||
func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])}
|
||||
}
|
||||
|
||||
func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
return nil, nil
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
||||
func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
beneficiary := scope.Stack.pop()
|
||||
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
||||
@@ -807,7 +841,7 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
|
||||
interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil)
|
||||
}
|
||||
return nil, nil
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
||||
// following functions are used by the instruction jump table
|
||||
@@ -815,6 +849,9 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
// make log instruction function
|
||||
func makeLog(size int) executionFunc {
|
||||
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
topics := make([]common.Hash, size)
|
||||
stack := scope.Stack
|
||||
mStart, mSize := stack.pop(), stack.pop()
|
||||
|
||||
@@ -541,12 +541,14 @@ func TestOpMstore(t *testing.T) {
|
||||
mem.Resize(64)
|
||||
pc := uint64(0)
|
||||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||
stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int))
|
||||
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
||||
stack.push(new(uint256.Int))
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||
}
|
||||
stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int))
|
||||
stack.push(new(uint256.Int).SetUint64(0x1))
|
||||
stack.push(new(uint256.Int))
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||
t.Fatalf("Mstore failed to overwrite previous value")
|
||||
@@ -569,12 +571,13 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(*value, *memStart)
|
||||
stack.push(value)
|
||||
stack.push(memStart)
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpSHA3(bench *testing.B) {
|
||||
func BenchmarkOpKeccak256(bench *testing.B) {
|
||||
var (
|
||||
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
@@ -588,8 +591,9 @@ func BenchmarkOpSHA3(bench *testing.B) {
|
||||
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(*uint256.NewInt(32), *start)
|
||||
opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
stack.push(uint256.NewInt(32))
|
||||
stack.push(start)
|
||||
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
-85
@@ -28,7 +28,6 @@ package vm
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
@@ -44,11 +43,10 @@ var BuiltinAddr = common.Address{
|
||||
type Config struct {
|
||||
Debug bool // Enables debugging
|
||||
Tracer EVMLogger // Opcode logger
|
||||
NoRecursion bool // Disables call, callcode, delegate call and create
|
||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||
|
||||
JumpTable [256]*operation // EVM instruction table, automatically populated if unset
|
||||
JumpTable *JumpTable // EVM instruction table, automatically populated if unset
|
||||
|
||||
ExtraEips []int // Additional EIPS that are to be enabled
|
||||
|
||||
@@ -86,37 +84,35 @@ type EVMInterpreter struct {
|
||||
|
||||
// NewEVMInterpreter returns a new instance of the Interpreter.
|
||||
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||
// We use the STOP instruction whether to see
|
||||
// the jump table was initialised. If it was not
|
||||
// we'll set the default jump table.
|
||||
if cfg.JumpTable[STOP] == nil {
|
||||
var jt JumpTable
|
||||
// If jump table was not initialised we set the default one.
|
||||
if cfg.JumpTable == nil {
|
||||
switch {
|
||||
case evm.chainRules.IsSubnetEVM:
|
||||
jt = subnetEVMInstructionSet
|
||||
cfg.JumpTable = &subnetEVMInstructionSet
|
||||
case evm.chainRules.IsIstanbul:
|
||||
jt = istanbulInstructionSet
|
||||
cfg.JumpTable = &istanbulInstructionSet
|
||||
case evm.chainRules.IsConstantinople:
|
||||
jt = constantinopleInstructionSet
|
||||
cfg.JumpTable = &constantinopleInstructionSet
|
||||
case evm.chainRules.IsByzantium:
|
||||
jt = byzantiumInstructionSet
|
||||
cfg.JumpTable = &byzantiumInstructionSet
|
||||
case evm.chainRules.IsEIP158:
|
||||
jt = spuriousDragonInstructionSet
|
||||
cfg.JumpTable = &spuriousDragonInstructionSet
|
||||
case evm.chainRules.IsEIP150:
|
||||
jt = tangerineWhistleInstructionSet
|
||||
cfg.JumpTable = &tangerineWhistleInstructionSet
|
||||
case evm.chainRules.IsHomestead:
|
||||
jt = homesteadInstructionSet
|
||||
cfg.JumpTable = &homesteadInstructionSet
|
||||
default:
|
||||
jt = frontierInstructionSet
|
||||
cfg.JumpTable = &frontierInstructionSet
|
||||
}
|
||||
for i, eip := range cfg.ExtraEips {
|
||||
if err := EnableEIP(eip, &jt); err != nil {
|
||||
copy := *cfg.JumpTable
|
||||
if err := EnableEIP(eip, ©); err != nil {
|
||||
// Disable it, so caller can check if it's activated or not
|
||||
cfg.ExtraEips = append(cfg.ExtraEips[:i], cfg.ExtraEips[i+1:]...)
|
||||
log.Error("EIP activation failed", "eip", eip, "error", err)
|
||||
}
|
||||
cfg.JumpTable = ©
|
||||
}
|
||||
cfg.JumpTable = jt
|
||||
}
|
||||
|
||||
return &EVMInterpreter{
|
||||
@@ -132,7 +128,11 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||
// considered a revert-and-consume-all-gas operation except for
|
||||
// ErrExecutionReverted which means revert-and-keep-gas-left.
|
||||
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
|
||||
if contract.Address() == BuiltinAddr {
|
||||
// Deprecate special handling of [BuiltinAddr] as of SubnetEVM.
|
||||
// In SubnetEVM, the contract deployed in the genesis is overridden by a deprecated precompiled
|
||||
// contract which will return an error immediately if its ever called. Therefore, this function should
|
||||
// never be called after SubnetEVM with [BuiltinAddr] as the contract address.
|
||||
if !in.evm.chainRules.IsSubnetEVM && contract.Address() == BuiltinAddr {
|
||||
self := AccountRef(contract.Caller())
|
||||
if _, ok := contract.caller.(*Contract); ok {
|
||||
contract = contract.AsDelegate()
|
||||
@@ -195,9 +195,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if !logged {
|
||||
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
} else {
|
||||
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
|
||||
in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -206,101 +206,71 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
||||
// the execution of one of the operations or until the done flag is set by the
|
||||
// parent context.
|
||||
steps := 0
|
||||
for {
|
||||
steps++
|
||||
if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 {
|
||||
break
|
||||
}
|
||||
if in.cfg.Debug {
|
||||
// Capture pre-execution values for tracing.
|
||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||
}
|
||||
|
||||
// Get the operation from the jump table and validate the stack to ensure there are
|
||||
// enough stack items available to perform the operation.
|
||||
op = contract.GetOp(pc)
|
||||
operation := in.cfg.JumpTable[op]
|
||||
if operation == nil {
|
||||
return nil, &ErrInvalidOpCode{opcode: op}
|
||||
}
|
||||
cost = operation.constantGas // For tracing
|
||||
// Validate stack
|
||||
if sLen := stack.len(); sLen < operation.minStack {
|
||||
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
|
||||
} else if sLen > operation.maxStack {
|
||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
// If the operation is valid, enforce write restrictions
|
||||
if in.readOnly && in.evm.chainRules.IsByzantium {
|
||||
// If the interpreter is operating in readonly mode, make sure no
|
||||
// state-modifying operation is performed. The 3rd stack item
|
||||
// for a call operation is the value. Transferring value from one
|
||||
// account to the others means the state is modified and should also
|
||||
// return with an error.
|
||||
if operation.writes || ((op == CALL) && stack.Back(2).Sign() != 0) {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
}
|
||||
// Static portion of gas
|
||||
cost = operation.constantGas // For tracing
|
||||
if !contract.UseGas(operation.constantGas) {
|
||||
if !contract.UseGas(cost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
||||
var memorySize uint64
|
||||
// calculate the new memory size and expand the memory to fit
|
||||
// the operation
|
||||
// Memory check needs to be done prior to evaluating the dynamic gas portion,
|
||||
// to detect calculation overflows
|
||||
if operation.memorySize != nil {
|
||||
memSize, overflow := operation.memorySize(stack)
|
||||
if overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
// memory is expanded in words of 32 bytes. Gas
|
||||
// is also calculated in words.
|
||||
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
// Dynamic portion of gas
|
||||
// consume the gas and return an error if not enough gas is available.
|
||||
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||
if operation.dynamicGas != nil {
|
||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||
var memorySize uint64
|
||||
// calculate the new memory size and expand the memory to fit
|
||||
// the operation
|
||||
// Memory check needs to be done prior to evaluating the dynamic gas portion,
|
||||
// to detect calculation overflows
|
||||
if operation.memorySize != nil {
|
||||
memSize, overflow := operation.memorySize(stack)
|
||||
if overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
// memory is expanded in words of 32 bytes. Gas
|
||||
// is also calculated in words.
|
||||
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
// Consume the gas and return an error if not enough gas is available.
|
||||
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||
var dynamicCost uint64
|
||||
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
|
||||
cost += dynamicCost // total cost, for debug tracing
|
||||
cost += dynamicCost // for tracing
|
||||
if err != nil || !contract.UseGas(dynamicCost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
|
||||
if in.cfg.Debug {
|
||||
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
logged = true
|
||||
}
|
||||
|
||||
// execute the operation
|
||||
res, err = operation.execute(&pc, in, callContext)
|
||||
// if the operation clears the return data (e.g. it has returning data)
|
||||
// set the last return to the result of the operation.
|
||||
if operation.returns {
|
||||
in.returnData = res
|
||||
}
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case operation.reverts:
|
||||
return res, ErrExecutionReverted
|
||||
case operation.halts:
|
||||
return res, nil
|
||||
case !operation.jumps:
|
||||
pc++
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
pc++
|
||||
}
|
||||
return nil, nil
|
||||
if err == errStopToken {
|
||||
err = nil // clear stop token error
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// (c) 2020-2021, 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 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
|
||||
// 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 vm
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/rawdb"
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
var loopInterruptTests = []string{
|
||||
// infinite loop using JUMP: push(2) jumpdest dup1 jump
|
||||
"60025b8056",
|
||||
// infinite loop using JUMPI: push(1) push(4) jumpdest dup2 dup2 jumpi
|
||||
"600160045b818157",
|
||||
}
|
||||
|
||||
func TestLoopInterrupt(t *testing.T) {
|
||||
address := common.BytesToAddress([]byte("contract"))
|
||||
vmctx := BlockContext{
|
||||
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
|
||||
}
|
||||
|
||||
for i, tt := range loopInterruptTests {
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
statedb.CreateAccount(address)
|
||||
statedb.SetCode(address, common.Hex2Bytes(tt))
|
||||
statedb.Finalise(true)
|
||||
|
||||
evm := NewEVM(vmctx, TxContext{}, statedb, params.TestChainConfig, Config{})
|
||||
|
||||
errChannel := make(chan error)
|
||||
timeout := make(chan bool)
|
||||
|
||||
go func(evm *EVM) {
|
||||
_, _, err := evm.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(big.Int))
|
||||
errChannel <- err
|
||||
}(evm)
|
||||
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
timeout <- true
|
||||
}()
|
||||
|
||||
evm.Cancel()
|
||||
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Errorf("test %d timed out", i)
|
||||
case err := <-errChannel:
|
||||
if err != nil {
|
||||
t.Errorf("test %d failure: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
-42
@@ -27,6 +27,8 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
)
|
||||
|
||||
@@ -50,12 +52,6 @@ type operation struct {
|
||||
|
||||
// memorySize returns the memory size required for the operation
|
||||
memorySize memorySizeFunc
|
||||
|
||||
halts bool // indicates whether the operation should halt further execution
|
||||
jumps bool // indicates whether the program counter should not increment
|
||||
writes bool // determines whether this a state modifying operation
|
||||
reverts bool // determines whether the operation reverts state (implicitly halts)
|
||||
returns bool // determines whether the operations sets the return data content
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -78,7 +74,25 @@ func newSubnetEVMInstructionSet() JumpTable {
|
||||
instructionSet := newIstanbulInstructionSet()
|
||||
enable2929(&instructionSet)
|
||||
enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func validate(jt JumpTable) JumpTable {
|
||||
for i, op := range jt {
|
||||
if op == nil {
|
||||
panic(fmt.Sprintf("op 0x%x is not set", i))
|
||||
}
|
||||
// The interpreter has an assumption that if the memorySize function is
|
||||
// set, then the dynamicGas function is also set. This is a somewhat
|
||||
// arbitrary assumption, and can be removed if we need to -- but it
|
||||
// allows us to avoid a condition check. As long as we have that assumption
|
||||
// in there, this little sanity check prevents us from merging in a
|
||||
// change which violates it.
|
||||
if op.memorySize != nil && op.dynamicGas == nil {
|
||||
panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String()))
|
||||
}
|
||||
}
|
||||
return jt
|
||||
}
|
||||
|
||||
// newIstanbulInstructionSet returns the frontier,
|
||||
@@ -90,7 +104,7 @@ func newIstanbulInstructionSet() JumpTable {
|
||||
enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884
|
||||
enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200
|
||||
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newConstantinopleInstructionSet returns the frontier, homestead,
|
||||
@@ -128,10 +142,8 @@ func newConstantinopleInstructionSet() JumpTable {
|
||||
minStack: minStack(4, 1),
|
||||
maxStack: maxStack(4, 1),
|
||||
memorySize: memoryCreate2,
|
||||
writes: true,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newByzantiumInstructionSet returns the frontier, homestead and
|
||||
@@ -145,7 +157,6 @@ func newByzantiumInstructionSet() JumpTable {
|
||||
minStack: minStack(6, 1),
|
||||
maxStack: maxStack(6, 1),
|
||||
memorySize: memoryStaticCall,
|
||||
returns: true,
|
||||
}
|
||||
instructionSet[RETURNDATASIZE] = &operation{
|
||||
execute: opReturnDataSize,
|
||||
@@ -167,17 +178,15 @@ func newByzantiumInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryRevert,
|
||||
reverts: true,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// EIP 158 a.k.a Spurious Dragon
|
||||
func newSpuriousDragonInstructionSet() JumpTable {
|
||||
instructionSet := newTangerineWhistleInstructionSet()
|
||||
instructionSet[EXP].dynamicGas = gasExpEIP158
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// EIP 150 a.k.a Tangerine Whistle
|
||||
@@ -190,7 +199,7 @@ func newTangerineWhistleInstructionSet() JumpTable {
|
||||
instructionSet[CALL].constantGas = params.CallGasEIP150
|
||||
instructionSet[CALLCODE].constantGas = params.CallGasEIP150
|
||||
instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newHomesteadInstructionSet returns the frontier and homestead
|
||||
@@ -204,21 +213,19 @@ func newHomesteadInstructionSet() JumpTable {
|
||||
minStack: minStack(6, 1),
|
||||
maxStack: maxStack(6, 1),
|
||||
memorySize: memoryDelegateCall,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newFrontierInstructionSet returns the frontier instructions
|
||||
// that can be executed during the frontier phase.
|
||||
func newFrontierInstructionSet() JumpTable {
|
||||
return JumpTable{
|
||||
tbl := JumpTable{
|
||||
STOP: {
|
||||
execute: opStop,
|
||||
constantGas: 0,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
halts: true,
|
||||
},
|
||||
ADD: {
|
||||
execute: opAdd,
|
||||
@@ -352,13 +359,13 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 1),
|
||||
maxStack: maxStack(2, 1),
|
||||
},
|
||||
SHA3: {
|
||||
execute: opSha3,
|
||||
constantGas: params.Sha3Gas,
|
||||
dynamicGas: gasSha3,
|
||||
KECCAK256: {
|
||||
execute: opKeccak256,
|
||||
constantGas: params.Keccak256Gas,
|
||||
dynamicGas: gasKeccak256,
|
||||
minStack: minStack(2, 1),
|
||||
maxStack: maxStack(2, 1),
|
||||
memorySize: memorySha3,
|
||||
memorySize: memoryKeccak256,
|
||||
},
|
||||
ADDRESS: {
|
||||
execute: opAddress,
|
||||
@@ -521,21 +528,18 @@ func newFrontierInstructionSet() JumpTable {
|
||||
dynamicGas: gasSStore,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
writes: true,
|
||||
},
|
||||
JUMP: {
|
||||
execute: opJump,
|
||||
constantGas: GasMidStep,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
jumps: true,
|
||||
},
|
||||
JUMPI: {
|
||||
execute: opJumpi,
|
||||
constantGas: GasSlowStep,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
jumps: true,
|
||||
},
|
||||
PC: {
|
||||
execute: opPc,
|
||||
@@ -951,7 +955,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG1: {
|
||||
execute: makeLog(1),
|
||||
@@ -959,7 +962,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(3, 0),
|
||||
maxStack: maxStack(3, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG2: {
|
||||
execute: makeLog(2),
|
||||
@@ -967,7 +969,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(4, 0),
|
||||
maxStack: maxStack(4, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG3: {
|
||||
execute: makeLog(3),
|
||||
@@ -975,7 +976,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(5, 0),
|
||||
maxStack: maxStack(5, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG4: {
|
||||
execute: makeLog(4),
|
||||
@@ -983,7 +983,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(6, 0),
|
||||
maxStack: maxStack(6, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
CREATE: {
|
||||
execute: opCreate,
|
||||
@@ -992,8 +991,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(3, 1),
|
||||
maxStack: maxStack(3, 1),
|
||||
memorySize: memoryCreate,
|
||||
writes: true,
|
||||
returns: true,
|
||||
},
|
||||
CALL: {
|
||||
execute: opCall,
|
||||
@@ -1002,7 +999,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(7, 1),
|
||||
maxStack: maxStack(7, 1),
|
||||
memorySize: memoryCall,
|
||||
returns: true,
|
||||
},
|
||||
CALLCODE: {
|
||||
execute: opCallCode,
|
||||
@@ -1011,7 +1007,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(7, 1),
|
||||
maxStack: maxStack(7, 1),
|
||||
memorySize: memoryCall,
|
||||
returns: true,
|
||||
},
|
||||
RETURN: {
|
||||
execute: opReturn,
|
||||
@@ -1019,15 +1014,21 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryReturn,
|
||||
halts: true,
|
||||
},
|
||||
SELFDESTRUCT: {
|
||||
execute: opSuicide,
|
||||
execute: opSelfdestruct,
|
||||
dynamicGas: gasSelfdestruct,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
halts: true,
|
||||
writes: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Fill all unassigned slots with opUndefined.
|
||||
for i, entry := range tbl {
|
||||
if entry == nil {
|
||||
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)}
|
||||
}
|
||||
}
|
||||
|
||||
return validate(tbl)
|
||||
}
|
||||
|
||||
+2
-320
@@ -27,87 +27,12 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// Storage represents a contract's storage.
|
||||
type Storage map[common.Hash]common.Hash
|
||||
|
||||
// Copy duplicates the current storage.
|
||||
func (s Storage) Copy() Storage {
|
||||
cpy := make(Storage)
|
||||
for key, value := range s {
|
||||
cpy[key] = value
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// LogConfig are the configuration options for structured logger the EVM
|
||||
type LogConfig struct {
|
||||
EnableMemory bool // enable memory capture
|
||||
DisableStack bool // disable stack capture
|
||||
DisableStorage bool // disable storage capture
|
||||
EnableReturnData bool // enable return data capture
|
||||
Debug bool // print output during capture end
|
||||
Limit int // maximum length of output, but zero means unlimited
|
||||
// Chain overrides, can be used to execute a trace using future fork rules
|
||||
Overrides *params.ChainConfig `json:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
||||
|
||||
// StructLog is emitted to the EVM each cycle and lists information about the current internal state
|
||||
// prior to the execution of the statement.
|
||||
type StructLog struct {
|
||||
Pc uint64 `json:"pc"`
|
||||
Op OpCode `json:"op"`
|
||||
Gas uint64 `json:"gas"`
|
||||
GasCost uint64 `json:"gasCost"`
|
||||
Memory []byte `json:"memory"`
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData []byte `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// overrides for gencodec
|
||||
type structLogMarshaling struct {
|
||||
Gas math.HexOrDecimal64
|
||||
GasCost math.HexOrDecimal64
|
||||
Memory hexutil.Bytes
|
||||
ReturnData hexutil.Bytes
|
||||
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
|
||||
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
|
||||
}
|
||||
|
||||
// OpName formats the operand name in a human-readable format.
|
||||
func (s *StructLog) OpName() string {
|
||||
return s.Op.String()
|
||||
}
|
||||
|
||||
// ErrorString formats the log's error as a string.
|
||||
func (s *StructLog) ErrorString() string {
|
||||
if s.Err != nil {
|
||||
return s.Err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// EVMLogger is used to collect execution traces from an EVM transaction
|
||||
// execution. CaptureState is called for each step of the VM with the
|
||||
// current VM state.
|
||||
@@ -115,252 +40,9 @@ func (s *StructLog) ErrorString() string {
|
||||
// if you need to retain them beyond the current call.
|
||||
type EVMLogger interface {
|
||||
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
|
||||
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
|
||||
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
|
||||
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
|
||||
CaptureExit(output []byte, gasUsed uint64, err error)
|
||||
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
|
||||
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
|
||||
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
|
||||
}
|
||||
|
||||
// StructLogger is an EVM state logger and implements EVMLogger.
|
||||
//
|
||||
// StructLogger can capture state based on the given Log configuration and also keeps
|
||||
// a track record of modified storage which is used in reporting snapshots of the
|
||||
// contract their storage.
|
||||
type StructLogger struct {
|
||||
cfg LogConfig
|
||||
|
||||
storage map[common.Address]Storage
|
||||
logs []StructLog
|
||||
output []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// NewStructLogger returns a new logger
|
||||
func NewStructLogger(cfg *LogConfig) *StructLogger {
|
||||
logger := &StructLogger{
|
||||
storage: make(map[common.Address]Storage),
|
||||
}
|
||||
if cfg != nil {
|
||||
logger.cfg = *cfg
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// Reset clears the data held by the logger.
|
||||
func (l *StructLogger) Reset() {
|
||||
l.storage = make(map[common.Address]Storage)
|
||||
l.output = make([]byte, 0)
|
||||
l.logs = l.logs[:0]
|
||||
l.err = nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureState logs a new structured log message and pushes it out to the environment
|
||||
//
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
|
||||
memory := scope.Memory
|
||||
stack := scope.Stack
|
||||
contract := scope.Contract
|
||||
// check if already accumulated the specified number of logs
|
||||
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
|
||||
return
|
||||
}
|
||||
// Copy a snapshot of the current memory state to a new buffer
|
||||
var mem []byte
|
||||
if l.cfg.EnableMemory {
|
||||
mem = make([]byte, len(memory.Data()))
|
||||
copy(mem, memory.Data())
|
||||
}
|
||||
// Copy a snapshot of the current stack state to a new buffer
|
||||
var stck []uint256.Int
|
||||
if !l.cfg.DisableStack {
|
||||
stck = make([]uint256.Int, len(stack.Data()))
|
||||
for i, item := range stack.Data() {
|
||||
stck[i] = item
|
||||
}
|
||||
}
|
||||
// Copy a snapshot of the current storage to a new container
|
||||
var storage Storage
|
||||
if !l.cfg.DisableStorage && (op == SLOAD || op == SSTORE) {
|
||||
// initialise new changed values storage container for this contract
|
||||
// if not present.
|
||||
if l.storage[contract.Address()] == nil {
|
||||
l.storage[contract.Address()] = make(Storage)
|
||||
}
|
||||
// capture SLOAD opcodes and record the read entry in the local storage
|
||||
if op == SLOAD && stack.len() >= 1 {
|
||||
var (
|
||||
address = common.Hash(stack.data[stack.len()-1].Bytes32())
|
||||
value = env.StateDB.GetState(contract.Address(), address)
|
||||
)
|
||||
l.storage[contract.Address()][address] = value
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
} else if op == SSTORE && stack.len() >= 2 {
|
||||
// capture SSTORE opcodes and record the written entry in the local storage.
|
||||
var (
|
||||
value = common.Hash(stack.data[stack.len()-2].Bytes32())
|
||||
address = common.Hash(stack.data[stack.len()-1].Bytes32())
|
||||
)
|
||||
l.storage[contract.Address()][address] = value
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
}
|
||||
}
|
||||
var rdata []byte
|
||||
if l.cfg.EnableReturnData {
|
||||
rdata = make([]byte, len(rData))
|
||||
copy(rdata, rData)
|
||||
}
|
||||
// create a new snapshot of the EVM.
|
||||
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err}
|
||||
l.logs = append(l.logs, log)
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault
|
||||
// while running an opcode.
|
||||
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
l.output = output
|
||||
l.err = err
|
||||
if l.cfg.Debug {
|
||||
fmt.Printf("0x%x\n", output)
|
||||
if err != nil {
|
||||
fmt.Printf(" error: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// StructLogs returns the captured log entries.
|
||||
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
|
||||
|
||||
// Error returns the VM error captured by the trace.
|
||||
func (l *StructLogger) Error() error { return l.err }
|
||||
|
||||
// Output returns the VM return value captured by the trace.
|
||||
func (l *StructLogger) Output() []byte { return l.output }
|
||||
|
||||
// WriteTrace writes a formatted trace to the given writer
|
||||
func WriteTrace(writer io.Writer, logs []StructLog) {
|
||||
for _, log := range logs {
|
||||
fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost)
|
||||
if log.Err != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %v", log.Err)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
|
||||
if len(log.Stack) > 0 {
|
||||
fmt.Fprintln(writer, "Stack:")
|
||||
for i := len(log.Stack) - 1; i >= 0; i-- {
|
||||
fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
|
||||
}
|
||||
}
|
||||
if len(log.Memory) > 0 {
|
||||
fmt.Fprintln(writer, "Memory:")
|
||||
fmt.Fprint(writer, hex.Dump(log.Memory))
|
||||
}
|
||||
if len(log.Storage) > 0 {
|
||||
fmt.Fprintln(writer, "Storage:")
|
||||
for h, item := range log.Storage {
|
||||
fmt.Fprintf(writer, "%x: %x\n", h, item)
|
||||
}
|
||||
}
|
||||
if len(log.ReturnData) > 0 {
|
||||
fmt.Fprintln(writer, "ReturnData:")
|
||||
fmt.Fprint(writer, hex.Dump(log.ReturnData))
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLogs writes vm logs in a readable format to the given writer
|
||||
func WriteLogs(writer io.Writer, logs []*types.Log) {
|
||||
for _, log := range logs {
|
||||
fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
|
||||
|
||||
for i, topic := range log.Topics {
|
||||
fmt.Fprintf(writer, "%08d %x\n", i, topic)
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, hex.Dump(log.Data))
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
type mdLogger struct {
|
||||
out io.Writer
|
||||
cfg *LogConfig
|
||||
}
|
||||
|
||||
// NewMarkdownLogger creates a logger which outputs information in a format adapted
|
||||
// for human readability, and is also a valid markdown table
|
||||
func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
|
||||
l := &mdLogger{writer, cfg}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &LogConfig{}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
if !create {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
input, gas, value)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
input, gas, value)
|
||||
}
|
||||
|
||||
fmt.Fprintf(t.out, `
|
||||
| Pc | Op | Cost | Stack | RStack | Refund |
|
||||
|-------|-------------|------|-----------|-----------|---------|
|
||||
`)
|
||||
}
|
||||
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
|
||||
stack := scope.Stack
|
||||
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
|
||||
|
||||
if !t.cfg.DisableStack {
|
||||
// format stack
|
||||
var a []string
|
||||
for _, elem := range stack.data {
|
||||
a = append(a, elem.Hex())
|
||||
}
|
||||
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||
fmt.Fprintf(t.out, "%10v |", b)
|
||||
}
|
||||
fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund())
|
||||
fmt.Fprintln(t.out, "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(t.out, "Error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
|
||||
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) {
|
||||
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||
output, gasUsed, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
package vm
|
||||
|
||||
func memorySha3(stack *Stack) (uint64, bool) {
|
||||
func memoryKeccak256(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||
}
|
||||
|
||||
|
||||
+72
-69
@@ -42,75 +42,70 @@ func (op OpCode) IsPush() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsStaticJump specifies if an opcode is JUMP.
|
||||
func (op OpCode) IsStaticJump() bool {
|
||||
return op == JUMP
|
||||
}
|
||||
|
||||
// 0x0 range - arithmetic ops.
|
||||
const (
|
||||
STOP OpCode = iota
|
||||
ADD
|
||||
MUL
|
||||
SUB
|
||||
DIV
|
||||
SDIV
|
||||
MOD
|
||||
SMOD
|
||||
ADDMOD
|
||||
MULMOD
|
||||
EXP
|
||||
SIGNEXTEND
|
||||
STOP OpCode = 0x0
|
||||
ADD OpCode = 0x1
|
||||
MUL OpCode = 0x2
|
||||
SUB OpCode = 0x3
|
||||
DIV OpCode = 0x4
|
||||
SDIV OpCode = 0x5
|
||||
MOD OpCode = 0x6
|
||||
SMOD OpCode = 0x7
|
||||
ADDMOD OpCode = 0x8
|
||||
MULMOD OpCode = 0x9
|
||||
EXP OpCode = 0xa
|
||||
SIGNEXTEND OpCode = 0xb
|
||||
)
|
||||
|
||||
// 0x10 range - comparison ops.
|
||||
const (
|
||||
LT OpCode = iota + 0x10
|
||||
GT
|
||||
SLT
|
||||
SGT
|
||||
EQ
|
||||
ISZERO
|
||||
AND
|
||||
OR
|
||||
XOR
|
||||
NOT
|
||||
BYTE
|
||||
SHL
|
||||
SHR
|
||||
SAR
|
||||
LT OpCode = 0x10
|
||||
GT OpCode = 0x11
|
||||
SLT OpCode = 0x12
|
||||
SGT OpCode = 0x13
|
||||
EQ OpCode = 0x14
|
||||
ISZERO OpCode = 0x15
|
||||
AND OpCode = 0x16
|
||||
OR OpCode = 0x17
|
||||
XOR OpCode = 0x18
|
||||
NOT OpCode = 0x19
|
||||
BYTE OpCode = 0x1a
|
||||
SHL OpCode = 0x1b
|
||||
SHR OpCode = 0x1c
|
||||
SAR OpCode = 0x1d
|
||||
|
||||
SHA3 OpCode = 0x20
|
||||
KECCAK256 OpCode = 0x20
|
||||
)
|
||||
|
||||
// 0x30 range - closure state.
|
||||
const (
|
||||
ADDRESS OpCode = 0x30 + iota
|
||||
BALANCE
|
||||
ORIGIN
|
||||
CALLER
|
||||
CALLVALUE
|
||||
CALLDATALOAD
|
||||
CALLDATASIZE
|
||||
CALLDATACOPY
|
||||
CODESIZE
|
||||
CODECOPY
|
||||
GASPRICE
|
||||
EXTCODESIZE
|
||||
EXTCODECOPY
|
||||
RETURNDATASIZE
|
||||
RETURNDATACOPY
|
||||
EXTCODEHASH
|
||||
ADDRESS OpCode = 0x30
|
||||
BALANCE OpCode = 0x31
|
||||
ORIGIN OpCode = 0x32
|
||||
CALLER OpCode = 0x33
|
||||
CALLVALUE OpCode = 0x34
|
||||
CALLDATALOAD OpCode = 0x35
|
||||
CALLDATASIZE OpCode = 0x36
|
||||
CALLDATACOPY OpCode = 0x37
|
||||
CODESIZE OpCode = 0x38
|
||||
CODECOPY OpCode = 0x39
|
||||
GASPRICE OpCode = 0x3a
|
||||
EXTCODESIZE OpCode = 0x3b
|
||||
EXTCODECOPY OpCode = 0x3c
|
||||
RETURNDATASIZE OpCode = 0x3d
|
||||
RETURNDATACOPY OpCode = 0x3e
|
||||
EXTCODEHASH OpCode = 0x3f
|
||||
)
|
||||
|
||||
// 0x40 range - block operations.
|
||||
const (
|
||||
BLOCKHASH OpCode = 0x40 + iota
|
||||
COINBASE
|
||||
TIMESTAMP
|
||||
NUMBER
|
||||
DIFFICULTY
|
||||
GASLIMIT
|
||||
BLOCKHASH OpCode = 0x40
|
||||
COINBASE OpCode = 0x41
|
||||
TIMESTAMP OpCode = 0x42
|
||||
NUMBER OpCode = 0x43
|
||||
DIFFICULTY OpCode = 0x44
|
||||
GASLIMIT OpCode = 0x45
|
||||
CHAINID OpCode = 0x46
|
||||
SELFBALANCE OpCode = 0x47
|
||||
BASEFEE OpCode = 0x48
|
||||
@@ -132,7 +127,7 @@ const (
|
||||
JUMPDEST OpCode = 0x5b
|
||||
)
|
||||
|
||||
// 0x60 range.
|
||||
// 0x60 range - pushes.
|
||||
const (
|
||||
PUSH1 OpCode = 0x60 + iota
|
||||
PUSH2
|
||||
@@ -166,7 +161,11 @@ const (
|
||||
PUSH30
|
||||
PUSH31
|
||||
PUSH32
|
||||
DUP1
|
||||
)
|
||||
|
||||
// 0x80 range - dups.
|
||||
const (
|
||||
DUP1 = 0x80 + iota
|
||||
DUP2
|
||||
DUP3
|
||||
DUP4
|
||||
@@ -182,7 +181,11 @@ const (
|
||||
DUP14
|
||||
DUP15
|
||||
DUP16
|
||||
SWAP1
|
||||
)
|
||||
|
||||
// 0x90 range - swaps.
|
||||
const (
|
||||
SWAP1 = 0x90 + iota
|
||||
SWAP2
|
||||
SWAP3
|
||||
SWAP4
|
||||
@@ -218,14 +221,16 @@ const (
|
||||
|
||||
// 0xf0 range - closures.
|
||||
const (
|
||||
CREATE OpCode = 0xf0 + iota
|
||||
CALL
|
||||
CALLCODE
|
||||
RETURN
|
||||
DELEGATECALL
|
||||
CREATE2
|
||||
CREATE OpCode = 0xf0
|
||||
CALL OpCode = 0xf1
|
||||
CALLCODE OpCode = 0xf2
|
||||
RETURN OpCode = 0xf3
|
||||
DELEGATECALL OpCode = 0xf4
|
||||
CREATE2 OpCode = 0xf5
|
||||
|
||||
STATICCALL OpCode = 0xfa
|
||||
REVERT OpCode = 0xfd
|
||||
INVALID OpCode = 0xfe
|
||||
SELFDESTRUCT OpCode = 0xff
|
||||
)
|
||||
|
||||
@@ -262,7 +267,7 @@ var opCodeToString = map[OpCode]string{
|
||||
MULMOD: "MULMOD",
|
||||
|
||||
// 0x20 range - crypto.
|
||||
SHA3: "SHA3",
|
||||
KECCAK256: "KECCAK256",
|
||||
|
||||
// 0x30 range - closure state.
|
||||
ADDRESS: "ADDRESS",
|
||||
@@ -391,11 +396,8 @@ var opCodeToString = map[OpCode]string{
|
||||
CREATE2: "CREATE2",
|
||||
STATICCALL: "STATICCALL",
|
||||
REVERT: "REVERT",
|
||||
INVALID: "INVALID",
|
||||
SELFDESTRUCT: "SELFDESTRUCT",
|
||||
|
||||
PUSH: "PUSH",
|
||||
DUP: "DUP",
|
||||
SWAP: "SWAP",
|
||||
}
|
||||
|
||||
func (op OpCode) String() string {
|
||||
@@ -434,7 +436,7 @@ var stringToOp = map[string]OpCode{
|
||||
"SAR": SAR,
|
||||
"ADDMOD": ADDMOD,
|
||||
"MULMOD": MULMOD,
|
||||
"SHA3": SHA3,
|
||||
"KECCAK256": KECCAK256,
|
||||
"ADDRESS": ADDRESS,
|
||||
"BALANCE": BALANCE,
|
||||
"ORIGIN": ORIGIN,
|
||||
@@ -549,6 +551,7 @@ var stringToOp = map[string]OpCode{
|
||||
"RETURN": RETURN,
|
||||
"CALLCODE": CALLCODE,
|
||||
"REVERT": REVERT,
|
||||
"INVALID": INVALID,
|
||||
"SELFDESTRUCT": SELFDESTRUCT,
|
||||
}
|
||||
|
||||
|
||||
@@ -41,10 +41,14 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/eth/tracers"
|
||||
"github.com/ava-labs/subnet-evm/eth/tracers/logger"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/asm"
|
||||
|
||||
// force-load js tracers to trigger registration
|
||||
_ "github.com/ava-labs/subnet-evm/eth/tracers/js"
|
||||
)
|
||||
|
||||
func TestDefaults(t *testing.T) {
|
||||
@@ -335,19 +339,19 @@ func TestBlockhash(t *testing.T) {
|
||||
}
|
||||
|
||||
type stepCounter struct {
|
||||
inner *vm.JSONLogger
|
||||
inner *logger.JSONLogger
|
||||
steps int
|
||||
}
|
||||
|
||||
func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
func (s *stepCounter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
|
||||
|
||||
func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
func (s *stepCounter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
s.steps++
|
||||
// Enable this for more output
|
||||
// s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
|
||||
@@ -501,7 +505,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
// tracer := vm.NewJSONLogger(nil, os.Stdout)
|
||||
// tracer := logger.NewJSONLogger(nil, os.Stdout)
|
||||
// Execute(loopingCode, nil, &Config{
|
||||
// EVMConfig: vm.Config{
|
||||
// Debug: true,
|
||||
@@ -522,6 +526,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||
// TestEip2929Cases contains various testcases that are used for
|
||||
// EIP-2929 about gas repricings
|
||||
func TestEip2929Cases(t *testing.T) {
|
||||
t.Skip("Test only useful for generating documentation")
|
||||
id := 1
|
||||
prettyPrint := func(comment string, code []byte) {
|
||||
instrs := make([]string, 0)
|
||||
@@ -542,7 +547,7 @@ func TestEip2929Cases(t *testing.T) {
|
||||
Execute(code, nil, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
Tracer: vm.NewMarkdownLogger(nil, os.Stdout),
|
||||
Tracer: logger.NewMarkdownLogger(nil, os.Stdout),
|
||||
ExtraEips: []int{2929},
|
||||
},
|
||||
})
|
||||
@@ -692,7 +697,7 @@ func TestColdAccountAccessCost(t *testing.T) {
|
||||
want: 7600,
|
||||
},
|
||||
} {
|
||||
tracer := vm.NewStructLogger(nil)
|
||||
tracer := logger.NewStructLogger(nil)
|
||||
Execute(tc.code, nil, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
|
||||
@@ -64,10 +64,6 @@ func (st *Stack) push(d *uint256.Int) {
|
||||
// NOTE push limit (1024) is checked in baseCheck
|
||||
st.data = append(st.data, *d)
|
||||
}
|
||||
func (st *Stack) pushN(ds ...uint256.Int) {
|
||||
// FIXME: Is there a way to pass args by pointers.
|
||||
st.data = append(st.data, ds...)
|
||||
}
|
||||
|
||||
func (st *Stack) pop() (ret uint256.Int) {
|
||||
ret = st.data[len(st.data)-1]
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ func (api *PrivateAdminAPI) ExportChain(file string, first *uint64, last *uint64
|
||||
last = &head
|
||||
}
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
// File already exists. Allowing overwrite could be a DoS vecotor,
|
||||
// File already exists. Allowing overwrite could be a DoS vector,
|
||||
// since the 'file' may point to arbitrary paths on the drive
|
||||
return false, errors.New("location would overwrite an existing file")
|
||||
}
|
||||
|
||||
+1
-8
@@ -242,13 +242,6 @@ func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*typ
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
|
||||
if header := b.eth.blockchain.GetHeaderByHash(hash); header != nil {
|
||||
return b.eth.blockchain.GetTd(hash, header.Number.Uint64())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
|
||||
vmError := func() error { return nil }
|
||||
if vmConfig == nil {
|
||||
@@ -428,7 +421,7 @@ func (b *EthAPIBackend) GetMaxBlocksPerRequest() int64 {
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) {
|
||||
return b.eth.stateAtBlock(block, reexec, base, checkLive, preferDisk)
|
||||
return b.eth.StateAtBlock(block, reexec, base, checkLive, preferDisk)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
|
||||
|
||||
+31
-5
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/eth/tracers"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/internal/ethapi"
|
||||
"github.com/ava-labs/subnet-evm/internal/shutdowncheck"
|
||||
"github.com/ava-labs/subnet-evm/miner"
|
||||
"github.com/ava-labs/subnet-evm/node"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
@@ -96,12 +97,18 @@ type Ethereum struct {
|
||||
|
||||
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
|
||||
|
||||
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
||||
|
||||
stackRPCs []rpc.API
|
||||
|
||||
settings Settings // Settings for Ethereum API
|
||||
}
|
||||
|
||||
// New creates a new Ethereum object (including the
|
||||
// initialisation of the common Ethereum object)
|
||||
func New(stack *node.Node, config *Config,
|
||||
func New(
|
||||
stack *node.Node,
|
||||
config *Config,
|
||||
chainDb ethdb.Database,
|
||||
settings Settings,
|
||||
lastAcceptedHash common.Hash,
|
||||
@@ -136,15 +143,16 @@ func New(stack *node.Node, config *Config,
|
||||
eth := &Ethereum{
|
||||
config: config,
|
||||
chainDb: chainDb,
|
||||
eventMux: stack.EventMux(),
|
||||
eventMux: new(event.TypeMux),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: dummy.NewEngine(),
|
||||
engine: dummy.NewFaker(),
|
||||
closeBloomHandler: make(chan struct{}),
|
||||
networkID: config.NetworkId,
|
||||
etherbase: config.Miner.Etherbase,
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
||||
settings: settings,
|
||||
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
|
||||
}
|
||||
|
||||
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
||||
@@ -211,8 +219,10 @@ func New(stack *node.Node, config *Config,
|
||||
// Start the RPC service
|
||||
eth.netRPCService = ethapi.NewPublicNetAPI(eth.NetVersion())
|
||||
|
||||
// Register the backend on the node
|
||||
stack.RegisterAPIs(eth.APIs())
|
||||
eth.stackRPCs = stack.APIs()
|
||||
|
||||
// Successful startup; push a marker and check previous unclean shutdowns.
|
||||
eth.shutdownTracker.MarkStartup()
|
||||
|
||||
return eth, nil
|
||||
}
|
||||
@@ -225,6 +235,9 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||
// Append tracing APIs
|
||||
apis = append(apis, tracers.APIs(s.APIBackend)...)
|
||||
|
||||
// Add the APIs from the node
|
||||
apis = append(apis, s.stackRPCs...)
|
||||
|
||||
// Append all the local APIs and return
|
||||
return append(apis, []rpc.API{
|
||||
{
|
||||
@@ -232,29 +245,35 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||
Version: "1.0",
|
||||
Service: NewPublicEthereumAPI(s),
|
||||
Public: true,
|
||||
Name: "public-eth",
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute),
|
||||
Public: true,
|
||||
Name: "public-eth-filter",
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Version: "1.0",
|
||||
Service: NewPrivateAdminAPI(s),
|
||||
Name: "private-admin",
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: NewPublicDebugAPI(s),
|
||||
Public: true,
|
||||
Name: "public-debug",
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: NewPrivateDebugAPI(s),
|
||||
Name: "private-debug",
|
||||
}, {
|
||||
Namespace: "net",
|
||||
Version: "1.0",
|
||||
Service: s.netRPCService,
|
||||
Public: true,
|
||||
Name: "net",
|
||||
},
|
||||
}...)
|
||||
}
|
||||
@@ -309,6 +328,9 @@ func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
||||
func (s *Ethereum) Start() {
|
||||
// Start the bloom bits servicing goroutines
|
||||
s.startBloomHandlers(params.BloomBitsBlocks)
|
||||
|
||||
// Regularly update shutdown marker
|
||||
s.shutdownTracker.Start()
|
||||
}
|
||||
|
||||
// Stop implements node.Lifecycle, terminating all internal goroutines used by the
|
||||
@@ -320,6 +342,10 @@ func (s *Ethereum) Stop() error {
|
||||
s.txPool.Stop()
|
||||
s.blockchain.Stop()
|
||||
s.engine.Close()
|
||||
|
||||
// Clean shutdown marker as the last thing before closing db
|
||||
s.shutdownTracker.Stop()
|
||||
|
||||
s.chainDb.Close()
|
||||
s.eventMux.Stop()
|
||||
return nil
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/interfaces"
|
||||
"github.com/ava-labs/subnet-evm/rpc"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -61,7 +60,6 @@ type PublicFilterAPI struct {
|
||||
backend Backend
|
||||
mux *event.TypeMux
|
||||
quit chan struct{}
|
||||
chainDb ethdb.Database
|
||||
events *EventSystem
|
||||
filtersMu sync.Mutex
|
||||
filters map[rpc.ID]*filter
|
||||
@@ -72,7 +70,6 @@ type PublicFilterAPI struct {
|
||||
func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *PublicFilterAPI {
|
||||
api := &PublicFilterAPI{
|
||||
backend: backend,
|
||||
chainDb: backend.ChainDb(),
|
||||
events: NewEventSystem(backend, lightMode),
|
||||
filters: make(map[rpc.ID]*filter),
|
||||
timeout: timeout,
|
||||
|
||||
@@ -111,8 +111,7 @@ func NewOracle(backend OracleBackend, config Config) *Oracle {
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
|
||||
}
|
||||
if percent > 100 {
|
||||
} else if percent > 100 {
|
||||
percent = 100
|
||||
log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
|
||||
}
|
||||
@@ -131,6 +130,16 @@ func NewOracle(backend OracleBackend, config Config) *Oracle {
|
||||
minGasUsed = DefaultMinGasUsed
|
||||
log.Warn("Sanitizing invalid gasprice oracle min gas used", "provided", config.MinGasUsed, "updated", minGasUsed)
|
||||
}
|
||||
maxHeaderHistory := config.MaxHeaderHistory
|
||||
if maxHeaderHistory < 1 {
|
||||
maxHeaderHistory = 1
|
||||
log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", config.MaxHeaderHistory, "updated", maxHeaderHistory)
|
||||
}
|
||||
maxBlockHistory := config.MaxBlockHistory
|
||||
if maxBlockHistory < 1 {
|
||||
maxBlockHistory = 1
|
||||
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", config.MaxBlockHistory, "updated", maxBlockHistory)
|
||||
}
|
||||
|
||||
cache, _ := lru.New(2048)
|
||||
headEvent := make(chan core.ChainHeadEvent, 1)
|
||||
@@ -154,8 +163,8 @@ func NewOracle(backend OracleBackend, config Config) *Oracle {
|
||||
minGasUsed: minGasUsed,
|
||||
checkBlocks: blocks,
|
||||
percentile: percent,
|
||||
maxHeaderHistory: config.MaxHeaderHistory,
|
||||
maxBlockHistory: config.MaxBlockHistory,
|
||||
maxHeaderHistory: maxHeaderHistory,
|
||||
maxBlockHistory: maxBlockHistory,
|
||||
historyCache: cache,
|
||||
}
|
||||
}
|
||||
@@ -253,7 +262,11 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
|
||||
|
||||
// suggestDynamicFees estimates the gas tip and base fee based on a simple sampling method
|
||||
func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.Int, error) {
|
||||
head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
||||
head, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
headHash := head.Hash()
|
||||
|
||||
// If the latest gasprice is still available, return it.
|
||||
|
||||
@@ -124,7 +124,7 @@ func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, gen
|
||||
Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}},
|
||||
}
|
||||
|
||||
engine := dummy.NewEngine()
|
||||
engine := dummy.NewFaker()
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
genesis := gspec.MustCommit(db)
|
||||
|
||||
@@ -412,3 +412,40 @@ func TestSuggestGasPricePreSubnetEVM(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test to ensure that SuggestPrice does not panic prior to activation of SubnetEVM
|
||||
// Note: support for gas estimation without activated hard forks has been deprecated, but we still
|
||||
// ensure that the call does not panic.
|
||||
func TestSuggestGasPricePreAP3(t *testing.T) {
|
||||
config := Config{
|
||||
Blocks: 20,
|
||||
Percentile: 60,
|
||||
}
|
||||
|
||||
backend := newTestBackend(t, params.TestChainConfig, 3, func(i int, b *core.BlockGen) {
|
||||
b.SetCoinbase(common.Address{1})
|
||||
|
||||
signer := types.LatestSigner(params.TestChainConfig)
|
||||
gasPrice := big.NewInt(params.MinGasPrice)
|
||||
for j := 0; j < 50; j++ {
|
||||
tx := types.NewTx(&types.LegacyTx{
|
||||
Nonce: b.TxNonce(addr),
|
||||
To: &common.Address{},
|
||||
Gas: params.TxGas,
|
||||
GasPrice: gasPrice,
|
||||
Data: []byte{},
|
||||
})
|
||||
tx, err := types.SignTx(tx, signer, key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tx: %s", err)
|
||||
}
|
||||
b.AddTx(tx)
|
||||
}
|
||||
})
|
||||
oracle := NewOracle(backend, config)
|
||||
|
||||
_, err := oracle.SuggestPrice(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// StateAtBlock retrieves the state database associated with a certain block.
|
||||
// If no state is locally available for the given block, a number of blocks
|
||||
// are attempted to be reexecuted to generate the desired state. The optional
|
||||
// base layer statedb can be passed then it's regarded as the statedb of the
|
||||
// parent block.
|
||||
// Parameters:
|
||||
// - block: The block for which we want the state (== state at the stateRoot of the parent)
|
||||
// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state
|
||||
@@ -51,7 +56,7 @@ import (
|
||||
// storing trash persistently
|
||||
// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided,
|
||||
// it would be preferrable to start from a fresh state, if we have it on disk.
|
||||
func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
|
||||
func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
|
||||
var (
|
||||
current *types.Block
|
||||
database state.Database
|
||||
@@ -178,7 +183,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
|
||||
}
|
||||
// Lookup the statedb of parent block from the live database,
|
||||
// otherwise regenerate it on the flight.
|
||||
statedb, err := eth.stateAtBlock(parent, reexec, nil, true, false)
|
||||
statedb, err := eth.StateAtBlock(parent, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, vm.BlockContext{}, nil, err
|
||||
}
|
||||
|
||||
+12
-10
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/core/state"
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/eth/tracers/logger"
|
||||
"github.com/ava-labs/subnet-evm/ethdb"
|
||||
"github.com/ava-labs/subnet-evm/internal/ethapi"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
@@ -170,7 +171,7 @@ func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber
|
||||
|
||||
// TraceConfig holds extra parameters to trace functions.
|
||||
type TraceConfig struct {
|
||||
*vm.LogConfig
|
||||
*logger.Config
|
||||
Tracer *string
|
||||
Timeout *string
|
||||
Reexec *uint64
|
||||
@@ -179,7 +180,7 @@ type TraceConfig struct {
|
||||
// TraceCallConfig is the config for traceCall API. It holds one more
|
||||
// field to override the state for tracing.
|
||||
type TraceCallConfig struct {
|
||||
*vm.LogConfig
|
||||
*logger.Config
|
||||
Tracer *string
|
||||
Timeout *string
|
||||
Reexec *uint64
|
||||
@@ -188,7 +189,7 @@ type TraceCallConfig struct {
|
||||
|
||||
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
||||
type StdTraceConfig struct {
|
||||
vm.LogConfig
|
||||
logger.Config
|
||||
Reexec *uint64
|
||||
TxHash common.Hash
|
||||
}
|
||||
@@ -697,10 +698,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||
var traceConfig *TraceConfig
|
||||
if config != nil {
|
||||
traceConfig = &TraceConfig{
|
||||
LogConfig: config.LogConfig,
|
||||
Tracer: config.Tracer,
|
||||
Timeout: config.Timeout,
|
||||
Reexec: config.Reexec,
|
||||
Config: config.Config,
|
||||
Tracer: config.Tracer,
|
||||
Timeout: config.Timeout,
|
||||
Reexec: config.Reexec,
|
||||
}
|
||||
}
|
||||
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig)
|
||||
@@ -718,7 +719,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
)
|
||||
switch {
|
||||
case config == nil:
|
||||
tracer = vm.NewStructLogger(nil)
|
||||
tracer = logger.NewStructLogger(nil)
|
||||
case config.Tracer != nil:
|
||||
// Define a meaningful timeout of a single transaction trace
|
||||
timeout := defaultTraceTimeout
|
||||
@@ -742,7 +743,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
}
|
||||
|
||||
default:
|
||||
tracer = vm.NewStructLogger(config.LogConfig)
|
||||
tracer = logger.NewStructLogger(config.Config)
|
||||
}
|
||||
// Run the transaction with tracing enabled.
|
||||
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
|
||||
@@ -757,7 +758,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
|
||||
// Depending on the tracer type, format and return the output.
|
||||
switch tracer := tracer.(type) {
|
||||
case *vm.StructLogger:
|
||||
case *logger.StructLogger:
|
||||
// If the result contains a revert reason, return it.
|
||||
returnVal := fmt.Sprintf("%x", result.Return())
|
||||
if len(result.Revert()) > 0 {
|
||||
@@ -787,6 +788,7 @@ func APIs(backend Backend) []rpc.API {
|
||||
Version: "1.0",
|
||||
Service: NewAPI(backend),
|
||||
Public: false,
|
||||
Name: "debug-tracer",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -324,147 +323,6 @@ func TestTraceCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverriddenTraceCall(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Initialize test accounts
|
||||
accounts := newAccounts(3)
|
||||
genesis := &core.Genesis{Alloc: core.GenesisAlloc{
|
||||
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
|
||||
}}
|
||||
genBlocks := 10
|
||||
signer := types.HomesteadSigner{}
|
||||
api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
|
||||
// Transfer from account[0] to account[1]
|
||||
// value: 1000 wei
|
||||
// fee: 0 wei
|
||||
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, new(big.Int).Add(b.BaseFee(), big.NewInt(int64(500*params.GWei))), nil), signer, accounts[0].key)
|
||||
b.AddTx(tx)
|
||||
}))
|
||||
randomAccounts, tracer := newAccounts(3), "callTracerJs"
|
||||
|
||||
testSuite := []struct {
|
||||
blockNumber rpc.BlockNumber
|
||||
call ethapi.TransactionArgs
|
||||
config *TraceCallConfig
|
||||
expectErr error
|
||||
expect *callTrace
|
||||
}{
|
||||
// Succcessful call with state overriding
|
||||
{
|
||||
blockNumber: rpc.PendingBlockNumber,
|
||||
call: ethapi.TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &randomAccounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
config: &TraceCallConfig{
|
||||
Tracer: &tracer,
|
||||
StateOverrides: ðapi.StateOverride{
|
||||
randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
|
||||
},
|
||||
},
|
||||
expectErr: nil,
|
||||
expect: &callTrace{
|
||||
Type: "CALL",
|
||||
From: randomAccounts[0].addr,
|
||||
To: randomAccounts[1].addr,
|
||||
Gas: newRPCUint64(24979000),
|
||||
GasUsed: newRPCUint64(0),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
},
|
||||
// Invalid call without state overriding
|
||||
{
|
||||
blockNumber: rpc.PendingBlockNumber,
|
||||
call: ethapi.TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &randomAccounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
config: &TraceCallConfig{
|
||||
Tracer: &tracer,
|
||||
},
|
||||
expectErr: core.ErrInsufficientFunds,
|
||||
expect: nil,
|
||||
},
|
||||
// Successful simple contract call
|
||||
//
|
||||
// // SPDX-License-Identifier: GPL-3.0
|
||||
//
|
||||
// pragma solidity >=0.7.0 <0.8.0;
|
||||
//
|
||||
// /**
|
||||
// * @title Storage
|
||||
// * @dev Store & retrieve value in a variable
|
||||
// */
|
||||
// contract Storage {
|
||||
// uint256 public number;
|
||||
// constructor() {
|
||||
// number = block.number;
|
||||
// }
|
||||
// }
|
||||
{
|
||||
blockNumber: rpc.PendingBlockNumber,
|
||||
call: ethapi.TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &randomAccounts[2].addr,
|
||||
Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number()
|
||||
},
|
||||
config: &TraceCallConfig{
|
||||
Tracer: &tracer,
|
||||
StateOverrides: ðapi.StateOverride{
|
||||
randomAccounts[2].addr: ethapi.OverrideAccount{
|
||||
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")),
|
||||
StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: nil,
|
||||
expect: &callTrace{
|
||||
Type: "CALL",
|
||||
From: randomAccounts[0].addr,
|
||||
To: randomAccounts[2].addr,
|
||||
Input: hexutil.Bytes(common.Hex2Bytes("8381f58a")),
|
||||
Output: hexutil.Bytes(common.BigToHash(big.NewInt(123)).Bytes()),
|
||||
Gas: newRPCUint64(24978936),
|
||||
GasUsed: newRPCUint64(2283),
|
||||
Value: (*hexutil.Big)(big.NewInt(0)),
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, testspec := range testSuite {
|
||||
result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
|
||||
if testspec.expectErr != nil {
|
||||
if err == nil {
|
||||
t.Errorf("test %d: want error %v, have nothing", i, testspec.expectErr)
|
||||
continue
|
||||
}
|
||||
if !errors.Is(err, testspec.expectErr) {
|
||||
t.Errorf("test %d: error mismatch, want %v, have %v", i, testspec.expectErr, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("test %d: want no error, have %v", i, err)
|
||||
continue
|
||||
}
|
||||
ret := new(callTrace)
|
||||
if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
|
||||
t.Fatalf("test %d: failed to unmarshal trace result: %v", i, err)
|
||||
}
|
||||
if !jsonEqual(ret, testspec.expect) {
|
||||
// uncomment this for easier debugging
|
||||
// have, _ := json.MarshalIndent(ret, "", " ")
|
||||
// want, _ := json.MarshalIndent(testspec.expect, "", " ")
|
||||
// t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
|
||||
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, testspec.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -629,29 +487,3 @@ func newAccounts(n int) (accounts Accounts) {
|
||||
sort.Sort(accounts)
|
||||
return accounts
|
||||
}
|
||||
|
||||
func newRPCBalance(balance *big.Int) **hexutil.Big {
|
||||
rpcBalance := (*hexutil.Big)(balance)
|
||||
return &rpcBalance
|
||||
}
|
||||
|
||||
func newRPCUint64(number uint64) *hexutil.Uint64 {
|
||||
rpcUint64 := hexutil.Uint64(number)
|
||||
return &rpcUint64
|
||||
}
|
||||
|
||||
func newRPCBytes(bytes []byte) *hexutil.Bytes {
|
||||
rpcBytes := hexutil.Bytes(bytes)
|
||||
return &rpcBytes
|
||||
}
|
||||
|
||||
func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.Hash {
|
||||
if len(keys) != len(vals) {
|
||||
panic("invalid input")
|
||||
}
|
||||
m := make(map[common.Hash]common.Hash)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
m[keys[i]] = vals[i]
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2017 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/>.
|
||||
|
||||
// 4byteTracer searches for 4byte-identifiers, and collects them for post-processing.
|
||||
// It collects the methods identifiers along with the size of the supplied data, so
|
||||
// a reversed signature can be matched against the size of the data.
|
||||
//
|
||||
// Example:
|
||||
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
|
||||
// {
|
||||
// 0x27dc297e-128: 1,
|
||||
// 0x38cc4831-0: 2,
|
||||
// 0x524f3889-96: 1,
|
||||
// 0xadf59f99-288: 1,
|
||||
// 0xc281d19e-0: 1
|
||||
// }
|
||||
{
|
||||
// ids aggregates the 4byte ids found.
|
||||
ids : {},
|
||||
|
||||
// store save the given indentifier and datasize.
|
||||
store: function(id, size){
|
||||
var key = "" + toHex(id) + "-" + size;
|
||||
this.ids[key] = this.ids[key] + 1 || 1;
|
||||
},
|
||||
|
||||
enter: function(frame) {
|
||||
// Skip any pre-compile invocations, those are just fancy opcodes
|
||||
if (isPrecompiled(frame.getTo())) {
|
||||
return;
|
||||
}
|
||||
var input = frame.getInput()
|
||||
if (input.length >= 4) {
|
||||
this.store(slice(input, 0, 4), input.length - 4);
|
||||
}
|
||||
},
|
||||
|
||||
exit: function(frameResult) {},
|
||||
|
||||
// fault is invoked when the actual execution of an opcode fails.
|
||||
fault: function(log, db) {},
|
||||
|
||||
// result is invoked when all the opcodes have been iterated over and returns
|
||||
// the final result of the tracing.
|
||||
result: function(ctx) {
|
||||
// Save the outer calldata also
|
||||
if (ctx.input.length >= 4) {
|
||||
this.store(slice(ctx.input, 0, 4), ctx.input.length-4)
|
||||
}
|
||||
return this.ids;
|
||||
},
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// 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
|
||||
// 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/>.
|
||||
|
||||
|
||||
// callFrameTracer uses the new call frame tracing methods to report useful information
|
||||
// about internal messages of a transaction.
|
||||
{
|
||||
callstack: [{}],
|
||||
fault: function(log, db) {},
|
||||
result: function(ctx, db) {
|
||||
// Prepare outer message info
|
||||
var result = {
|
||||
type: ctx.type,
|
||||
from: toHex(ctx.from),
|
||||
to: toHex(ctx.to),
|
||||
value: '0x' + ctx.value.toString(16),
|
||||
gas: '0x' + bigInt(ctx.gas).toString(16),
|
||||
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
|
||||
input: toHex(ctx.input),
|
||||
output: toHex(ctx.output),
|
||||
}
|
||||
if (this.callstack[0].calls !== undefined) {
|
||||
result.calls = this.callstack[0].calls
|
||||
}
|
||||
if (this.callstack[0].error !== undefined) {
|
||||
result.error = this.callstack[0].error
|
||||
} else if (ctx.error !== undefined) {
|
||||
result.error = ctx.error
|
||||
}
|
||||
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
|
||||
delete result.output
|
||||
}
|
||||
|
||||
return this.finalize(result)
|
||||
},
|
||||
enter: function(frame) {
|
||||
var call = {
|
||||
type: frame.getType(),
|
||||
from: toHex(frame.getFrom()),
|
||||
to: toHex(frame.getTo()),
|
||||
input: toHex(frame.getInput()),
|
||||
gas: '0x' + bigInt(frame.getGas()).toString('16'),
|
||||
}
|
||||
if (frame.getValue() !== undefined){
|
||||
call.value='0x' + bigInt(frame.getValue()).toString(16)
|
||||
}
|
||||
this.callstack.push(call)
|
||||
},
|
||||
exit: function(frameResult) {
|
||||
var len = this.callstack.length
|
||||
if (len > 1) {
|
||||
var call = this.callstack.pop()
|
||||
call.gasUsed = '0x' + bigInt(frameResult.getGasUsed()).toString('16')
|
||||
var error = frameResult.getError()
|
||||
if (error === undefined) {
|
||||
call.output = toHex(frameResult.getOutput())
|
||||
} else {
|
||||
call.error = error
|
||||
if (call.type === 'CREATE' || call.type === 'CREATE2') {
|
||||
delete call.to
|
||||
}
|
||||
}
|
||||
len -= 1
|
||||
if (this.callstack[len-1].calls === undefined) {
|
||||
this.callstack[len-1].calls = []
|
||||
}
|
||||
this.callstack[len-1].calls.push(call)
|
||||
}
|
||||
},
|
||||
// finalize recreates a call object using the final desired field oder for json
|
||||
// serialization. This is a nicety feature to pass meaningfully ordered results
|
||||
// to users who don't interpret it, just display it.
|
||||
finalize: function(call) {
|
||||
var sorted = {
|
||||
type: call.type,
|
||||
from: call.from,
|
||||
to: call.to,
|
||||
value: call.value,
|
||||
gas: call.gas,
|
||||
gasUsed: call.gasUsed,
|
||||
input: call.input,
|
||||
output: call.output,
|
||||
error: call.error,
|
||||
time: call.time,
|
||||
calls: call.calls,
|
||||
}
|
||||
for (var key in sorted) {
|
||||
if (sorted[key] === undefined) {
|
||||
delete sorted[key]
|
||||
}
|
||||
}
|
||||
if (sorted.calls !== undefined) {
|
||||
for (var i=0; i<sorted.calls.length; i++) {
|
||||
sorted.calls[i] = this.finalize(sorted.calls[i])
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
}
|
||||
+165
-7
@@ -1,4 +1,30 @@
|
||||
package testing
|
||||
// (c) 2020-2021, 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 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
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tracetest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -15,16 +41,69 @@ import (
|
||||
"github.com/ava-labs/subnet-evm/core/types"
|
||||
"github.com/ava-labs/subnet-evm/core/vm"
|
||||
"github.com/ava-labs/subnet-evm/eth/tracers"
|
||||
"github.com/ava-labs/subnet-evm/params"
|
||||
"github.com/ava-labs/subnet-evm/tests"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
||||
// Force-load the native, to trigger registration
|
||||
_ "github.com/ava-labs/subnet-evm/eth/tracers/js"
|
||||
_ "github.com/ava-labs/subnet-evm/eth/tracers/native"
|
||||
)
|
||||
|
||||
// To generate a new callTracer test, copy paste the makeTest method below into
|
||||
// a Geth console and call it with a transaction hash you which to export.
|
||||
|
||||
/*
|
||||
// makeTest generates a callTracer test by running a prestate reassembled and a
|
||||
// call trace run, assembling all the gathered information into a test case.
|
||||
var makeTest = function(tx, rewind) {
|
||||
// Generate the genesis block from the block, transaction and prestate data
|
||||
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
|
||||
var genesis = eth.getBlock(block.parentHash);
|
||||
|
||||
delete genesis.gasUsed;
|
||||
delete genesis.logsBloom;
|
||||
delete genesis.parentHash;
|
||||
delete genesis.receiptsRoot;
|
||||
delete genesis.sha3Uncles;
|
||||
delete genesis.size;
|
||||
delete genesis.transactions;
|
||||
delete genesis.transactionsRoot;
|
||||
delete genesis.uncles;
|
||||
|
||||
genesis.gasLimit = genesis.gasLimit.toString();
|
||||
genesis.number = genesis.number.toString();
|
||||
genesis.timestamp = genesis.timestamp.toString();
|
||||
|
||||
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
|
||||
for (var key in genesis.alloc) {
|
||||
genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
|
||||
}
|
||||
genesis.config = admin.nodeInfo.protocols.eth.config;
|
||||
|
||||
// Generate the call trace and produce the test input
|
||||
var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
|
||||
delete result.time;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
genesis: genesis,
|
||||
context: {
|
||||
number: block.number.toString(),
|
||||
difficulty: block.difficulty,
|
||||
timestamp: block.timestamp.toString(),
|
||||
gasLimit: block.gasLimit.toString(),
|
||||
miner: block.miner,
|
||||
},
|
||||
input: eth.getRawTransaction(tx),
|
||||
result: result,
|
||||
}, null, 2));
|
||||
}
|
||||
*/
|
||||
|
||||
type callContext struct {
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty"`
|
||||
@@ -70,7 +149,7 @@ func TestCallTracerNative(t *testing.T) {
|
||||
}
|
||||
|
||||
func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||
files, err := ioutil.ReadDir(filepath.Join("..", "testdata", dirPath))
|
||||
files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||
}
|
||||
@@ -87,7 +166,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||
tx = new(types.Transaction)
|
||||
)
|
||||
// Call tracer test found, read if from disk
|
||||
if blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", dirPath, file.Name())); err != nil {
|
||||
if blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
|
||||
t.Fatalf("failed to read testcase: %v", err)
|
||||
} else if err := json.Unmarshal(blob, test); err != nil {
|
||||
t.Fatalf("failed to parse testcase: %v", err)
|
||||
@@ -97,7 +176,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||
}
|
||||
// Configure a blockchain with the given prestate
|
||||
var (
|
||||
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), new(big.Int).SetUint64((uint64(test.Context.Time))))
|
||||
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), new(big.Int).SetUint64(uint64(test.Context.Time)))
|
||||
origin, _ = signer.Sender(tx)
|
||||
txContext = vm.TxContext{
|
||||
Origin: origin,
|
||||
@@ -176,7 +255,7 @@ func camel(str string) string {
|
||||
}
|
||||
|
||||
func BenchmarkTracers(b *testing.B) {
|
||||
files, err := ioutil.ReadDir(filepath.Join("..", "testdata", "call_tracer"))
|
||||
files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer"))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||
}
|
||||
@@ -186,7 +265,7 @@ func BenchmarkTracers(b *testing.B) {
|
||||
}
|
||||
file := file // capture range variable
|
||||
b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
|
||||
blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", "call_tracer", file.Name()))
|
||||
blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to read testcase: %v", err)
|
||||
}
|
||||
@@ -205,7 +284,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
|
||||
b.Fatalf("failed to parse testcase input: %v", err)
|
||||
}
|
||||
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), new(big.Int).SetUint64((uint64(test.Context.Time))))
|
||||
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), new(big.Int).SetUint64(uint64(test.Context.Time)))
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
@@ -245,3 +324,82 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||
statedb.RevertToSnapshot(snap)
|
||||
}
|
||||
}
|
||||
|
||||
// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
|
||||
// Tx to A, A calls B with zero value. B does not already exist.
|
||||
// Expected: that enter/exit is invoked and the inner call is shown in the result
|
||||
func TestZeroValueToNotExitCall(t *testing.T) {
|
||||
to := common.HexToAddress("0x00000000000000000000000000000000deadbeef")
|
||||
privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
|
||||
if err != nil {
|
||||
t.Fatalf("err %v", err)
|
||||
}
|
||||
signer := types.NewEIP155Signer(big.NewInt(1))
|
||||
tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
|
||||
GasPrice: big.NewInt(0),
|
||||
Gas: 50000,
|
||||
To: &to,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err %v", err)
|
||||
}
|
||||
origin, _ := signer.Sender(tx)
|
||||
txContext := vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: big.NewInt(1),
|
||||
}
|
||||
context := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: common.Address{},
|
||||
BlockNumber: new(big.Int).SetUint64(8000000),
|
||||
Time: new(big.Int).SetUint64(5),
|
||||
Difficulty: big.NewInt(0x30000),
|
||||
GasLimit: uint64(6000000),
|
||||
}
|
||||
code := []byte{
|
||||
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
|
||||
byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
|
||||
byte(vm.CALL),
|
||||
}
|
||||
alloc := core.GenesisAlloc{
|
||||
to: core.GenesisAccount{
|
||||
Nonce: 1,
|
||||
Code: code,
|
||||
},
|
||||
origin: core.GenesisAccount{
|
||||
Nonce: 0,
|
||||
Balance: big.NewInt(500000000000000),
|
||||
},
|
||||
}
|
||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := tracers.New("callTracer", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, params.TestPreSubnetEVMConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if _, err = st.TransitionDb(); err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
// Retrieve the trace result and compare against the etalon
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve trace result: %v", err)
|
||||
}
|
||||
have := new(callTrace)
|
||||
if err := json.Unmarshal(res, have); err != nil {
|
||||
t.Fatalf("failed to unmarshal trace result: %v", err)
|
||||
}
|
||||
wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
|
||||
want := new(callTrace)
|
||||
json.Unmarshal([]byte(wantStr), want)
|
||||
if !jsonEqual(have, want) {
|
||||
t.Error("have != want")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -58,7 +58,7 @@
|
||||
"result": {
|
||||
"calls": [
|
||||
{
|
||||
"error": "invalid opcode: opcode 0xfe not defined",
|
||||
"error": "invalid opcode: INVALID",
|
||||
"from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
|
||||
"gas": "0x75fe3",
|
||||
"gasUsed": "0x75fe3",
|
||||
+1
-1
@@ -58,7 +58,7 @@
|
||||
"result": {
|
||||
"calls": [
|
||||
{
|
||||
"error": "invalid opcode: opcode 0xfe not defined",
|
||||
"error": "invalid opcode: INVALID",
|
||||
"from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
|
||||
"gas": "0x75fe3",
|
||||
"gasUsed": "0x75fe3",
|
||||
File diff suppressed because one or more lines are too long
+10
@@ -1,3 +1,13 @@
|
||||
// (c) 2020-2021, 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 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
File diff suppressed because one or more lines are too long
+10
@@ -1,3 +1,13 @@
|
||||
// (c) 2020-2021, 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 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// (c) 2019-2020, Ava Labs, Inc.
|
||||
// (c) 2020-2021, Ava Labs, Inc.
|
||||
//
|
||||
// This file is a derived work, based on the go-ethereum library whose original
|
||||
// notices appear below.
|
||||
+10
@@ -1,3 +1,13 @@
|
||||
// (c) 2020-2021, 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 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user