From 78745551c077bf54151202138c2629f288769561 Mon Sep 17 00:00:00 2001 From: Determinant Date: Tue, 15 Sep 2020 23:55:34 -0400 Subject: WIP: geth-tavum --- core/vm/common.go | 31 +-- core/vm/contract.go | 36 ++- core/vm/contracts.go | 487 ++++++++++++++++++++++++++++++++++++- core/vm/eips.go | 82 +++++-- core/vm/gas.go | 10 +- core/vm/gas_table.go | 76 +++--- core/vm/gen_structlog.go | 26 +- core/vm/int_pool_verifier.go | 31 --- core/vm/int_pool_verifier_empty.go | 23 -- core/vm/intpool.go | 106 -------- core/vm/logger.go | 189 ++++++++++---- core/vm/logger_json.go | 19 +- core/vm/memory.go | 9 +- core/vm/stack.go | 84 +++++-- 14 files changed, 869 insertions(+), 340 deletions(-) delete mode 100644 core/vm/int_pool_verifier.go delete mode 100644 core/vm/int_pool_verifier_empty.go delete mode 100644 core/vm/intpool.go (limited to 'core/vm') diff --git a/core/vm/common.go b/core/vm/common.go index ead30fc..90ba4a4 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -17,15 +17,14 @@ package vm import ( - "math/big" - - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/holiman/uint256" ) // calcMemSize64 calculates the required memory size, and returns // the size and whether the result overflowed uint64 -func calcMemSize64(off, l *big.Int) (uint64, bool) { +func calcMemSize64(off, l *uint256.Int) (uint64, bool) { if !l.IsUint64() { return 0, true } @@ -35,16 +34,16 @@ func calcMemSize64(off, l *big.Int) (uint64, bool) { // calcMemSize64WithUint calculates the required memory size, and returns // the size and whether the result overflowed uint64 // Identical to calcMemSize64, but length is a uint64 -func calcMemSize64WithUint(off *big.Int, length64 uint64) (uint64, bool) { +func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) { // if length is zero, memsize is always zero, regardless of offset if length64 == 0 { return 0, false } // Check that offset doesn't overflow - if !off.IsUint64() { + offset64, overflow := off.Uint64WithOverflow() + if overflow { return 0, true } - offset64 := off.Uint64() val := offset64 + length64 // if value < either of it's parts, then it overflowed return val, val < offset64 @@ -64,22 +63,6 @@ func getData(data []byte, start uint64, size uint64) []byte { return common.RightPadBytes(data[start:end], int(size)) } -// getDataBig returns a slice from the data based on the start and size and pads -// up to size with zero's. This function is overflow safe. -func getDataBig(data []byte, start *big.Int, size *big.Int) []byte { - dlen := big.NewInt(int64(len(data))) - - s := math.BigMin(start, dlen) - e := math.BigMin(new(big.Int).Add(s, size), dlen) - return common.RightPadBytes(data[s.Uint64():e.Uint64()], int(size.Uint64())) -} - -// bigUint64 returns the integer casted to a uint64 and returns whether it -// overflowed in the process. -func bigUint64(v *big.Int) (uint64, bool) { - return v.Uint64(), !v.IsUint64() -} - // toWordSize returns the ceiled word size required for memory expansion. func toWordSize(size uint64) uint64 { if size > math.MaxUint64-31 { diff --git a/core/vm/contract.go b/core/vm/contract.go index ed17402..915193d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -19,7 +19,8 @@ package vm import ( "math/big" - "github.com/ava-labs/go-ethereum/common" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" ) // ContractRef is a reference to the contract's backing object @@ -81,18 +82,43 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin return c } -func (c *Contract) validJumpdest(dest *big.Int) bool { - udest := dest.Uint64() +func (c *Contract) validJumpdest(dest *uint256.Int) bool { + udest, overflow := dest.Uint64WithOverflow() // PC cannot go beyond len(code) and certainly can't be bigger than 63bits. // Don't bother checking for JUMPDEST in that case. - if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) { + if overflow || udest >= uint64(len(c.Code)) { return false } // Only JUMPDESTs allowed for destinations if OpCode(c.Code[udest]) != JUMPDEST { return false } + return c.isCode(udest) +} + +func (c *Contract) validJumpSubdest(udest uint64) bool { + // PC cannot go beyond len(code) and certainly can't be bigger than 63 bits. + // Don't bother checking for BEGINSUB in that case. + if int64(udest) < 0 || udest >= uint64(len(c.Code)) { + return false + } + // Only BEGINSUBs allowed for destinations + if OpCode(c.Code[udest]) != BEGINSUB { + return false + } + return c.isCode(udest) +} + +// isCode returns true if the provided PC location is an actual opcode, as +// opposed to a data-segment following a PUSHN operation. +func (c *Contract) isCode(udest uint64) bool { + // Do we already have an analysis laying around? + if c.analysis != nil { + return c.analysis.codeSegment(udest) + } // Do we have a contract hash already? + // If we do have a hash, that means it's a 'regular' contract. For regular + // contracts ( not temporary initcode), we store the analysis in a map if c.CodeHash != (common.Hash{}) { // Does parent context have the analysis? analysis, exist := c.jumpdests[c.CodeHash] @@ -102,6 +128,8 @@ func (c *Contract) validJumpdest(dest *big.Int) bool { analysis = codeBitmap(c.Code) c.jumpdests[c.CodeHash] = analysis } + // Also stash it in current contract for faster access + c.analysis = analysis return analysis.codeSegment(udest) } // We don't have the code hash, most likely a piece of initcode not already diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 54eab4e..9827bac 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -23,11 +23,14 @@ import ( "math/big" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" - "github.com/ava-labs/go-ethereum/crypto" - "github.com/ava-labs/go-ethereum/crypto/blake2b" - "github.com/ava-labs/go-ethereum/crypto/bn256" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/blake2b" + "github.com/ethereum/go-ethereum/crypto/bls12381" + "github.com/ethereum/go-ethereum/crypto/bn256" + + //lint:ignore SA1019 Needed for precompile "golang.org/x/crypto/ripemd160" ) @@ -75,13 +78,42 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } +// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum +// contracts used in the Yolo v1 test release. +var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{10}): &bls12381G1Add{}, + common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{13}): &bls12381G2Add{}, + common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{16}): &bls12381Pairing{}, + common.BytesToAddress([]byte{17}): &bls12381MapG1{}, + common.BytesToAddress([]byte{18}): &bls12381MapG2{}, +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. -func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { - gas := p.RequiredGas(input) - if contract.UseGas(gas) { - return p.Run(input) +// It returns +// - the returned bytes, +// - the _remaining_ gas, +// - any error that occurred +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + gasCost := p.RequiredGas(input) + if suppliedGas < gasCost { + return nil, 0, ErrOutOfGas } - return nil, ErrOutOfGas + suppliedGas -= gasCost + output, err := p.Run(input) + return output, suppliedGas, err } // ECRECOVER implemented as a native contract. @@ -106,8 +138,13 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) { if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { return nil, nil } + // We must make sure not to modify the 'input', so placing the 'v' along with + // the signature needs to be done on a new allocation + sig := make([]byte, 65) + copy(sig, input[64:128]) + sig[64] = v // v needs to be at the end for libsecp256k1 - pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v)) + pubKey, err := crypto.Ecrecover(input[:32], sig) // make sure the public key is a valid one if err != nil { return nil, nil @@ -166,6 +203,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { type bigModExp struct{} var ( + big0 = big.NewInt(0) big1 = big.NewInt(1) big4 = big.NewInt(4) big8 = big.NewInt(8) @@ -458,7 +496,7 @@ var ( ) func (c *blake2F) Run(input []byte) ([]byte, error) { - // Make sure the input is valid (correct lenth and final flag) + // Make sure the input is valid (correct length and final flag) if len(input) != blake2FInputLength { return nil, errBlake2FInvalidInputLength } @@ -495,3 +533,428 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { } return output, nil } + +var ( + errBLS12381InvalidInputLength = errors.New("invalid input length") + errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes") + errBLS12381G1PointSubgroup = errors.New("g1 point is not on correct subgroup") + errBLS12381G2PointSubgroup = errors.New("g2 point is not on correct subgroup") +) + +// bls12381G1Add implements EIP-2537 G1Add precompile. +type bls12381G1Add struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1Add) RequiredGas(input []byte) uint64 { + return params.Bls12381G1AddGas +} + +func (c *bls12381G1Add) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G1Add precompile. + // > G1 addition call expects `256` bytes as an input that is interpreted as byte concatenation of two G1 points (`128` bytes each). + // > Output is an encoding of addition operation result - single G1 point (`128` bytes). + if len(input) != 256 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0, p1 *bls12381.PointG1 + + // Initialize G1 + g := bls12381.NewG1() + + // Decode G1 point p_0 + if p0, err = g.DecodePoint(input[:128]); err != nil { + return nil, err + } + // Decode G1 point p_1 + if p1, err = g.DecodePoint(input[128:]); err != nil { + return nil, err + } + + // Compute r = p_0 + p_1 + r := g.New() + g.Add(r, p0, p1) + + // Encode the G1 point result into 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381G1Mul implements EIP-2537 G1Mul precompile. +type bls12381G1Mul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1Mul) RequiredGas(input []byte) uint64 { + return params.Bls12381G1MulGas +} + +func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G1Mul precompile. + // > G1 multiplication call expects `160` bytes as an input that is interpreted as byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiplication operation result - single G1 point (`128` bytes). + if len(input) != 160 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0 *bls12381.PointG1 + + // Initialize G1 + g := bls12381.NewG1() + + // Decode G1 point + if p0, err = g.DecodePoint(input[:128]); err != nil { + return nil, err + } + // Decode scalar value + e := new(big.Int).SetBytes(input[128:]) + + // Compute r = e * p_0 + r := g.New() + g.MulScalar(r, p0, e) + + // Encode the G1 point into 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile. +type bls12381G1MultiExp struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { + // Calculate G1 point, scalar value pair length + k := len(input) / 160 + if k == 0 { + // Return 0 gas for small input length + return 0 + } + // Lookup discount value for G1 point, scalar value pair length + var discount uint64 + if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen { + discount = params.Bls12381MultiExpDiscountTable[k-1] + } else { + discount = params.Bls12381MultiExpDiscountTable[dLen-1] + } + // Calculate gas and return the result + return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000 +} + +func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G1MultiExp precompile. + // G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). + // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes). + k := len(input) / 160 + if len(input) == 0 || len(input)%160 != 0 { + return nil, errBLS12381InvalidInputLength + } + var err error + points := make([]*bls12381.PointG1, k) + scalars := make([]*big.Int, k) + + // Initialize G1 + g := bls12381.NewG1() + + // Decode point scalar pairs + for i := 0; i < k; i++ { + off := 160 * i + t0, t1, t2 := off, off+128, off+160 + // Decode G1 point + if points[i], err = g.DecodePoint(input[t0:t1]); err != nil { + return nil, err + } + // Decode scalar value + scalars[i] = new(big.Int).SetBytes(input[t1:t2]) + } + + // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) + r := g.New() + g.MultiExp(r, points, scalars) + + // Encode the G1 point to 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381G2Add implements EIP-2537 G2Add precompile. +type bls12381G2Add struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2Add) RequiredGas(input []byte) uint64 { + return params.Bls12381G2AddGas +} + +func (c *bls12381G2Add) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G2Add precompile. + // > G2 addition call expects `512` bytes as an input that is interpreted as byte concatenation of two G2 points (`256` bytes each). + // > Output is an encoding of addition operation result - single G2 point (`256` bytes). + if len(input) != 512 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0, p1 *bls12381.PointG2 + + // Initialize G2 + g := bls12381.NewG2() + r := g.New() + + // Decode G2 point p_0 + if p0, err = g.DecodePoint(input[:256]); err != nil { + return nil, err + } + // Decode G2 point p_1 + if p1, err = g.DecodePoint(input[256:]); err != nil { + return nil, err + } + + // Compute r = p_0 + p_1 + g.Add(r, p0, p1) + + // Encode the G2 point into 256 bytes + return g.EncodePoint(r), nil +} + +// bls12381G2Mul implements EIP-2537 G2Mul precompile. +type bls12381G2Mul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2Mul) RequiredGas(input []byte) uint64 { + return params.Bls12381G2MulGas +} + +func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G2MUL precompile logic. + // > G2 multiplication call expects `288` bytes as an input that is interpreted as byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiplication operation result - single G2 point (`256` bytes). + if len(input) != 288 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0 *bls12381.PointG2 + + // Initialize G2 + g := bls12381.NewG2() + + // Decode G2 point + if p0, err = g.DecodePoint(input[:256]); err != nil { + return nil, err + } + // Decode scalar value + e := new(big.Int).SetBytes(input[256:]) + + // Compute r = e * p_0 + r := g.New() + g.MulScalar(r, p0, e) + + // Encode the G2 point into 256 bytes + return g.EncodePoint(r), nil +} + +// bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile. +type bls12381G2MultiExp struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { + // Calculate G2 point, scalar value pair length + k := len(input) / 288 + if k == 0 { + // Return 0 gas for small input length + return 0 + } + // Lookup discount value for G2 point, scalar value pair length + var discount uint64 + if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen { + discount = params.Bls12381MultiExpDiscountTable[k-1] + } else { + discount = params.Bls12381MultiExpDiscountTable[dLen-1] + } + // Calculate gas and return the result + return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000 +} + +func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G2MultiExp precompile logic + // > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes). + k := len(input) / 288 + if len(input) == 0 || len(input)%288 != 0 { + return nil, errBLS12381InvalidInputLength + } + var err error + points := make([]*bls12381.PointG2, k) + scalars := make([]*big.Int, k) + + // Initialize G2 + g := bls12381.NewG2() + + // Decode point scalar pairs + for i := 0; i < k; i++ { + off := 288 * i + t0, t1, t2 := off, off+256, off+288 + // Decode G1 point + if points[i], err = g.DecodePoint(input[t0:t1]); err != nil { + return nil, err + } + // Decode scalar value + scalars[i] = new(big.Int).SetBytes(input[t1:t2]) + } + + // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) + r := g.New() + g.MultiExp(r, points, scalars) + + // Encode the G2 point to 256 bytes. + return g.EncodePoint(r), nil +} + +// bls12381Pairing implements EIP-2537 Pairing precompile. +type bls12381Pairing struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381Pairing) RequiredGas(input []byte) uint64 { + return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas +} + +func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 Pairing precompile logic. + // > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure: + // > - `128` bytes of G1 point encoding + // > - `256` bytes of G2 point encoding + // > Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise + // > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively). + k := len(input) / 384 + if len(input) == 0 || len(input)%384 != 0 { + return nil, errBLS12381InvalidInputLength + } + + // Initialize BLS12-381 pairing engine + e := bls12381.NewPairingEngine() + g1, g2 := e.G1, e.G2 + + // Decode pairs + for i := 0; i < k; i++ { + off := 384 * i + t0, t1, t2 := off, off+128, off+384 + + // Decode G1 point + p1, err := g1.DecodePoint(input[t0:t1]) + if err != nil { + return nil, err + } + // Decode G2 point + p2, err := g2.DecodePoint(input[t1:t2]) + if err != nil { + return nil, err + } + + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !g1.InCorrectSubgroup(p1) { + return nil, errBLS12381G1PointSubgroup + } + if !g2.InCorrectSubgroup(p2) { + return nil, errBLS12381G2PointSubgroup + } + + // Update pairing engine with G1 and G2 ponits + e.AddPair(p1, p2) + } + // Prepare 32 byte output + out := make([]byte, 32) + + // Compute pairing and set the result + if e.Check() { + out[31] = 1 + } + return out, nil +} + +// decodeBLS12381FieldElement decodes BLS12-381 elliptic curve field element. +// Removes top 16 bytes of 64 byte input. +func decodeBLS12381FieldElement(in []byte) ([]byte, error) { + if len(in) != 64 { + return nil, errors.New("invalid field element length") + } + // check top bytes + for i := 0; i < 16; i++ { + if in[i] != byte(0x00) { + return nil, errBLS12381InvalidFieldElementTopBytes + } + } + out := make([]byte, 48) + copy(out[:], in[16:]) + return out, nil +} + +// bls12381MapG1 implements EIP-2537 MapG1 precompile. +type bls12381MapG1 struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381MapG1) RequiredGas(input []byte) uint64 { + return params.Bls12381MapG1Gas +} + +func (c *bls12381MapG1) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 Map_To_G1 precompile. + // > Field-to-curve call expects `64` bytes an an input that is interpreted as a an element of the base field. + // > Output of this call is `128` bytes and is G1 point following respective encoding rules. + if len(input) != 64 { + return nil, errBLS12381InvalidInputLength + } + + // Decode input field element + fe, err := decodeBLS12381FieldElement(input) + if err != nil { + return nil, err + } + + // Initialize G1 + g := bls12381.NewG1() + + // Compute mapping + r, err := g.MapToCurve(fe) + if err != nil { + return nil, err + } + + // Encode the G1 point to 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381MapG2 implements EIP-2537 MapG2 precompile. +type bls12381MapG2 struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381MapG2) RequiredGas(input []byte) uint64 { + return params.Bls12381MapG2Gas +} + +func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 Map_FP2_TO_G2 precompile logic. + // > Field-to-curve call expects `128` bytes an an input that is interpreted as a an element of the quadratic extension field. + // > Output of this call is `256` bytes and is G2 point following respective encoding rules. + if len(input) != 128 { + return nil, errBLS12381InvalidInputLength + } + + // Decode input field element + fe := make([]byte, 96) + c0, err := decodeBLS12381FieldElement(input[:64]) + if err != nil { + return nil, err + } + copy(fe[48:], c0) + c1, err := decodeBLS12381FieldElement(input[64:]) + if err != nil { + return nil, err + } + copy(fe[:48], c1) + + // Initialize G2 + g := bls12381.NewG2() + + // Compute mapping + r, err := g.MapToCurve(fe) + if err != nil { + return nil, err + } + + // Encode the G2 point to 256 bytes + return g.EncodePoint(r), nil +} diff --git a/core/vm/eips.go b/core/vm/eips.go index 75e5fb1..5ec7587 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -18,27 +18,44 @@ package vm import ( "fmt" + "sort" "github.com/ava-labs/coreth/params" + "github.com/holiman/uint256" ) +var activators = map[int]func(*JumpTable){ + 2200: enable2200, + 1884: enable1884, + 1344: enable1344, + 2315: enable2315, +} + // EnableEIP enables the given EIP on the config. // This operation writes in-place, and callers need to ensure that the globally // defined jump tables are not polluted. func EnableEIP(eipNum int, jt *JumpTable) error { - switch eipNum { - case 2200: - enable2200(jt) - case 1884: - enable1884(jt) - case 1344: - enable1344(jt) - default: + enablerFn, ok := activators[eipNum] + if !ok { return fmt.Errorf("undefined eip %d", eipNum) } + enablerFn(jt) return nil } +func ValidEip(eipNum int) bool { + _, ok := activators[eipNum] + return ok +} +func ActivateableEips() []string { + var nums []string + for k := range activators { + nums = append(nums, fmt.Sprintf("%d", k)) + } + sort.Strings(nums) + return nums +} + // enable1884 applies EIP-1884 to the given jump table: // - Increase cost of BALANCE to 700 // - Increase cost of EXTCODEHASH to 700 @@ -46,23 +63,22 @@ func EnableEIP(eipNum int, jt *JumpTable) error { // - Define SELFBALANCE, with cost GasFastStep (5) func enable1884(jt *JumpTable) { // Gas cost changes + jt[SLOAD].constantGas = params.SloadGasEIP1884 jt[BALANCE].constantGas = params.BalanceGasEIP1884 jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884 - jt[SLOAD].constantGas = params.SloadGasEIP1884 // New opcode - jt[SELFBALANCE] = operation{ + jt[SELFBALANCE] = &operation{ execute: opSelfBalance, constantGas: GasFastStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, } } -func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(contract.Address())) - stack.push(balance) +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) + callContext.stack.push(balance) return nil, nil } @@ -70,23 +86,51 @@ func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, // - Adds an opcode that returns the current chain’s EIP-155 unique identifier func enable1344(jt *JumpTable) { // New opcode - jt[CHAINID] = operation{ + jt[CHAINID] = &operation{ execute: opChainID, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, } } // opChainID implements CHAINID opcode -func opChainID(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - chainId := interpreter.intPool.get().Set(interpreter.evm.chainConfig.ChainID) - stack.push(chainId) +func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) + callContext.stack.push(chainId) return nil, nil } // enable2200 applies EIP-2200 (Rebalance net-metered SSTORE) func enable2200(jt *JumpTable) { + jt[SLOAD].constantGas = params.SloadGasEIP2200 jt[SSTORE].dynamicGas = gasSStoreEIP2200 } + +// enable2315 applies EIP-2315 (Simple Subroutines) +// - Adds opcodes that jump to and return from subroutines +func enable2315(jt *JumpTable) { + // New opcode + jt[BEGINSUB] = &operation{ + execute: opBeginSub, + constantGas: GasQuickStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + // New opcode + jt[JUMPSUB] = &operation{ + execute: opJumpSub, + constantGas: GasSlowStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + jumps: true, + } + // New opcode + jt[RETURNSUB] = &operation{ + execute: opReturnSub, + constantGas: GasFastStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + jumps: true, + } +} diff --git a/core/vm/gas.go b/core/vm/gas.go index bd8b4f1..5cf1d85 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -17,7 +17,7 @@ package vm import ( - "math/big" + "github.com/holiman/uint256" ) // Gas costs @@ -30,23 +30,23 @@ const ( GasExtStep uint64 = 20 ) -// calcGas returns the actual gas cost of the call. +// callGas returns the actual gas cost of the call. // // The cost of gas was changed during the homestead price change HF. // As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64. -func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint64, error) { +func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) { if isEip150 { availableGas = availableGas - base gas := availableGas - availableGas/64 // If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150 - // is smaller than the requested amount. Therefor we return the new gas instead + // is smaller than the requested amount. Therefore we return the new gas instead // of returning an error. if !callCost.IsUint64() || gas < callCost.Uint64() { return gas, nil } } if !callCost.IsUint64() { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return callCost.Uint64(), nil diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 2adaf85..0182892 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -20,8 +20,8 @@ import ( "errors" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so @@ -36,7 +36,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used // without overflowing the gas calculation. if newMemSize > 0x1FFFFFFFE0 { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } newMemSizeWords := toWordSize(newMemSize) newMemSize = newMemSizeWords * 32 @@ -70,17 +70,17 @@ func memoryCopierGas(stackpos int) gasFunc { return 0, err } // And gas for copying data, charged per word at param.CopyGas - words, overflow := bigUint64(stack.Back(stackpos)) + words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -96,7 +96,7 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) + current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -131,11 +131,11 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 2.2.2. If original value equals new value (this storage slot is reset) // 2.2.2.1. If original value is 0, add 19800 gas to refund counter. // 2.2.2.2. Otherwise, add 4800 gas to refund counter. - value := common.BigToHash(y) + value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -183,14 +183,14 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) + current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) ) - value := common.BigToHash(y) + value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.SstoreNoopGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreInitGasEIP2200, nil @@ -219,9 +219,9 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m func makeGasLog(n uint64) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - requestedSize, overflow := bigUint64(stack.Back(1)) + requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } gas, err := memoryGasCost(mem, memorySize) @@ -230,18 +230,18 @@ func makeGasLog(n uint64) gasFunc { } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -252,15 +252,15 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if err != nil { return 0, err } - wordGas, overflow := bigUint64(stack.Back(1)) + wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -286,15 +286,15 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS if err != nil { return 0, err } - wordGas, overflow := bigUint64(stack.Back(2)) + wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -307,7 +307,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -320,7 +320,7 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -328,8 +328,8 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 - transfersValue = stack.Back(2).Sign() != 0 - address = common.BigToAddress(stack.Back(1)) + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) ) if evm.chainRules.IsEIP158 { if transfersValue && evm.StateDB.Empty(address) { @@ -347,7 +347,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) @@ -355,7 +355,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -373,14 +373,14 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -396,7 +396,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -412,7 +412,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -422,7 +422,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 - var address = common.BigToAddress(stack.Back(0)) + var address = common.Address(stack.Back(0).Bytes20()) if evm.chainRules.IsEIP158 { // if empty and transfers value diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index 038841c..ac1a907 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -6,9 +6,9 @@ import ( "encoding/json" "math/big" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/hexutil" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" ) var _ = (*structLogMarshaling)(nil) @@ -23,6 +23,8 @@ func (s StructLog) MarshalJSON() ([]byte, error) { Memory hexutil.Bytes `json:"memory"` MemorySize int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` + ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -43,6 +45,13 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.Stack[k] = (*math.HexOrDecimal256)(v) } } + if s.ReturnStack != nil { + enc.ReturnStack = make([]math.HexOrDecimal64, len(s.ReturnStack)) + for k, v := range s.ReturnStack { + enc.ReturnStack[k] = math.HexOrDecimal64(v) + } + } + enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth enc.RefundCounter = s.RefundCounter @@ -62,6 +71,8 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Memory *hexutil.Bytes `json:"memory"` MemorySize *int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` + ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` @@ -95,6 +106,15 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { s.Stack[k] = (*big.Int)(v) } } + if dec.ReturnStack != nil { + s.ReturnStack = make([]uint32, len(dec.ReturnStack)) + for k, v := range dec.ReturnStack { + s.ReturnStack[k] = uint32(v) + } + } + if dec.ReturnData != nil { + s.ReturnData = dec.ReturnData + } if dec.Storage != nil { s.Storage = dec.Storage } diff --git a/core/vm/int_pool_verifier.go b/core/vm/int_pool_verifier.go deleted file mode 100644 index 82fbfed..0000000 --- a/core/vm/int_pool_verifier.go +++ /dev/null @@ -1,31 +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 . - -// +build VERIFY_EVM_INTEGER_POOL - -package vm - -import "fmt" - -const verifyPool = true - -func verifyIntegerPool(ip *intPool) { - for i, item := range ip.pool.data { - if item.Cmp(checkVal) != 0 { - panic(fmt.Sprintf("%d'th item failed aggressive pool check. Value was modified", i)) - } - } -} diff --git a/core/vm/int_pool_verifier_empty.go b/core/vm/int_pool_verifier_empty.go deleted file mode 100644 index a5f1dc0..0000000 --- a/core/vm/int_pool_verifier_empty.go +++ /dev/null @@ -1,23 +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 . - -// +build !VERIFY_EVM_INTEGER_POOL - -package vm - -const verifyPool = false - -func verifyIntegerPool(ip *intPool) {} diff --git a/core/vm/intpool.go b/core/vm/intpool.go deleted file mode 100644 index 917a78d..0000000 --- a/core/vm/intpool.go +++ /dev/null @@ -1,106 +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 . - -package vm - -import ( - "math/big" - "sync" -) - -var checkVal = big.NewInt(-42) - -const poolLimit = 256 - -// intPool is a pool of big integers that -// can be reused for all big.Int operations. -type intPool struct { - pool *Stack -} - -func newIntPool() *intPool { - return &intPool{pool: newstack()} -} - -// get retrieves a big int from the pool, allocating one if the pool is empty. -// Note, the returned int's value is arbitrary and will not be zeroed! -func (p *intPool) get() *big.Int { - if p.pool.len() > 0 { - return p.pool.pop() - } - return new(big.Int) -} - -// getZero retrieves a big int from the pool, setting it to zero or allocating -// a new one if the pool is empty. -func (p *intPool) getZero() *big.Int { - if p.pool.len() > 0 { - return p.pool.pop().SetUint64(0) - } - return new(big.Int) -} - -// put returns an allocated big int to the pool to be later reused by get calls. -// Note, the values as saved as is; neither put nor get zeroes the ints out! -func (p *intPool) put(is ...*big.Int) { - if len(p.pool.data) > poolLimit { - return - } - for _, i := range is { - // verifyPool is a build flag. Pool verification makes sure the integrity - // of the integer pool by comparing values to a default value. - if verifyPool { - i.Set(checkVal) - } - p.pool.push(i) - } -} - -// The intPool pool's default capacity -const poolDefaultCap = 25 - -// intPoolPool manages a pool of intPools. -type intPoolPool struct { - pools []*intPool - lock sync.Mutex -} - -var poolOfIntPools = &intPoolPool{ - pools: make([]*intPool, 0, poolDefaultCap), -} - -// get is looking for an available pool to return. -func (ipp *intPoolPool) get() *intPool { - ipp.lock.Lock() - defer ipp.lock.Unlock() - - if len(poolOfIntPools.pools) > 0 { - ip := ipp.pools[len(ipp.pools)-1] - ipp.pools = ipp.pools[:len(ipp.pools)-1] - return ip - } - return newIntPool() -} - -// put a pool that has been allocated with get. -func (ipp *intPoolPool) put(ip *intPool) { - ipp.lock.Lock() - defer ipp.lock.Unlock() - - if len(ipp.pools) < cap(ipp.pools) { - ipp.pools = append(ipp.pools, ip) - } -} diff --git a/core/vm/logger.go b/core/vm/logger.go index 95143f1..ea5d6f2 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,17 +18,21 @@ package vm import ( "encoding/hex" + "errors" "fmt" "io" "math/big" + "strings" "time" "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/hexutil" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" ) +var errTraceLimitReached = errors.New("the number of logs reached the specified limit") + // Storage represents a contract's storage. type Storage map[common.Hash]common.Hash @@ -38,17 +42,17 @@ func (s Storage) Copy() Storage { for key, value := range s { cpy[key] = value } - return cpy } // LogConfig are the configuration options for structured logger the EVM type LogConfig struct { - DisableMemory bool // disable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - Debug bool // print output during capture end - Limit int // maximum length of output, but zero means unlimited + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + DisableReturnData bool // disable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited } //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go @@ -63,6 +67,8 @@ type StructLog struct { Memory []byte `json:"memory"` MemorySize int `json:"memSize"` Stack []*big.Int `json:"stack"` + ReturnStack []uint32 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -72,6 +78,7 @@ type StructLog struct { // overrides for gencodec type structLogMarshaling struct { Stack []*math.HexOrDecimal256 + ReturnStack []math.HexOrDecimal64 Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 Memory hexutil.Bytes @@ -98,9 +105,9 @@ func (s *StructLog) ErrorString() string { // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type Tracer interface { - CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) error - CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error - CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error + CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error + CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error + CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error } @@ -112,16 +119,16 @@ type Tracer interface { type StructLogger struct { cfg LogConfig - logs []StructLog - changedValues map[common.Address]Storage - output []byte - err error + storage map[common.Address]Storage + logs []StructLog + output []byte + err error } // NewStructLogger returns a new logger func NewStructLogger(cfg *LogConfig) *StructLogger { logger := &StructLogger{ - changedValues: make(map[common.Address]Storage), + storage: make(map[common.Address]Storage), } if cfg != nil { logger.cfg = *cfg @@ -136,27 +143,11 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea // CaptureState logs a new structured log message and pushes it out to the environment // -// CaptureState also tracks SSTORE ops to track dirty values. -func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { +// CaptureState also tracks SLOAD/SSTORE ops to track storage change. +func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { - return ErrTraceLimitReached - } - - // initialise new changed values storage container for this contract - // if not present. - if l.changedValues[contract.Address()] == nil { - l.changedValues[contract.Address()] = make(Storage) - } - - // capture SSTORE opcodes and determine the changed value and store - // it in the local storage container. - if op == SSTORE && stack.len() >= 2 { - var ( - value = common.BigToHash(stack.data[stack.len()-2]) - address = common.BigToHash(stack.data[stack.len()-1]) - ) - l.changedValues[contract.Address()][address] = value + return errTraceLimitReached } // Copy a snapshot of the current memory state to a new buffer var mem []byte @@ -169,24 +160,54 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui if !l.cfg.DisableStack { stck = make([]*big.Int, len(stack.Data())) for i, item := range stack.Data() { - stck[i] = new(big.Int).Set(item) + stck[i] = new(big.Int).Set(item.ToBig()) } } + var rstack []uint32 + if !l.cfg.DisableStack && rStack != nil { + rstck := make([]uint32, len(rStack.data)) + copy(rstck, rStack.data) + } // Copy a snapshot of the current storage to a new container var storage Storage if !l.cfg.DisableStorage { - storage = l.changedValues[contract.Address()].Copy() + // 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 + } + // capture SSTORE opcodes and record the written entry in the local storage. + if op == SSTORE && stack.len() >= 2 { + 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.DisableReturnData { + 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, storage, depth, env.StateDB.GetRefund(), err} - + log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) return nil } // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { return nil } @@ -227,6 +248,12 @@ func WriteTrace(writer io.Writer, logs []StructLog) { fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) } } + if len(log.ReturnStack) > 0 { + fmt.Fprintln(writer, "ReturnStack:") + for i := len(log.Stack) - 1; i >= 0; i-- { + fmt.Fprintf(writer, "%08d 0x%x (%d)\n", len(log.Stack)-i-1, log.ReturnStack[i], log.ReturnStack[i]) + } + } if len(log.Memory) > 0 { fmt.Fprintln(writer, "Memory:") fmt.Fprint(writer, hex.Dump(log.Memory)) @@ -237,6 +264,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) { 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) } } @@ -254,3 +285,79 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { 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(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { + 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 | +|-------|-------------|------|-----------|-----------| +`) + return nil +} + +func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { + 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, fmt.Sprintf("%d", elem)) + } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + } + if !t.cfg.DisableStack { // format return stack + var a []string + for _, elem := range rStack.data { + a = append(a, fmt.Sprintf("%2d", elem)) + } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + } + fmt.Fprintln(t.out, "") + if err != nil { + fmt.Fprintf(t.out, "Error: %v\n", err) + } + return nil +} + +func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { + + fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) + + return nil +} + +func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error { + fmt.Fprintf(t.out, ` +Output: 0x%x +Consumed gas: %d +Error: %v +`, + output, gasUsed, err) + return nil +} diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index 5068343..5f3f2c4 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -22,8 +22,8 @@ import ( "math/big" "time" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" ) type JSONLogger struct { @@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create } // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { log := StructLog{ Pc: pc, Op: op, @@ -62,13 +62,22 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint log.Memory = memory.Data() } if !l.cfg.DisableStack { - log.Stack = stack.Data() + //TODO(@holiman) improve this + logstack := make([]*big.Int, len(stack.Data())) + for i, item := range stack.Data() { + logstack[i] = item.ToBig() + } + log.Stack = logstack + log.ReturnStack = rStack.data + } + if !l.cfg.DisableReturnData { + log.ReturnData = rData } return l.encoder.Encode(log) } // CaptureFault outputs state information on the logger. -func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { return nil } diff --git a/core/vm/memory.go b/core/vm/memory.go index 5408707..ba5f848 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -18,9 +18,8 @@ package vm import ( "fmt" - "math/big" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/holiman/uint256" ) // Memory implements a simple memory model for the ethereum virtual machine. @@ -50,7 +49,7 @@ func (m *Memory) Set(offset, size uint64, value []byte) { // Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to // 32 bytes. -func (m *Memory) Set32(offset uint64, val *big.Int) { +func (m *Memory) Set32(offset uint64, val *uint256.Int) { // length of store may never be less than offset + size. // The store should be resized PRIOR to setting the memory if offset+32 > uint64(len(m.store)) { @@ -59,7 +58,7 @@ func (m *Memory) Set32(offset uint64, val *big.Int) { // Zero the memory area copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) // Fill in relevant bits - math.ReadBits(val, m.store[offset:offset+32]) + val.WriteToSlice(m.store[offset:]) } // Resize resizes the memory to size @@ -70,7 +69,7 @@ func (m *Memory) Resize(size uint64) { } // Get returns offset + size as a new slice -func (m *Memory) Get(offset, size int64) (cpy []byte) { +func (m *Memory) GetCopy(offset, size int64) (cpy []byte) { if size == 0 { return nil } diff --git a/core/vm/stack.go b/core/vm/stack.go index 4c1b9e8..af27d65 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -18,36 +18,48 @@ package vm import ( "fmt" - "math/big" + "sync" + + "github.com/holiman/uint256" ) +var stackPool = sync.Pool{ + New: func() interface{} { + return &Stack{data: make([]uint256.Int, 0, 16)} + }, +} + // Stack is an object for basic stack operations. Items popped to the stack are // expected to be changed and modified. stack does not take care of adding newly // initialised objects. type Stack struct { - data []*big.Int + data []uint256.Int } func newstack() *Stack { - return &Stack{data: make([]*big.Int, 0, 1024)} + return stackPool.Get().(*Stack) } -// Data returns the underlying big.Int array. -func (st *Stack) Data() []*big.Int { +func returnStack(s *Stack) { + s.data = s.data[:0] + stackPool.Put(s) +} + +// Data returns the underlying uint256.Int array. +func (st *Stack) Data() []uint256.Int { return st.data } -func (st *Stack) push(d *big.Int) { +func (st *Stack) push(d *uint256.Int) { // NOTE push limit (1024) is checked in baseCheck - //stackItem := new(big.Int).Set(d) - //st.data = append(st.data, stackItem) - st.data = append(st.data, d) + st.data = append(st.data, *d) } -func (st *Stack) pushN(ds ...*big.Int) { +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 *big.Int) { +func (st *Stack) pop() (ret uint256.Int) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return @@ -61,24 +73,17 @@ func (st *Stack) swap(n int) { st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n] } -func (st *Stack) dup(pool *intPool, n int) { - st.push(pool.get().Set(st.data[st.len()-n])) +func (st *Stack) dup(n int) { + st.push(&st.data[st.len()-n]) } -func (st *Stack) peek() *big.Int { - return st.data[st.len()-1] +func (st *Stack) peek() *uint256.Int { + return &st.data[st.len()-1] } // Back returns the n'th item in stack -func (st *Stack) Back(n int) *big.Int { - return st.data[st.len()-n-1] -} - -func (st *Stack) require(n int) error { - if st.len() < n { - return fmt.Errorf("stack underflow (%d <=> %d)", len(st.data), n) - } - return nil +func (st *Stack) Back(n int) *uint256.Int { + return &st.data[st.len()-n-1] } // Print dumps the content of the stack @@ -93,3 +98,34 @@ func (st *Stack) Print() { } fmt.Println("#############") } + +var rStackPool = sync.Pool{ + New: func() interface{} { + return &ReturnStack{data: make([]uint32, 0, 10)} + }, +} + +// ReturnStack is an object for basic return stack operations. +type ReturnStack struct { + data []uint32 +} + +func newReturnStack() *ReturnStack { + return rStackPool.Get().(*ReturnStack) +} + +func returnRStack(rs *ReturnStack) { + rs.data = rs.data[:0] + rStackPool.Put(rs) +} + +func (st *ReturnStack) push(d uint32) { + st.data = append(st.data, d) +} + +// A uint32 is sufficient as for code below 4.2G +func (st *ReturnStack) pop() (ret uint32) { + ret = st.data[len(st.data)-1] + st.data = st.data[:len(st.data)-1] + return +} -- cgit v1.2.3-70-g09d2 From dba1cbd83254b0c8e4e99139f1f9748c38bf1537 Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 16 Sep 2020 17:00:11 -0400 Subject: update more files --- core/tx_pool.go | 255 ++++++++----- core/types/block.go | 66 ++-- core/vm/errors.go | 49 ++- core/vm/evm.go | 289 +++++++------- core/vm/instructions.go | 971 +++++++++++++++++++++++------------------------- core/vm/interface.go | 2 +- core/vm/interpreter.go | 94 +++-- core/vm/jump_table.go | 181 ++------- core/vm/opcodes.go | 62 ++-- eth/api.go | 128 +++---- eth/api_backend.go | 107 +++++- eth/api_tracer.go | 90 +++-- eth/backend.go | 181 ++++----- hacked-list.txt | 2 +- 14 files changed, 1288 insertions(+), 1189 deletions(-) (limited to 'core/vm') diff --git a/core/tx_pool.go b/core/tx_pool.go index 5b2a3c0..848627a 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -18,7 +18,6 @@ package core import ( "errors" - "fmt" "math" "math/big" "sort" @@ -28,26 +27,38 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/prque" - "github.com/ava-labs/go-ethereum/event" - "github.com/ava-labs/go-ethereum/log" - "github.com/ava-labs/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" ) const ( // chainHeadChanSize is the size of channel listening to ChainHeadEvent. chainHeadChanSize = 10 + + // txSlotSize is used to calculate how many data slots a single transaction + // takes up based on its size. The slots are used as DoS protection, ensuring + // that validating a new transaction remains a constant operation (in reality + // O(maxslots), where max slots are 4 currently). + txSlotSize = 32 * 1024 + + // txMaxSize is the maximum size a single transaction can have. This field has + // non-trivial consequences: larger transactions are significantly harder and + // more expensive to propagate; larger transactions also take more resources + // to validate whether they fit into the pool or not. + txMaxSize = 4 * txSlotSize // 128KB ) var ( + // ErrAlreadyKnown is returned if the transactions is already contained + // within the pool. + ErrAlreadyKnown = errors.New("already known") + // ErrInvalidSender is returned if the transaction contains an invalid signature. ErrInvalidSender = errors.New("invalid sender") - // ErrNonceTooLow is returned if the nonce of a transaction is lower than the - // one present in the local chain. - ErrNonceTooLow = errors.New("nonce too low") - // ErrUnderpriced is returned if a transaction's gas price is below the minimum // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") @@ -56,19 +67,11 @@ var ( // with a different one without the required price bump. ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") - // ErrInsufficientFunds is returned if the total cost of executing a transaction - // is higher than the balance of the user's account. - ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") - - // ErrIntrinsicGas is returned if the transaction is specified to use less gas - // than required to start the invocation. - ErrIntrinsicGas = errors.New("intrinsic gas too low") - // ErrGasLimit is returned if a transaction's requested gas limit exceeds the // maximum allowance of the current block. ErrGasLimit = errors.New("exceeds block gas limit") - // ErrNegativeValue is a sanity error to ensure noone is able to specify a + // ErrNegativeValue is a sanity error to ensure no one is able to specify a // transaction with a negative value. ErrNegativeValue = errors.New("negative value") @@ -95,15 +98,18 @@ var ( queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil) queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds + queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime // General tx metrics - validMeter = metrics.NewRegisteredMeter("txpool/valid", nil) + knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil) + validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) - pendingCounter = metrics.NewRegisteredCounter("txpool/pending", nil) - queuedCounter = metrics.NewRegisteredCounter("txpool/queued", nil) - localCounter = metrics.NewRegisteredCounter("txpool/local", nil) + pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) + queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) + localGauge = metrics.NewRegisteredGauge("txpool/local", nil) + slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) ) // TxStatus is the current status of a transaction as seen by the pool. @@ -359,9 +365,11 @@ func (pool *TxPool) loop() { } // Any non-locals old enough should be removed if time.Since(pool.beats[addr]) > pool.config.Lifetime { - for _, tx := range pool.queue[addr].Flatten() { + list := pool.queue[addr].Flatten() + for _, tx := range list { pool.removeTx(tx.Hash(), true) } + queuedEvictionMeter.Mark(int64(len(list))) } } pool.mu.Unlock() @@ -517,8 +525,8 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { - // Heuristic limit, reject transactions over 32KB to prevent DOS attacks - if tx.Size() > 32*1024 { + // Reject transactions over defined size to prevent DOS attacks + if uint64(tx.Size()) > txMaxSize { return ErrOversizedData } // Transactions can't be negative. This may never happen using RLP decoded @@ -537,7 +545,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { } // Drop non-local transactions under our own minimal accepted gas price local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network - if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 { + if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { return ErrUnderpriced } // Ensure the transaction adheres to nonce ordering @@ -572,16 +580,15 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e hash := tx.Hash() if pool.all.Get(hash) != nil { log.Trace("Discarding already known transaction", "hash", hash) - return false, fmt.Errorf("known transaction: %x", hash) + knownTxMeter.Mark(1) + return false, ErrAlreadyKnown } - // If the transaction fails basic validation, discard it if err := pool.validateTx(tx, local); err != nil { log.Trace("Discarding invalid transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) return false, err } - // If the transaction pool is full, discard underpriced transactions if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it @@ -591,14 +598,13 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e return false, ErrUnderpriced } // New transaction is better than our worse ones, make room for it - drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals) + drop := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), pool.locals) for _, tx := range drop { log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) underpricedTxMeter.Mark(1) pool.removeTx(tx.Hash(), false) } } - // Try to replace an existing transaction in the pending pool from, _ := types.Sender(pool.signer, tx) // already validated if list := pool.pending[from]; list != nil && list.Overlaps(tx) { @@ -619,15 +625,16 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e pool.journalTx(from, tx) pool.queueTxEvent(tx) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) + + // Successful promotion, bump the heartbeat + pool.beats[from] = time.Now() return old != nil, nil } - // New transaction isn't replacing a pending one, push into queue replaced, err = pool.enqueueTx(hash, tx) if err != nil { return false, err } - // Mark local addresses and journal local transactions if local { if !pool.locals.contains(from) { @@ -636,7 +643,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e } } if local || pool.locals.contains(from) { - localCounter.Inc(1) + localGauge.Inc(1) } pool.journalTx(from, tx) @@ -666,12 +673,16 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er queuedReplaceMeter.Mark(1) } else { // Nothing was replaced, bump the queued counter - queuedCounter.Inc(1) + queuedGauge.Inc(1) } if pool.all.Get(hash) == nil { pool.all.Add(tx) pool.priced.Put(tx) } + // If we never record the heartbeat, do it right now. + if _, exist := pool.beats[from]; !exist { + pool.beats[from] = time.Now() + } return old != nil, nil } @@ -703,7 +714,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T // An older transaction was better, discard this pool.all.Remove(hash) pool.priced.Removed(1) - pendingDiscardMeter.Mark(1) return false } @@ -711,11 +721,10 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T if old != nil { pool.all.Remove(old.Hash()) pool.priced.Removed(1) - pendingReplaceMeter.Mark(1) } else { // Nothing was replaced, bump the pending counter - pendingCounter.Inc(1) + pendingGauge.Inc(1) } // Failsafe to work around direct pending inserts (tests) if pool.all.Get(hash) == nil { @@ -723,9 +732,10 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T pool.priced.Put(tx) } // Set the potentially new pending nonce and notify any subsystems of the new tx - pool.beats[addr] = time.Now() pool.pendingNonces.set(addr, tx.Nonce()+1) + // Successful promotion, bump the heartbeat + pool.beats[addr] = time.Now() return true } @@ -776,15 +786,47 @@ func (pool *TxPool) AddRemote(tx *types.Transaction) error { // addTxs attempts to queue a batch of transactions if they are valid. func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { - // Cache senders in transactions before obtaining lock (pool.signer is immutable) - for _, tx := range txs { - types.Sender(pool.signer, tx) + // Filter out known ones without obtaining the pool lock or recovering signatures + var ( + errs = make([]error, len(txs)) + news = make([]*types.Transaction, 0, len(txs)) + ) + for i, tx := range txs { + // If the transaction is known, pre-set the error slot + if pool.all.Get(tx.Hash()) != nil { + errs[i] = ErrAlreadyKnown + knownTxMeter.Mark(1) + continue + } + // Exclude transactions with invalid signatures as soon as + // possible and cache senders in transactions before + // obtaining lock + _, err := types.Sender(pool.signer, tx) + if err != nil { + errs[i] = ErrInvalidSender + invalidTxMeter.Mark(1) + continue + } + // Accumulate all unknown transactions for deeper processing + news = append(news, tx) + } + if len(news) == 0 { + return errs } + // Process all the new transaction and merge any errors into the original slice pool.mu.Lock() - errs, dirtyAddrs := pool.addTxsLocked(txs, local) + newErrs, dirtyAddrs := pool.addTxsLocked(news, local) pool.mu.Unlock() + var nilSlot = 0 + for _, err := range newErrs { + for errs[nilSlot] != nil { + nilSlot++ + } + errs[nilSlot] = err + } + // Reorg the pool internals if needed and return done := pool.requestPromoteExecutables(dirtyAddrs) if sync { <-done @@ -804,26 +846,29 @@ func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, dirty.addTx(tx) } } - validMeter.Mark(int64(len(dirty.accounts))) + validTxMeter.Mark(int64(len(dirty.accounts))) return errs, dirty } // Status returns the status (unknown/pending/queued) of a batch of transactions // identified by their hashes. func (pool *TxPool) Status(hashes []common.Hash) []TxStatus { - pool.mu.RLock() - defer pool.mu.RUnlock() - status := make([]TxStatus, len(hashes)) for i, hash := range hashes { - if tx := pool.all.Get(hash); tx != nil { - from, _ := types.Sender(pool.signer, tx) // already validated - if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil { - status[i] = TxStatusPending - } else { - status[i] = TxStatusQueued - } + tx := pool.Get(hash) + if tx == nil { + continue + } + from, _ := types.Sender(pool.signer, tx) // already validated + pool.mu.RLock() + if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + status[i] = TxStatusPending + } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + status[i] = TxStatusQueued } + // implicit else: the tx may have been included into a block between + // checking pool.Get and obtaining the lock. In that case, TxStatusUnknown is correct + pool.mu.RUnlock() } return status } @@ -833,6 +878,12 @@ func (pool *TxPool) Get(hash common.Hash) *types.Transaction { return pool.all.Get(hash) } +// Has returns an indicator whether txpool has a transaction cached with the +// given hash. +func (pool *TxPool) Has(hash common.Hash) bool { + return pool.all.Get(hash) != nil +} + // removeTx removes a single transaction from the queue, moving all subsequent // transactions back to the future queue. func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { @@ -849,7 +900,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { pool.priced.Removed(1) } if pool.locals.contains(addr) { - localCounter.Dec(1) + localGauge.Dec(1) } // Remove the transaction from the pending lists and reset the account nonce if pending := pool.pending[addr]; pending != nil { @@ -857,7 +908,6 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { // If no more pending transactions are left, remove the list if pending.Empty() { delete(pool.pending, addr) - delete(pool.beats, addr) } // Postpone any invalidated transactions for _, tx := range invalids { @@ -866,7 +916,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { // Update the account nonce if needed pool.pendingNonces.setIfLower(addr, tx.Nonce()) // Reduce the pending counter - pendingCounter.Dec(int64(1 + len(invalids))) + pendingGauge.Dec(int64(1 + len(invalids))) return } } @@ -874,10 +924,11 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { if future := pool.queue[addr]; future != nil { if removed, _ := future.Remove(tx); removed { // Reduce the queued counter - queuedCounter.Dec(1) + queuedGauge.Dec(1) } if future.Empty() { delete(pool.queue, addr) + delete(pool.beats, addr) } } } @@ -989,7 +1040,10 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt defer close(done) var promoteAddrs []common.Address - if dirtyAccounts != nil { + if dirtyAccounts != nil && reset == nil { + // Only dirty accounts need to be promoted, unless we're resetting. + // For resets, all addresses in the tx queue will be promoted and + // the flatten operation can be avoided. promoteAddrs = dirtyAccounts.flatten() } pool.mu.Lock() @@ -1005,20 +1059,14 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt } } // Reset needs promote for all addresses - promoteAddrs = promoteAddrs[:0] + promoteAddrs = make([]common.Address, 0, len(pool.queue)) for addr := range pool.queue { promoteAddrs = append(promoteAddrs, addr) } } // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) - for _, tx := range promoted { - addr, _ := types.Sender(pool.signer, tx) - if _, ok := events[addr]; !ok { - events[addr] = newTxSortedMap() - } - events[addr].Put(tx) - } + // If a new block appeared, validate the pool of pending transactions. This will // remove any transaction that has been included in the block or was invalidated // because of another transaction (e.g. higher gas price). @@ -1031,12 +1079,19 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt // Update all accounts to the latest known pending nonce for addr, list := range pool.pending { - txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway - pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1) + highestPending := list.LastElement() + pool.pendingNonces.set(addr, highestPending.Nonce()+1) } pool.mu.Unlock() // Notify subsystems for newly added transactions + for _, tx := range promoted { + addr, _ := types.Sender(pool.signer, tx) + if _, ok := events[addr]; !ok { + events[addr] = newTxSortedMap() + } + events[addr].Put(tx) + } if len(events) > 0 { var txs []*types.Transaction for _, set := range events { @@ -1152,15 +1207,15 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans for _, tx := range forwards { hash := tx.Hash() pool.all.Remove(hash) - log.Trace("Removed old queued transaction", "hash", hash) } + log.Trace("Removed old queued transactions", "count", len(forwards)) // Drop all transactions that are too costly (low balance or out of gas) drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) for _, tx := range drops { hash := tx.Hash() pool.all.Remove(hash) - log.Trace("Removed unpayable queued transaction", "hash", hash) } + log.Trace("Removed unpayable queued transactions", "count", len(drops)) queuedNofundsMeter.Mark(int64(len(drops))) // Gather all executable transactions and promote them @@ -1168,11 +1223,11 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans for _, tx := range readies { hash := tx.Hash() if pool.promoteTx(addr, hash, tx) { - log.Trace("Promoting queued transaction", "hash", hash) promoted = append(promoted, tx) } } - queuedCounter.Dec(int64(len(readies))) + log.Trace("Promoted queued transactions", "count", len(promoted)) + queuedGauge.Dec(int64(len(readies))) // Drop all transactions over the allowed limit var caps types.Transactions @@ -1187,13 +1242,14 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans } // Mark all the items dropped as removed pool.priced.Removed(len(forwards) + len(drops) + len(caps)) - queuedCounter.Dec(int64(len(forwards) + len(drops) + len(caps))) + queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) if pool.locals.contains(addr) { - localCounter.Dec(int64(len(forwards) + len(drops) + len(caps))) + localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) } // Delete the entire queue entry if it became empty. if list.Empty() { delete(pool.queue, addr) + delete(pool.beats, addr) } } return promoted @@ -1248,9 +1304,9 @@ func (pool *TxPool) truncatePending() { log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) } pool.priced.Removed(len(caps)) - pendingCounter.Dec(int64(len(caps))) + pendingGauge.Dec(int64(len(caps))) if pool.locals.contains(offenders[i]) { - localCounter.Dec(int64(len(caps))) + localGauge.Dec(int64(len(caps))) } pending-- } @@ -1275,9 +1331,9 @@ func (pool *TxPool) truncatePending() { log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) } pool.priced.Removed(len(caps)) - pendingCounter.Dec(int64(len(caps))) + pendingGauge.Dec(int64(len(caps))) if pool.locals.contains(addr) { - localCounter.Dec(int64(len(caps))) + localGauge.Dec(int64(len(caps))) } pending-- } @@ -1361,9 +1417,9 @@ func (pool *TxPool) demoteUnexecutables() { log.Trace("Demoting pending transaction", "hash", hash) pool.enqueueTx(hash, tx) } - pendingCounter.Dec(int64(len(olds) + len(drops) + len(invalids))) + pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) if pool.locals.contains(addr) { - localCounter.Dec(int64(len(olds) + len(drops) + len(invalids))) + localGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) } // If there's a gap in front, alert (should never happen) and postpone all transactions if list.Len() > 0 && list.txs.Get(nonce) == nil { @@ -1373,12 +1429,13 @@ func (pool *TxPool) demoteUnexecutables() { log.Error("Demoting invalidated transaction", "hash", hash) pool.enqueueTx(hash, tx) } - pendingCounter.Dec(int64(len(gapped))) + pendingGauge.Dec(int64(len(gapped))) + // This might happen in a reorg, so log it to the metering + blockReorgInvalidatedTx.Mark(int64(len(gapped))) } - // Delete the entire queue entry if it became empty. + // Delete the entire pending entry if it became empty. if list.Empty() { delete(pool.pending, addr) - delete(pool.beats, addr) } } } @@ -1422,6 +1479,10 @@ func (as *accountSet) contains(addr common.Address) bool { return exist } +func (as *accountSet) empty() bool { + return len(as.accounts) == 0 +} + // containsTx checks if the sender of a given tx is within the set. If the sender // cannot be derived, this method returns false. func (as *accountSet) containsTx(tx *types.Transaction) bool { @@ -1475,8 +1536,9 @@ func (as *accountSet) merge(other *accountSet) { // peeking into the pool in TxPool.Get without having to acquire the widely scoped // TxPool.mu mutex. type txLookup struct { - all map[common.Hash]*types.Transaction - lock sync.RWMutex + all map[common.Hash]*types.Transaction + slots int + lock sync.RWMutex } // newTxLookup returns a new txLookup structure. @@ -1514,11 +1576,22 @@ func (t *txLookup) Count() int { return len(t.all) } +// Slots returns the current number of slots used in the lookup. +func (t *txLookup) Slots() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.slots +} + // Add adds a transaction to the lookup. func (t *txLookup) Add(tx *types.Transaction) { t.lock.Lock() defer t.lock.Unlock() + t.slots += numSlots(tx) + slotsGauge.Update(int64(t.slots)) + t.all[tx.Hash()] = tx } @@ -1527,5 +1600,13 @@ func (t *txLookup) Remove(hash common.Hash) { t.lock.Lock() defer t.lock.Unlock() + t.slots -= numSlots(t.all[hash]) + slotsGauge.Update(int64(t.slots)) + delete(t.all, hash) } + +// numSlots calculates the number of slots needed for a single transaction. +func numSlots(tx *types.Transaction) int { + return int((tx.Size() + txSlotSize - 1) / txSlotSize) +} diff --git a/core/types/block.go b/core/types/block.go index 4096d86..37f3464 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -24,18 +24,19 @@ import ( "io" "math/big" "reflect" - "sort" + "sync" "sync/atomic" "time" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/hexutil" - "github.com/ava-labs/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" ) var ( - EmptyRootHash = DeriveSha(Transactions{}) + EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") EmptyUncleHash = rlpHash([]*Header(nil)) ) @@ -132,13 +133,33 @@ func (h *Header) SanityCheck() error { return nil } +// hasherPool holds LegacyKeccak hashers. +var hasherPool = sync.Pool{ + New: func() interface{} { + return sha3.NewLegacyKeccak256() + }, +} + func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) return h } +// EmptyBody returns true if there is no additional 'body' to complete the header +// that is: no transactions and no uncles. +func (h *Header) EmptyBody() bool { + return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash +} + +// EmptyReceipts returns true if there are no receipts for this header/block. +func (h *Header) EmptyReceipts() bool { + return h.ReceiptHash == EmptyRootHash +} + // Body is a simple (mutable, non-safe) data container for storing and moving // a block's data contents (transactions and uncles) together. type Body struct { @@ -214,14 +235,14 @@ type storageblock struct { // The values of TxHash, UncleHash, ReceiptHash and Bloom in header // are ignored and set to values derived from the given txs, uncles // and receipts. -func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, extdata []byte) *Block { +func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher Hasher, extdata []byte) *Block { b := &Block{header: CopyHeader(header), td: new(big.Int)} // TODO: panic if len(txs) != len(receipts) if len(txs) == 0 { b.header.TxHash = EmptyRootHash } else { - b.header.TxHash = DeriveSha(Transactions(txs)) + b.header.TxHash = DeriveSha(Transactions(txs), hasher) b.transactions = make(Transactions, len(txs)) copy(b.transactions, txs) } @@ -229,7 +250,7 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* if len(receipts) == 0 { b.header.ReceiptHash = EmptyRootHash } else { - b.header.ReceiptHash = DeriveSha(Receipts(receipts)) + b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) b.header.Bloom = CreateBloom(receipts) } @@ -467,26 +488,3 @@ func (b *Block) Hash() common.Hash { } type Blocks []*Block - -type BlockBy func(b1, b2 *Block) bool - -func (self BlockBy) Sort(blocks Blocks) { - bs := blockSorter{ - blocks: blocks, - by: self, - } - sort.Sort(bs) -} - -type blockSorter struct { - blocks Blocks - by func(b1, b2 *Block) bool -} - -func (self blockSorter) Len() int { return len(self.blocks) } -func (self blockSorter) Swap(i, j int) { - self.blocks[i], self.blocks[j] = self.blocks[j], self.blocks[i] -} -func (self blockSorter) Less(i, j int) bool { return self.by(self.blocks[i], self.blocks[j]) } - -func Number(b1, b2 *Block) bool { return b1.header.Number.Cmp(b2.header.Number) < 0 } diff --git a/core/vm/errors.go b/core/vm/errors.go index 94f0ed8..9f3f335 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -16,16 +16,57 @@ package vm -import "errors" +import ( + "errors" + "fmt" +) -// List execution errors +// List evm execution errors var ( + // ErrInvalidSubroutineEntry means that a BEGINSUB was reached via iteration, + // as opposed to from a JUMPSUB instruction + ErrInvalidSubroutineEntry = errors.New("invalid subroutine entry") ErrOutOfGas = errors.New("out of gas") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") ErrDepth = errors.New("max call depth exceeded") - ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrIncompatibleAccount = errors.New("incompatible account") ErrContractAddressCollision = errors.New("contract address collision") - ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidRetsub = errors.New("invalid retsub") + ErrReturnStackExceeded = errors.New("return stack limit reached") ) + +// ErrStackUnderflow wraps an evm error when the items on the stack less +// than the minimal requirement. +type ErrStackUnderflow struct { + stackLen int + required int +} + +func (e *ErrStackUnderflow) Error() string { + return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required) +} + +// ErrStackOverflow wraps an evm error when the items on the stack exceeds +// the maximum allowance. +type ErrStackOverflow struct { + stackLen int + limit int +} + +func (e *ErrStackOverflow) Error() string { + return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit) +} + +// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered. +type ErrInvalidOpCode struct { + opcode OpCode +} + +func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } diff --git a/core/vm/evm.go b/core/vm/evm.go index be8b240..85b7ba7 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,13 +17,15 @@ package vm import ( + "errors" "math/big" "sync/atomic" "time" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" ) // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -42,20 +44,24 @@ type ( GetHashFunc func(uint64) common.Hash ) +func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { + var precompiles map[common.Address]PrecompiledContract + switch { + case evm.chainRules.IsYoloV1: + precompiles = PrecompiledContractsYoloV1 + case evm.chainRules.IsIstanbul: + precompiles = PrecompiledContractsIstanbul + case evm.chainRules.IsByzantium: + precompiles = PrecompiledContractsByzantium + default: + precompiles = PrecompiledContractsHomestead + } + p, ok := precompiles[addr] + return p, ok +} + // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { - if contract.CodeAddr != nil { - precompiles := PrecompiledContractsHomestead - if evm.chainRules.IsByzantium { - precompiles = PrecompiledContractsByzantium - } - if evm.chainRules.IsIstanbul { - precompiles = PrecompiledContractsIstanbul - } - if p := precompiles[*contract.CodeAddr]; p != nil { - return RunPrecompiledContract(p, input, contract) - } - } for _, interpreter := range evm.interpreters { if interpreter.CanRun(contract.Code) { if evm.interpreter != interpreter { @@ -69,7 +75,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return interpreter.Run(contract, input, readOnly) } } - return nil, ErrNoCompatibleInterpreter + return nil, errors.New("no compatible interpreter") } // Context provides the EVM with auxiliary information. Once provided @@ -194,29 +200,19 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.vmConfig.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 } // Fail if we're trying to transfer more than the available balance - if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) - var ( - to = AccountRef(addr) - snapshot = evm.StateDB.Snapshot() - ) if !evm.StateDB.Exist(addr) { - precompiles := PrecompiledContractsHomestead - if evm.chainRules.IsByzantium { - precompiles = PrecompiledContractsByzantium - } - if evm.chainRules.IsIstanbul { - precompiles = PrecompiledContractsIstanbul - } - if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) @@ -226,35 +222,47 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - // Even if the account has no code, we need to continue because it might be a precompile - start := time.Now() + evm.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - - defer func() { // Lazy evaluation of the parameters - evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) - }() + defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters + evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) + }(gas, time.Now()) + } + + if isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } } - ret, err = run(evm, contract, input, false) - // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { - contract.UseGas(contract.Gas) + if err != ErrExecutionReverted { + gas = 0 } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) } - return ret, contract.Gas, err + return ret, gas, err } // This allows the user transfer balance of a specified coinId in addition to a normal Call(). @@ -262,35 +270,27 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte if evm.vmConfig.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 } - // Fail if we're trying to transfer more than the available balance - if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, gas, ErrInsufficientBalance - } - var to = AccountRef(addr) - mcerr := evm.Context.CanTransferMC(evm.StateDB, caller.Address(), to.Address(), coinID, value2) + mcerr := evm.Context.CanTransferMC(evm.StateDB, caller.Address(), addr, coinID, value2) if mcerr == 1 { return nil, gas, ErrInsufficientBalance } else if mcerr != 0 { return nil, gas, ErrIncompatibleAccount } - var snapshot = evm.StateDB.Snapshot() + // Fail if we're trying to transfer more than the available balance + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) if !evm.StateDB.Exist(addr) { - precompiles := PrecompiledContractsHomestead - if evm.chainRules.IsByzantium { - precompiles = PrecompiledContractsByzantium - } - if evm.chainRules.IsIstanbul { - precompiles = PrecompiledContractsIstanbul - } - if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) @@ -300,36 +300,48 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) - evm.TransferMultiCoin(evm.StateDB, caller.Address(), to.Address(), coinID, value2) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - // Even if the account has no code, we need to continue because it might be a precompile - start := time.Now() + evm.Transfer(evm.StateDB, caller.Address(), addr, value) + evm.TransferMultiCoin(evm.StateDB, caller.Address(), addr, coinID, value2) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - - defer func() { // Lazy evaluation of the parameters - evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) - }() + defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters + evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) + }(gas, time.Now()) + } + + if isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } } - ret, err = run(evm, contract, input, false) - // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { - contract.UseGas(contract.Gas) + if err != ErrExecutionReverted { + gas = 0 } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) } - return ret, contract.Gas, err + return ret, gas, err } // CallCode executes the contract associated with the addr with the given input @@ -343,33 +355,38 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if evm.vmConfig.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 } // Fail if we're trying to transfer more than the available balance - if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { + // Note although it's noop to transfer X ether to caller itself. But + // if caller doesn't have enough balance, it would be an error to allow + // over-charging itself. So the check here is necessary. + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } + var snapshot = evm.StateDB.Snapshot() - var ( - snapshot = evm.StateDB.Snapshot() - to = AccountRef(caller.Address()) - ) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - ret, err = run(evm, contract, input, false) + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(caller.Address()), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { - contract.UseGas(contract.Gas) + if err != ErrExecutionReverted { + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } // DelegateCall executes the contract associated with the addr with the given input @@ -385,24 +402,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + var snapshot = evm.StateDB.Snapshot() - var ( - snapshot = evm.StateDB.Snapshot() - to = AccountRef(caller.Address()) - ) - - // Initialise a new contract and make initialise the delegate values - contract := NewContract(caller, to, nil, gas).AsDelegate() - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - ret, err = run(evm, contract, input, false) + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + addrCopy := addr + // Initialise a new contract and make initialise the delegate values + contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { - contract.UseGas(contract.Gas) + if err != ErrExecutionReverted { + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } // StaticCall executes the contract associated with the addr with the given input @@ -417,33 +436,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - - var ( - to = AccountRef(addr) - snapshot = evm.StateDB.Snapshot() - ) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, new(big.Int), gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. + // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced + // after all empty accounts were deleted, so this is not required. However, if we omit this, + // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. + // We could change this, but for now it's left for legacy reasons + var snapshot = evm.StateDB.Snapshot() // We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.StateDB.AddBalance(addr, bigZero) - - // When an error was returned by the EVM or when setting the creation code - // above we revert to the snapshot and consume any gas remaining. Additionally - // when we're in Homestead this also counts for code storage gas errors. - ret, err = run(evm, contract, input, true) + evm.StateDB.AddBalance(addr, big0) + + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // At this point, we use a copy of address. If we don't, the go compiler will + // leak the 'contract' to the outer scope, and make allocation for 'contract' + // even if the actual execution ends on RunPrecompiled above. + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in Homestead this also counts for code storage gas errors. + ret, err = run(evm, contract, input, true) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { - contract.UseGas(contract.Gas) + if err != ErrExecutionReverted { + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } type codeAndHash struct { @@ -520,13 +549,13 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // when we're in homestead this also counts for code storage gas errors. if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) { evm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { + if err != ErrExecutionReverted { contract.UseGas(contract.Gas) } } // Assign err if contract code size exceeds the max while the err is still empty. if maxCodeSizeExceeded && err == nil { - err = errMaxCodeSizeExceeded + err = ErrMaxCodeSizeExceeded } if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) @@ -545,9 +574,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I // // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(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 *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +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} - contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) + contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index ecdccbd..35ce39f 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,374 +17,223 @@ package vm import ( - "errors" - "math/big" - "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) -var ( - bigZero = new(big.Int) - tt255 = math.BigPow(2, 255) - errWriteProtection = errors.New("evm: write protection") - errReturnDataOutOfBounds = errors.New("evm: return data out of bounds") - errExecutionReverted = errors.New("evm: execution reverted") - errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded") - errInvalidJump = errors.New("evm: invalid jump destination") -) - -func opAdd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - math.U256(y.Add(x, y)) - - interpreter.intPool.put(x) +func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Add(&x, y) return nil, nil } -func opSub(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - math.U256(y.Sub(x, y)) - - interpreter.intPool.put(x) +func opSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Sub(&x, y) return nil, nil } -func opMul(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - stack.push(math.U256(x.Mul(x, y))) - - interpreter.intPool.put(y) - +func opMul(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Mul(&x, y) return nil, nil } -func opDiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if y.Sign() != 0 { - math.U256(y.Div(x, y)) - } else { - y.SetUint64(0) - } - interpreter.intPool.put(x) +func opDiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Div(&x, y) return nil, nil } -func opSdiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := math.S256(stack.pop()), math.S256(stack.pop()) - res := interpreter.intPool.getZero() - - if y.Sign() == 0 || x.Sign() == 0 { - stack.push(res) - } else { - if x.Sign() != y.Sign() { - res.Div(x.Abs(x), y.Abs(y)) - res.Neg(res) - } else { - res.Div(x.Abs(x), y.Abs(y)) - } - stack.push(math.U256(res)) - } - interpreter.intPool.put(x, y) +func opSdiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.SDiv(&x, y) return nil, nil } -func opMod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - if y.Sign() == 0 { - stack.push(x.SetUint64(0)) - } else { - stack.push(math.U256(x.Mod(x, y))) - } - interpreter.intPool.put(y) +func opMod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Mod(&x, y) return nil, nil } -func opSmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := math.S256(stack.pop()), math.S256(stack.pop()) - res := interpreter.intPool.getZero() - - if y.Sign() == 0 { - stack.push(res) - } else { - if x.Sign() < 0 { - res.Mod(x.Abs(x), y.Abs(y)) - res.Neg(res) - } else { - res.Mod(x.Abs(x), y.Abs(y)) - } - stack.push(math.U256(res)) - } - interpreter.intPool.put(x, y) +func opSmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.SMod(&x, y) return nil, nil } -func opExp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - base, exponent := stack.pop(), stack.pop() - // some shortcuts - cmpToOne := exponent.Cmp(big1) - if cmpToOne < 0 { // Exponent is zero - // x ^ 0 == 1 - stack.push(base.SetUint64(1)) - } else if base.Sign() == 0 { - // 0 ^ y, if y != 0, == 0 - stack.push(base.SetUint64(0)) - } else if cmpToOne == 0 { // Exponent is one - // x ^ 1 == x - stack.push(base) - } else { - stack.push(math.Exp(base, exponent)) - interpreter.intPool.put(base) - } - interpreter.intPool.put(exponent) +func opExp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + base, exponent := callContext.stack.pop(), callContext.stack.peek() + exponent.Exp(&base, exponent) return nil, nil } -func opSignExtend(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - back := stack.pop() - if back.Cmp(big.NewInt(31)) < 0 { - bit := uint(back.Uint64()*8 + 7) - num := stack.pop() - mask := back.Lsh(common.Big1, bit) - mask.Sub(mask, common.Big1) - if num.Bit(int(bit)) > 0 { - num.Or(num, mask.Not(mask)) - } else { - num.And(num, mask) - } - - stack.push(math.U256(num)) - } - - interpreter.intPool.put(back) +func opSignExtend(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + back, num := callContext.stack.pop(), callContext.stack.peek() + num.ExtendSign(num, &back) return nil, nil } -func opNot(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x := stack.peek() - math.U256(x.Not(x)) +func opNot(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() + x.Not(x) return nil, nil } -func opLt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if x.Cmp(y) < 0 { - y.SetUint64(1) +func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Lt(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opGt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if x.Cmp(y) > 0 { - y.SetUint64(1) +func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Gt(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opSlt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - - xSign := x.Cmp(tt255) - ySign := y.Cmp(tt255) - - switch { - case xSign >= 0 && ySign < 0: - y.SetUint64(1) - - case xSign < 0 && ySign >= 0: - y.SetUint64(0) - - default: - if x.Cmp(y) < 0 { - y.SetUint64(1) - } else { - y.SetUint64(0) - } +func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Slt(y) { + y.SetOne() + } else { + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opSgt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - - xSign := x.Cmp(tt255) - ySign := y.Cmp(tt255) - - switch { - case xSign >= 0 && ySign < 0: - y.SetUint64(0) - - case xSign < 0 && ySign >= 0: - y.SetUint64(1) - - default: - if x.Cmp(y) > 0 { - y.SetUint64(1) - } else { - y.SetUint64(0) - } +func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Sgt(y) { + y.SetOne() + } else { + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opEq(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if x.Cmp(y) == 0 { - y.SetUint64(1) +func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Eq(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opIszero(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x := stack.peek() - if x.Sign() > 0 { - x.SetUint64(0) +func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() + if x.IsZero() { + x.SetOne() } else { - x.SetUint64(1) + x.Clear() } return nil, nil } -func opAnd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - stack.push(x.And(x, y)) - - interpreter.intPool.put(y) +func opAnd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.And(&x, y) return nil, nil } -func opOr(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - y.Or(x, y) - - interpreter.intPool.put(x) +func opOr(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Or(&x, y) return nil, nil } -func opXor(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - y.Xor(x, y) - - interpreter.intPool.put(x) +func opXor(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Xor(&x, y) return nil, nil } -func opByte(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - th, val := stack.pop(), stack.peek() - if th.Cmp(common.Big32) < 0 { - b := math.Byte(val, 32, int(th.Int64())) - val.SetUint64(uint64(b)) - } else { - val.SetUint64(0) - } - interpreter.intPool.put(th) +func opByte(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + th, val := callContext.stack.pop(), callContext.stack.peek() + val.Byte(&th) return nil, nil } -func opAddmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y, z := stack.pop(), stack.pop(), stack.pop() - if z.Cmp(bigZero) > 0 { - x.Add(x, y) - x.Mod(x, z) - stack.push(math.U256(x)) +func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() + if z.IsZero() { + z.Clear() } else { - stack.push(x.SetUint64(0)) + z.AddMod(&x, &y, z) } - interpreter.intPool.put(y, z) return nil, nil } -func opMulmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y, z := stack.pop(), stack.pop(), stack.pop() - if z.Cmp(bigZero) > 0 { - x.Mul(x, y) - x.Mod(x, z) - stack.push(math.U256(x)) - } else { - stack.push(x.SetUint64(0)) - } - interpreter.intPool.put(y, z) +func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() + z.MulMod(&x, &y, z) return nil, nil } // opSHL implements Shift Left // The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the left by arg1 number of bits. -func opSHL(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(stack.pop()), math.U256(stack.peek()) - defer interpreter.intPool.put(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { - value.SetUint64(0) - return nil, nil + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.LtUint64(256) { + value.Lsh(value, uint(shift.Uint64())) + } else { + value.Clear() } - n := uint(shift.Uint64()) - math.U256(value.Lsh(value, n)) - return nil, nil } // opSHR implements Logical Shift Right // The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill. -func opSHR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(stack.pop()), math.U256(stack.peek()) - defer interpreter.intPool.put(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { - value.SetUint64(0) - return nil, nil + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.LtUint64(256) { + value.Rsh(value, uint(shift.Uint64())) + } else { + value.Clear() } - n := uint(shift.Uint64()) - math.U256(value.Rsh(value, n)) - return nil, nil } // opSAR implements Arithmetic Shift Right // The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension. -func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - // Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one - shift, value := math.U256(stack.pop()), math.S256(stack.pop()) - defer interpreter.intPool.put(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { +func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.GtUint64(256) { if value.Sign() >= 0 { - value.SetUint64(0) + value.Clear() } else { - value.SetInt64(-1) + // Max negative shift: all bits set + value.SetAllOne() } - stack.push(math.U256(value)) return nil, nil } n := uint(shift.Uint64()) - value.Rsh(value, n) - stack.push(math.U256(value)) - + value.SRsh(value, n) return nil, nil } -func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - data := memory.Get(offset.Int64(), size.Int64()) +func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.peek() + data := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) if interpreter.hasher == nil { interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState) @@ -398,127 +247,149 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory if evm.vmConfig.EnablePreimageRecording { evm.StateDB.AddPreimage(interpreter.hasherBuf, data) } - stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:])) - interpreter.intPool.put(offset, size) + size.SetBytes(interpreter.hasherBuf[:]) return nil, nil } - -func opAddress(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(contract.Address().Bytes())) +func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Address().Bytes())) return nil, nil } -func opBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() - slot.Set(interpreter.evm.StateDB.GetBalance(common.BigToAddress(slot))) +func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() + address := common.Address(slot.Bytes20()) + slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } -func opBalanceMultiCoin(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - addr, cid := stack.pop(), stack.pop() - stack.push(interpreter.evm.StateDB.GetBalanceMultiCoin(common.BigToAddress(addr), common.BigToHash(cid))) +func opBalanceMultiCoin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + addr, cid := callContext.stack.pop(), callContext.stack.pop() + callContext.stack.push(interpreter.evm.StateDB.GetBalanceMultiCoin(common.BigToAddress(addr), common.BigToHash(cid))) return nil, nil } -func opOrigin(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes())) +func opOrigin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil } - -func opCaller(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(contract.Caller().Bytes())) +func opCaller(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Caller().Bytes())) return nil, nil } -func opCallValue(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().Set(contract.value)) +func opCallValue(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(callContext.contract.value) + callContext.stack.push(v) return nil, nil } -func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(getDataBig(contract.Input, stack.pop(), big32))) +func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() + if offset, overflow := x.Uint64WithOverflow(); !overflow { + data := getData(callContext.contract.Input, offset, 32) + x.SetBytes(data) + } else { + x.Clear() + } return nil, nil } -func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetInt64(int64(len(contract.Input)))) +func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(callContext.contract.Input)))) return nil, nil } -func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - dataOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + dataOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - memory.Set(memOffset.Uint64(), length.Uint64(), getDataBig(contract.Input, dataOffset, length)) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + dataOffset64 = 0xffffffffffffffff + } + // These values are checked for overflow during gas cost calculation + memOffset64 := memOffset.Uint64() + length64 := length.Uint64() + callContext.memory.Set(memOffset64, length64, getData(callContext.contract.Input, dataOffset64, length64)) - interpreter.intPool.put(memOffset, dataOffset, length) return nil, nil } -func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(uint64(len(interpreter.returnData)))) +func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) return nil, nil } -func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - dataOffset = stack.pop() - length = stack.pop() - - end = interpreter.intPool.get().Add(dataOffset, length) + memOffset = callContext.stack.pop() + dataOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - defer interpreter.intPool.put(memOffset, dataOffset, length, end) - if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() { - return nil, errReturnDataOutOfBounds + offset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + return nil, ErrReturnDataOutOfBounds } - memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()]) - + // we can reuse dataOffset now (aliasing it for clarity) + var end = dataOffset + end.Add(&dataOffset, &length) + end64, overflow := end.Uint64WithOverflow() + if overflow || uint64(len(interpreter.returnData)) < end64 { + return nil, ErrReturnDataOutOfBounds + } + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) return nil, nil } -func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.BigToAddress(slot)))) - +func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.Address(slot.Bytes20())))) return nil, nil } -func opCodeSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - l := interpreter.intPool.get().SetInt64(int64(len(contract.Code))) - stack.push(l) - +func opCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + l := new(uint256.Int) + l.SetUint64(uint64(len(callContext.contract.Code))) + callContext.stack.push(l) return nil, nil } -func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - codeOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + codeOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - codeCopy := getDataBig(contract.Code, codeOffset, length) - memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + codeCopy := getData(callContext.contract.Code, uint64CodeOffset, length.Uint64()) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } -func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - addr = common.BigToAddress(stack.pop()) + stack = callContext.stack + a = stack.pop() memOffset = stack.pop() codeOffset = stack.pop() length = stack.pop() ) - codeCopy := getDataBig(interpreter.evm.StateDB.GetCode(addr), codeOffset, length) - memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + addr := common.Address(a.Bytes20()) + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } @@ -548,381 +419,463 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, // // (6) Caller tries to get the code hash for an account which is marked as deleted, // this account should be regarded as a non-existent account and zero should be returned. -func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() - address := common.BigToAddress(slot) +func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() + address := common.Address(slot.Bytes20()) if interpreter.evm.StateDB.Empty(address) { - slot.SetUint64(0) + slot.Clear() } else { slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } return nil, nil } -func opGasprice(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().Set(interpreter.evm.GasPrice)) +func opGasprice(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.GasPrice) + callContext.stack.push(v) return nil, nil } -func opBlockhash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - num := stack.pop() - - n := interpreter.intPool.get().Sub(interpreter.evm.BlockNumber, common.Big257) - if num.Cmp(n) > 0 && num.Cmp(interpreter.evm.BlockNumber) < 0 { - stack.push(interpreter.evm.GetHash(num.Uint64()).Big()) +func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + num := callContext.stack.peek() + num64, overflow := num.Uint64WithOverflow() + if overflow { + num.Clear() + return nil, nil + } + var upper, lower uint64 + upper = interpreter.evm.BlockNumber.Uint64() + if upper < 257 { + lower = 0 } else { - stack.push(interpreter.intPool.getZero()) + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + num.SetBytes(interpreter.evm.GetHash(num64).Bytes()) + } else { + num.Clear() } - interpreter.intPool.put(num, n) return nil, nil } -func opCoinbase(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes())) +func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Coinbase.Bytes())) return nil, nil } -func opTimestamp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Time))) +func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Time) + callContext.stack.push(v) return nil, nil } -func opNumber(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.BlockNumber))) +func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.BlockNumber) + callContext.stack.push(v) return nil, nil } -func opDifficulty(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Difficulty))) +func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Difficulty) + callContext.stack.push(v) return nil, nil } -func opGasLimit(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().SetUint64(interpreter.evm.GasLimit))) +func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.GasLimit)) return nil, nil } -func opPop(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - interpreter.intPool.put(stack.pop()) +func opPop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.pop() return nil, nil } -func opMload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset := stack.pop() - val := interpreter.intPool.get().SetBytes(memory.Get(offset.Int64(), 32)) - stack.push(val) - - interpreter.intPool.put(offset) +func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v := callContext.stack.peek() + offset := int64(v.Uint64()) + v.SetBytes(callContext.memory.GetPtr(offset, 32)) return nil, nil } -func opMstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opMstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // pop value of the stack - mStart, val := stack.pop(), stack.pop() - memory.Set32(mStart.Uint64(), val) - - interpreter.intPool.put(mStart, val) + mStart, val := callContext.stack.pop(), callContext.stack.pop() + callContext.memory.Set32(mStart.Uint64(), &val) return nil, nil } -func opMstore8(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - off, val := stack.pop().Int64(), stack.pop().Int64() - memory.store[off] = byte(val & 0xff) - +func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + off, val := callContext.stack.pop(), callContext.stack.pop() + callContext.memory.store[off.Uint64()] = byte(val.Uint64()) return nil, nil } -func opSload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := stack.peek() - val := interpreter.evm.StateDB.GetState(contract.Address(), common.BigToHash(loc)) +func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + loc := callContext.stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), hash) loc.SetBytes(val.Bytes()) return nil, nil } -func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := common.BigToHash(stack.pop()) - val := stack.pop() - if err := interpreter.evm.StateDB.SetState(contract.Address(), loc, common.BigToHash(val)); err != nil { +func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + loc := callContext.stack.pop() + val := callContext.stack.pop() + if err := interpreter.evm.StateDB.SetState(callContext.contract.Address(), + common.Hash(loc.Bytes32()), common.Hash(val.Bytes32())); err != nil { return nil, err } - interpreter.intPool.put(val) return nil, nil } -func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - pos := stack.pop() - if !contract.validJumpdest(pos) { - return nil, errInvalidJump +func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + pos := callContext.stack.pop() + if !callContext.contract.validJumpdest(&pos) { + return nil, ErrInvalidJump } *pc = pos.Uint64() - - interpreter.intPool.put(pos) return nil, nil } -func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - pos, cond := stack.pop(), stack.pop() - if cond.Sign() != 0 { - if !contract.validJumpdest(pos) { - return nil, errInvalidJump +func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + pos, cond := callContext.stack.pop(), callContext.stack.pop() + if !cond.IsZero() { + if !callContext.contract.validJumpdest(&pos) { + return nil, ErrInvalidJump } *pc = pos.Uint64() } else { *pc++ } + return nil, nil +} - interpreter.intPool.put(pos, cond) +func opJumpdest(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { return nil, nil } -func opJumpdest(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opBeginSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + return nil, ErrInvalidSubroutineEntry +} + +func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + if len(callContext.rstack.data) >= 1023 { + return nil, ErrReturnStackExceeded + } + pos := callContext.stack.pop() + if !pos.IsUint64() { + return nil, ErrInvalidJump + } + posU64 := pos.Uint64() + if !callContext.contract.validJumpSubdest(posU64) { + return nil, ErrInvalidJump + } + callContext.rstack.push(uint32(*pc)) + *pc = posU64 + 1 return nil, nil } -func opPc(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(*pc)) +func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + if len(callContext.rstack.data) == 0 { + return nil, ErrInvalidRetsub + } + // Other than the check that the return stack is not empty, there is no + // need to validate the pc from 'returns', since we only ever push valid + //values onto it via jumpsub. + *pc = uint64(callContext.rstack.pop()) + 1 return nil, nil } -func opMsize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetInt64(int64(memory.Len()))) +func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(*pc)) return nil, nil } -func opGas(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(contract.Gas)) +func opMsize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(uint64(callContext.memory.Len()))) return nil, nil } -func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opGas(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(callContext.contract.Gas)) + return nil, nil +} + +func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - value = stack.pop() - offset, size = stack.pop(), stack.pop() - input = memory.Get(offset.Int64(), size.Int64()) - gas = contract.Gas + value = callContext.stack.pop() + offset, size = callContext.stack.pop(), callContext.stack.pop() + input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = callContext.contract.Gas ) if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } + // reuse size int for stackvalue + stackvalue := size + + callContext.contract.UseGas(gas) + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 + if !value.IsZero() { + bigVal = value.ToBig() + } - contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create(contract, input, gas, value) + res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must // ignore this error and pretend the operation was successful. if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { - stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { - stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else { - stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + stackvalue.SetBytes(addr.Bytes()) } - contract.Gas += returnGas - interpreter.intPool.put(value, offset, size) + callContext.stack.push(&stackvalue) + callContext.contract.Gas += returnGas - if suberr == errExecutionReverted { + if suberr == ErrExecutionReverted { return res, nil } return nil, nil } -func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - endowment = stack.pop() - offset, size = stack.pop(), stack.pop() - salt = stack.pop() - input = memory.Get(offset.Int64(), size.Int64()) - gas = contract.Gas + endowment = callContext.stack.pop() + offset, size = callContext.stack.pop(), callContext.stack.pop() + salt = callContext.stack.pop() + input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = callContext.contract.Gas ) // Apply EIP150 gas -= gas / 64 - contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create2(contract, input, gas, endowment, salt) + callContext.contract.UseGas(gas) + // reuse size int for stackvalue + stackvalue := size + //TODO: use uint256.Int instead of converting with toBig() + bigEndowment := big0 + if !endowment.IsZero() { + bigEndowment = endowment.ToBig() + } + res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, + bigEndowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { - stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else { - stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + stackvalue.SetBytes(addr.Bytes()) } - contract.Gas += returnGas - interpreter.intPool.put(endowment, offset, size, salt) + callContext.stack.push(&stackvalue) + callContext.contract.Gas += returnGas - if suberr == errExecutionReverted { + if suberr == ErrExecutionReverted { return res, nil } return nil, nil } -func opCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + // We can use this as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) - value = math.U256(value) + toAddr := common.Address(addr.Bytes20()) // Get the arguments from the memory. - args := memory.Get(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + var 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), + // but it would make more sense to extend the usage of uint256.Int + if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.Call(contract, toAddr, args, gas, value) + + ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal) + if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } - if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } -func opCallExpert(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallExpert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + // We can use this as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, value, cid, value2, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) + toAddr := common.Address(addr.Bytes20()) coinID := common.BigToHash(cid) - value = math.U256(value) - value2 = math.U256(value2) // Get the arguments from the memory. - args := memory.Get(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + var 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), + // but it would make more sense to extend the usage of uint256.Int + if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.CallExpert(contract, toAddr, args, gas, value, &coinID, value2) + + var bigVal2 = 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), + // but it would make more sense to extend the usage of uint256.Int + if !value2.IsZero() { + bigVal2 = value2.ToBig() + } + + ret, returnGas, err := interpreter.evm.CallExpert(callContext.contract, toAddr, args, gas, bigVal, &coinID, bigVal2) + if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } - if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } - -func opCallCode(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + stack := callContext.stack + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) - value = math.U256(value) + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := memory.Get(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 + if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.CallCode(contract, toAddr, args, gas, value) + + ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal) if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } - if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } -func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := memory.Get(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - ret, returnGas, err := interpreter.evm.DelegateCall(contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.DelegateCall(callContext.contract, toAddr, args, gas) if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } - if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } -func opStaticCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + stack := callContext.stack + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := memory.Get(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - ret, returnGas, err := interpreter.evm.StaticCall(contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.StaticCall(callContext.contract, toAddr, args, gas) if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } - if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } -func opReturn(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - ret := memory.GetPtr(offset.Int64(), size.Int64()) +func opReturn(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - interpreter.intPool.put(offset, size) return ret, nil } -func opRevert(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - ret := memory.GetPtr(offset.Int64(), size.Int64()) +func opRevert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - interpreter.intPool.put(offset, size) return ret, nil } -func opStop(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { return nil, nil } -func opSuicide(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - balance := interpreter.evm.StateDB.GetBalance(contract.Address()) - interpreter.evm.StateDB.AddBalance(common.BigToAddress(stack.pop()), balance) - - interpreter.evm.StateDB.Suicide(contract.Address()) +func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + beneficiary := callContext.stack.pop() + balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) + interpreter.evm.StateDB.AddBalance(common.Address(beneficiary.Bytes20()), balance) + interpreter.evm.StateDB.Suicide(callContext.contract.Address()) return nil, nil } -func opEMC(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opEMC(pc *uint64, interpreter *EVMInterpreter, contract *Contract, callContext *callCtx) ([]byte, error) { return nil, interpreter.evm.StateDB.EnableMultiCoin(contract.Address()) } @@ -930,16 +883,18 @@ func opEMC(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * // make log instruction function func makeLog(size int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { topics := make([]common.Hash, size) + stack := callContext.stack mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { - topics[i] = common.BigToHash(stack.pop()) + addr := stack.pop() + topics[i] = common.Hash(addr.Bytes32()) } - d := memory.Get(mStart.Int64(), mSize.Int64()) + d := callContext.memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) interpreter.evm.StateDB.AddLog(&types.Log{ - Address: contract.Address(), + Address: callContext.contract.Address(), Topics: topics, Data: d, // This is a non-consensus field, but assigned here because @@ -947,30 +902,29 @@ func makeLog(size int) executionFunc { BlockNumber: interpreter.evm.BlockNumber.Uint64(), }) - interpreter.intPool.put(mStart, mSize) return nil, nil } } // opPush1 is a specialized version of pushN -func opPush1(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opPush1(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - codeLen = uint64(len(contract.Code)) - integer = interpreter.intPool.get() + codeLen = uint64(len(callContext.contract.Code)) + integer = new(uint256.Int) ) *pc += 1 if *pc < codeLen { - stack.push(integer.SetUint64(uint64(contract.Code[*pc]))) + callContext.stack.push(integer.SetUint64(uint64(callContext.contract.Code[*pc]))) } else { - stack.push(integer.SetUint64(0)) + callContext.stack.push(integer.Clear()) } return nil, nil } // make push instruction function func makePush(size uint64, pushByteSize int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - codeLen := len(contract.Code) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + codeLen := len(callContext.contract.Code) startMin := codeLen if int(*pc+1) < startMin { @@ -982,8 +936,9 @@ func makePush(size uint64, pushByteSize int) executionFunc { endMin = startMin + pushByteSize } - integer := interpreter.intPool.get() - stack.push(integer.SetBytes(common.RightPadBytes(contract.Code[startMin:endMin], pushByteSize))) + integer := new(uint256.Int) + callContext.stack.push(integer.SetBytes(common.RightPadBytes( + callContext.contract.Code[startMin:endMin], pushByteSize))) *pc += size return nil, nil @@ -992,8 +947,8 @@ func makePush(size uint64, pushByteSize int) executionFunc { // make dup instruction function func makeDup(size int64) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.dup(interpreter.intPool, int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.dup(int(size)) return nil, nil } } @@ -1002,8 +957,8 @@ func makeDup(size int64) executionFunc { func makeSwap(size int64) executionFunc { // switch n + 1 otherwise n would be swapped with n size++ - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.swap(int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.swap(int(size)) return nil, nil } } diff --git a/core/vm/interface.go b/core/vm/interface.go index 7e2d324..4f95423 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -20,7 +20,7 @@ import ( "math/big" "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/go-ethereum/common" + "github.com/ethereum/go-ethereum/common" ) // StateDB is an EVM database for full state querying. diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index e23896a..a114a8d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,13 +17,12 @@ package vm import ( - "fmt" "hash" "sync/atomic" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" - "github.com/ava-labs/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/log" ) var ( @@ -40,7 +39,7 @@ type Config struct { NoRecursion bool // Disables call, callcode, delegate call and create EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages - JumpTable [256]operation // EVM instruction table, automatically populated if unset + JumpTable [256]*operation // EVM instruction table, automatically populated if unset EWASMInterpreter string // External EWASM interpreter options EVMInterpreter string // External EVM interpreter options @@ -70,6 +69,15 @@ type Interpreter interface { CanRun([]byte) bool } +// callCtx contains the things that are per-call, such as stack and memory, +// but not transients like pc and gas +type callCtx struct { + memory *Memory + stack *Stack + rstack *ReturnStack + contract *Contract +} + // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports // Read to get a variable amount of data from the hash state. Read is faster than Sum // because it doesn't copy the internal state, but also modifies the internal state. @@ -83,8 +91,6 @@ type EVMInterpreter struct { evm *EVM cfg Config - intPool *intPool - hasher keccakState // Keccak256 hasher instance shared across opcodes hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes @@ -97,9 +103,11 @@ 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].valid { + if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { + case evm.chainRules.IsYoloV1: + jt = yoloV1InstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet case evm.chainRules.IsConstantinople: @@ -136,7 +144,7 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { // // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-gas operation except for -// errExecutionReverted which means revert-and-keep-gas-left. +// 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 { self := AccountRef(contract.Caller()) @@ -145,13 +153,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } contract.self = self } - if in.intPool == nil { - in.intPool = poolOfIntPools.get() - defer func() { - poolOfIntPools.put(in.intPool) - in.intPool = nil - }() - } // Increment the call depth which is restricted to 1024 in.evm.depth++ @@ -174,9 +175,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } var ( - op OpCode // current opcode - mem = NewMemory() // bound memory - stack = newstack() // local stack + op OpCode // current opcode + mem = NewMemory() // bound memory + stack = newstack() // local stack + returns = newReturnStack() // local returns stack + callContext = &callCtx{ + memory: mem, + stack: stack, + rstack: returns, + contract: contract, + } // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC // to be uint256. Practically much less so feasible. @@ -188,18 +196,22 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged bool // deferred Tracer should ignore already logged steps res []byte // result of the opcode execution function ) + // Don't move this deferrred function, it's placed before the capturestate-deferred method, + // so that it get's executed _after_: the capturestate needs the stacks before + // they are returned to the pools + defer func() { + returnStack(stack) + returnRStack(returns) + }() contract.Input = input - // Reclaim the stack as an int pool when the execution stops - defer func() { in.intPool.put(stack.data...) }() - if in.cfg.Debug { defer func() { if err != nil { if !logged { - in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) } else { - in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) } } }() @@ -208,7 +220,12 @@ 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. - for atomic.LoadInt32(&in.evm.abort) == 0 { + 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 @@ -218,14 +235,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // enough stack items available to perform the operation. op = contract.GetOp(pc) operation := in.cfg.JumpTable[op] - if !operation.valid { - return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) + if operation == nil { + return nil, &ErrInvalidOpCode{opcode: op} } // Validate stack if sLen := stack.len(); sLen < operation.minStack { - return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack) + return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} } else if sLen > operation.maxStack { - return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack) + return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } // If the operation is valid, enforce and write restrictions if in.readOnly && in.evm.chainRules.IsByzantium { @@ -235,7 +252,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // account to the others means the state is modified and should also // return with an error. if operation.writes || ((op == CALL || op == CALLEX) && stack.Back(2).Sign() != 0) { - return nil, errWriteProtection + return nil, ErrWriteProtection } } // Static portion of gas @@ -252,12 +269,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if operation.memorySize != nil { memSize, overflow := operation.memorySize(stack) if overflow { - return nil, errGasUintOverflow + 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 + return nil, ErrGasUintOverflow } } // Dynamic portion of gas @@ -276,28 +293,23 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } if in.cfg.Debug { - in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) logged = true } // execute the operation - res, err = operation.execute(&pc, in, contract, mem, stack) - // verifyPool is a build flag. Pool verification makes sure the integrity - // of the integer pool by comparing values to a default value. - if verifyPool { - verifyIntegerPool(in.intPool) - } + 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 + in.returnData = common.CopyBytes(res) } switch { case err != nil: return nil, err case operation.reverts: - return res, errExecutionReverted + return res, ErrExecutionReverted case operation.halts: return res, nil case !operation.jumps: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 737dd14..7e3e354 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -17,20 +17,16 @@ package vm import ( - "errors" - "github.com/ava-labs/coreth/params" ) type ( - executionFunc func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) ) -var errGasUintOverflow = errors.New("gas uint64 overflow") - type operation struct { // execute is the operation function execute executionFunc @@ -48,7 +44,6 @@ type operation struct { 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 - valid bool // indication whether the retrieved operation is valid and known reverts bool // determines whether the operation reverts state (implicitly halts) returns bool // determines whether the operations sets the return data content } @@ -61,10 +56,19 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() + yoloV1InstructionSet = newYoloV1InstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. -type JumpTable [256]operation +type JumpTable [256]*operation + +func newYoloV1InstructionSet() JumpTable { + instructionSet := newIstanbulInstructionSet() + + enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 + + return instructionSet +} // newIstanbulInstructionSet returns the frontier, homestead // byzantium, contantinople and petersburg instructions. @@ -82,42 +86,37 @@ func newIstanbulInstructionSet() JumpTable { // byzantium and contantinople instructions. func newConstantinopleInstructionSet() JumpTable { instructionSet := newByzantiumInstructionSet() - instructionSet[SHL] = operation{ + instructionSet[SHL] = &operation{ execute: opSHL, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, } - instructionSet[SHR] = operation{ + instructionSet[SHR] = &operation{ execute: opSHR, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, } - instructionSet[SAR] = operation{ + instructionSet[SAR] = &operation{ execute: opSAR, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, } - instructionSet[EXTCODEHASH] = operation{ + instructionSet[EXTCODEHASH] = &operation{ execute: opExtCodeHash, constantGas: params.ExtcodeHashGasConstantinople, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, } - instructionSet[CREATE2] = operation{ + instructionSet[CREATE2] = &operation{ execute: opCreate2, constantGas: params.Create2Gas, dynamicGas: gasCreate2, minStack: minStack(4, 1), maxStack: maxStack(4, 1), memorySize: memoryCreate2, - valid: true, writes: true, returns: true, } @@ -128,39 +127,35 @@ func newConstantinopleInstructionSet() JumpTable { // byzantium instructions. func newByzantiumInstructionSet() JumpTable { instructionSet := newSpuriousDragonInstructionSet() - instructionSet[STATICCALL] = operation{ + instructionSet[STATICCALL] = &operation{ execute: opStaticCall, constantGas: params.CallGasEIP150, dynamicGas: gasStaticCall, minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryStaticCall, - valid: true, returns: true, } - instructionSet[RETURNDATASIZE] = operation{ + instructionSet[RETURNDATASIZE] = &operation{ execute: opReturnDataSize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, } - instructionSet[RETURNDATACOPY] = operation{ + instructionSet[RETURNDATACOPY] = &operation{ execute: opReturnDataCopy, constantGas: GasFastestStep, dynamicGas: gasReturnDataCopy, minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryReturnDataCopy, - valid: true, } - instructionSet[REVERT] = operation{ + instructionSet[REVERT] = &operation{ execute: opRevert, dynamicGas: gasRevert, minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryRevert, - valid: true, reverts: true, returns: true, } @@ -193,14 +188,13 @@ func newTangerineWhistleInstructionSet() JumpTable { // instructions that can be executed during the homestead phase. func newHomesteadInstructionSet() JumpTable { instructionSet := newFrontierInstructionSet() - instructionSet[DELEGATECALL] = operation{ + instructionSet[DELEGATECALL] = &operation{ execute: opDelegateCall, dynamicGas: gasDelegateCall, constantGas: params.CallGasFrontier, minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryDelegateCall, - valid: true, returns: true, } return instructionSet @@ -216,161 +210,138 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(0, 0), maxStack: maxStack(0, 0), halts: true, - valid: true, }, ADD: { execute: opAdd, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, MUL: { execute: opMul, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SUB: { execute: opSub, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, DIV: { execute: opDiv, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SDIV: { execute: opSdiv, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, MOD: { execute: opMod, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SMOD: { execute: opSmod, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, ADDMOD: { execute: opAddmod, constantGas: GasMidStep, minStack: minStack(3, 1), maxStack: maxStack(3, 1), - valid: true, }, MULMOD: { execute: opMulmod, constantGas: GasMidStep, minStack: minStack(3, 1), maxStack: maxStack(3, 1), - valid: true, }, EXP: { execute: opExp, dynamicGas: gasExpFrontier, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SIGNEXTEND: { execute: opSignExtend, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, LT: { execute: opLt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, GT: { execute: opGt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SLT: { execute: opSlt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SGT: { execute: opSgt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, EQ: { execute: opEq, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, ISZERO: { execute: opIszero, constantGas: GasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, AND: { execute: opAnd, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, XOR: { execute: opXor, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, OR: { execute: opOr, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, NOT: { execute: opNot, constantGas: GasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, BYTE: { execute: opByte, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SHA3: { execute: opSha3, @@ -379,63 +350,54 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 1), maxStack: maxStack(2, 1), memorySize: memorySha3, - valid: true, }, ADDRESS: { execute: opAddress, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, BALANCE: { execute: opBalance, constantGas: params.BalanceGasFrontier, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, BALANCEMC: { execute: opBalanceMultiCoin, constantGas: params.BalanceGasFrontier, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, ORIGIN: { execute: opOrigin, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLER: { execute: opCaller, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLVALUE: { execute: opCallValue, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLDATALOAD: { execute: opCallDataLoad, constantGas: GasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, CALLDATASIZE: { execute: opCallDataSize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLDATACOPY: { execute: opCallDataCopy, @@ -444,14 +406,12 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryCallDataCopy, - valid: true, }, CODESIZE: { execute: opCodeSize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CODECOPY: { execute: opCodeCopy, @@ -460,21 +420,18 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryCodeCopy, - valid: true, }, GASPRICE: { execute: opGasprice, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, EXTCODESIZE: { execute: opExtCodeSize, constantGas: params.ExtcodeSizeGasFrontier, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, EXTCODECOPY: { execute: opExtCodeCopy, @@ -483,56 +440,48 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(4, 0), maxStack: maxStack(4, 0), memorySize: memoryExtCodeCopy, - valid: true, }, BLOCKHASH: { execute: opBlockhash, constantGas: GasExtStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, COINBASE: { execute: opCoinbase, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, TIMESTAMP: { execute: opTimestamp, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, NUMBER: { execute: opNumber, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, DIFFICULTY: { execute: opDifficulty, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, GASLIMIT: { execute: opGasLimit, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, POP: { execute: opPop, constantGas: GasQuickStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), - valid: true, }, MLOAD: { execute: opMload, @@ -541,7 +490,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 1), maxStack: maxStack(1, 1), memorySize: memoryMLoad, - valid: true, }, MSTORE: { execute: opMstore, @@ -550,7 +498,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryMStore, - valid: true, }, MSTORE8: { execute: opMstore8, @@ -559,22 +506,18 @@ func newFrontierInstructionSet() JumpTable { memorySize: memoryMStore8, minStack: minStack(2, 0), maxStack: maxStack(2, 0), - - valid: true, }, SLOAD: { execute: opSload, constantGas: params.SloadGasFrontier, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, SSTORE: { execute: opSstore, dynamicGas: gasSStore, minStack: minStack(2, 0), maxStack: maxStack(2, 0), - valid: true, writes: true, }, JUMP: { @@ -583,7 +526,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 0), maxStack: maxStack(1, 0), jumps: true, - valid: true, }, JUMPI: { execute: opJumpi, @@ -591,490 +533,420 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), jumps: true, - valid: true, }, PC: { execute: opPc, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, MSIZE: { execute: opMsize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, GAS: { execute: opGas, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, JUMPDEST: { execute: opJumpdest, constantGas: params.JumpdestGas, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - valid: true, }, EMC: { execute: opEMC, constantGas: params.EMCGas, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - valid: true, }, PUSH1: { execute: opPush1, constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH2: { execute: makePush(2, 2), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH3: { execute: makePush(3, 3), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH4: { execute: makePush(4, 4), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH5: { execute: makePush(5, 5), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH6: { execute: makePush(6, 6), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH7: { execute: makePush(7, 7), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH8: { execute: makePush(8, 8), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH9: { execute: makePush(9, 9), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH10: { execute: makePush(10, 10), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH11: { execute: makePush(11, 11), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH12: { execute: makePush(12, 12), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH13: { execute: makePush(13, 13), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH14: { execute: makePush(14, 14), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH15: { execute: makePush(15, 15), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH16: { execute: makePush(16, 16), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH17: { execute: makePush(17, 17), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH18: { execute: makePush(18, 18), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH19: { execute: makePush(19, 19), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH20: { execute: makePush(20, 20), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH21: { execute: makePush(21, 21), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH22: { execute: makePush(22, 22), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH23: { execute: makePush(23, 23), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH24: { execute: makePush(24, 24), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH25: { execute: makePush(25, 25), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH26: { execute: makePush(26, 26), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH27: { execute: makePush(27, 27), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH28: { execute: makePush(28, 28), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH29: { execute: makePush(29, 29), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH30: { execute: makePush(30, 30), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH31: { execute: makePush(31, 31), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH32: { execute: makePush(32, 32), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, DUP1: { execute: makeDup(1), constantGas: GasFastestStep, minStack: minDupStack(1), maxStack: maxDupStack(1), - valid: true, }, DUP2: { execute: makeDup(2), constantGas: GasFastestStep, minStack: minDupStack(2), maxStack: maxDupStack(2), - valid: true, }, DUP3: { execute: makeDup(3), constantGas: GasFastestStep, minStack: minDupStack(3), maxStack: maxDupStack(3), - valid: true, }, DUP4: { execute: makeDup(4), constantGas: GasFastestStep, minStack: minDupStack(4), maxStack: maxDupStack(4), - valid: true, }, DUP5: { execute: makeDup(5), constantGas: GasFastestStep, minStack: minDupStack(5), maxStack: maxDupStack(5), - valid: true, }, DUP6: { execute: makeDup(6), constantGas: GasFastestStep, minStack: minDupStack(6), maxStack: maxDupStack(6), - valid: true, }, DUP7: { execute: makeDup(7), constantGas: GasFastestStep, minStack: minDupStack(7), maxStack: maxDupStack(7), - valid: true, }, DUP8: { execute: makeDup(8), constantGas: GasFastestStep, minStack: minDupStack(8), maxStack: maxDupStack(8), - valid: true, }, DUP9: { execute: makeDup(9), constantGas: GasFastestStep, minStack: minDupStack(9), maxStack: maxDupStack(9), - valid: true, }, DUP10: { execute: makeDup(10), constantGas: GasFastestStep, minStack: minDupStack(10), maxStack: maxDupStack(10), - valid: true, }, DUP11: { execute: makeDup(11), constantGas: GasFastestStep, minStack: minDupStack(11), maxStack: maxDupStack(11), - valid: true, }, DUP12: { execute: makeDup(12), constantGas: GasFastestStep, minStack: minDupStack(12), maxStack: maxDupStack(12), - valid: true, }, DUP13: { execute: makeDup(13), constantGas: GasFastestStep, minStack: minDupStack(13), maxStack: maxDupStack(13), - valid: true, }, DUP14: { execute: makeDup(14), constantGas: GasFastestStep, minStack: minDupStack(14), maxStack: maxDupStack(14), - valid: true, }, DUP15: { execute: makeDup(15), constantGas: GasFastestStep, minStack: minDupStack(15), maxStack: maxDupStack(15), - valid: true, }, DUP16: { execute: makeDup(16), constantGas: GasFastestStep, minStack: minDupStack(16), maxStack: maxDupStack(16), - valid: true, }, SWAP1: { execute: makeSwap(1), constantGas: GasFastestStep, minStack: minSwapStack(2), maxStack: maxSwapStack(2), - valid: true, }, SWAP2: { execute: makeSwap(2), constantGas: GasFastestStep, minStack: minSwapStack(3), maxStack: maxSwapStack(3), - valid: true, }, SWAP3: { execute: makeSwap(3), constantGas: GasFastestStep, minStack: minSwapStack(4), maxStack: maxSwapStack(4), - valid: true, }, SWAP4: { execute: makeSwap(4), constantGas: GasFastestStep, minStack: minSwapStack(5), maxStack: maxSwapStack(5), - valid: true, }, SWAP5: { execute: makeSwap(5), constantGas: GasFastestStep, minStack: minSwapStack(6), maxStack: maxSwapStack(6), - valid: true, }, SWAP6: { execute: makeSwap(6), constantGas: GasFastestStep, minStack: minSwapStack(7), maxStack: maxSwapStack(7), - valid: true, }, SWAP7: { execute: makeSwap(7), constantGas: GasFastestStep, minStack: minSwapStack(8), maxStack: maxSwapStack(8), - valid: true, }, SWAP8: { execute: makeSwap(8), constantGas: GasFastestStep, minStack: minSwapStack(9), maxStack: maxSwapStack(9), - valid: true, }, SWAP9: { execute: makeSwap(9), constantGas: GasFastestStep, minStack: minSwapStack(10), maxStack: maxSwapStack(10), - valid: true, }, SWAP10: { execute: makeSwap(10), constantGas: GasFastestStep, minStack: minSwapStack(11), maxStack: maxSwapStack(11), - valid: true, }, SWAP11: { execute: makeSwap(11), constantGas: GasFastestStep, minStack: minSwapStack(12), maxStack: maxSwapStack(12), - valid: true, }, SWAP12: { execute: makeSwap(12), constantGas: GasFastestStep, minStack: minSwapStack(13), maxStack: maxSwapStack(13), - valid: true, }, SWAP13: { execute: makeSwap(13), constantGas: GasFastestStep, minStack: minSwapStack(14), maxStack: maxSwapStack(14), - valid: true, }, SWAP14: { execute: makeSwap(14), constantGas: GasFastestStep, minStack: minSwapStack(15), maxStack: maxSwapStack(15), - valid: true, }, SWAP15: { execute: makeSwap(15), constantGas: GasFastestStep, minStack: minSwapStack(16), maxStack: maxSwapStack(16), - valid: true, }, SWAP16: { execute: makeSwap(16), constantGas: GasFastestStep, minStack: minSwapStack(17), maxStack: maxSwapStack(17), - valid: true, }, LOG0: { execute: makeLog(0), @@ -1082,7 +954,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG1: { @@ -1091,7 +962,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG2: { @@ -1100,7 +970,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(4, 0), maxStack: maxStack(4, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG3: { @@ -1109,7 +978,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(5, 0), maxStack: maxStack(5, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG4: { @@ -1118,7 +986,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(6, 0), maxStack: maxStack(6, 0), memorySize: memoryLog, - valid: true, writes: true, }, CREATE: { @@ -1128,7 +995,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 1), maxStack: maxStack(3, 1), memorySize: memoryCreate, - valid: true, writes: true, returns: true, }, @@ -1139,7 +1005,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(7, 1), maxStack: maxStack(7, 1), memorySize: memoryCall, - valid: true, returns: true, }, CALLEX: { @@ -1149,7 +1014,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(9, 1), maxStack: maxStack(9, 1), memorySize: memoryCallExpert, - valid: true, returns: true, }, CALLCODE: { @@ -1159,7 +1023,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(7, 1), maxStack: maxStack(7, 1), memorySize: memoryCall, - valid: true, returns: true, }, RETURN: { @@ -1169,7 +1032,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(2, 0), memorySize: memoryReturn, halts: true, - valid: true, }, SELFDESTRUCT: { execute: opSuicide, @@ -1177,7 +1039,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 0), maxStack: maxStack(1, 0), halts: true, - valid: true, writes: true, }, } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 1a66ef8..99688b1 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -70,7 +70,7 @@ const ( SHR SAR - SHA3 = 0x20 + SHA3 OpCode = 0x20 ) // 0x30 range - closure state. @@ -101,26 +101,27 @@ const ( NUMBER DIFFICULTY GASLIMIT - CHAINID = 0x46 - SELFBALANCE = 0x47 + CHAINID OpCode = 0x46 + SELFBALANCE OpCode = 0x47 ) // 0x50 range - 'storage' and execution. const ( - POP OpCode = 0x50 + iota - MLOAD - MSTORE - MSTORE8 - SLOAD - SSTORE - JUMP - JUMPI - PC - MSIZE - GAS - JUMPDEST - EMC = 0x5c - BALANCEMC = 0x5d + POP OpCode = 0x50 + MLOAD OpCode = 0x51 + MSTORE OpCode = 0x52 + MSTORE8 OpCode = 0x53 + SLOAD OpCode = 0x54 + SSTORE OpCode = 0x55 + JUMP OpCode = 0x56 + JUMPI OpCode = 0x57 + PC OpCode = 0x58 + MSIZE OpCode = 0x59 + GAS OpCode = 0x5a + JUMPDEST OpCode = 0x5b + BEGINSUB OpCode = 0x5c + RETURNSUB OpCode = 0x5d + JUMPSUB OpCode = 0x5e ) // 0x60 range. @@ -207,6 +208,12 @@ const ( SWAP ) +const ( + BALANCEMC = 0xcd + EMC = 0xce + CALLEX = 0xcf +) + // 0xf0 range - closures. const ( CREATE OpCode = 0xf0 + iota @@ -215,11 +222,9 @@ const ( RETURN DELEGATECALL CREATE2 - CALLEX = 0xf6 - STATICCALL = 0xfa - - REVERT = 0xfd - SELFDESTRUCT = 0xff + STATICCALL OpCode = 0xfa + REVERT OpCode = 0xfd + SELFDESTRUCT OpCode = 0xff ) // Since the opcodes aren't all in order we can't use a regular slice. @@ -301,7 +306,10 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", - EMC: "EMC", + + BEGINSUB: "BEGINSUB", + JUMPSUB: "JUMPSUB", + RETURNSUB: "RETURNSUB", // 0x60 range - push. PUSH1: "PUSH1", @@ -379,6 +387,7 @@ var opCodeToString = map[OpCode]string{ // 0xf0 range. CREATE: "CREATE", CALL: "CALL", + EMC: "EMC", CALLEX: "CALLEX", RETURN: "RETURN", CALLCODE: "CALLCODE", @@ -396,7 +405,7 @@ var opCodeToString = map[OpCode]string{ func (op OpCode) String() string { str := opCodeToString[op] if len(str) == 0 { - return fmt.Sprintf("Missing opcode 0x%x", int(op)) + return fmt.Sprintf("opcode 0x%x not defined", int(op)) } return str @@ -469,7 +478,9 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, - "EMC": EMC, + "BEGINSUB": BEGINSUB, + "RETURNSUB": RETURNSUB, + "JUMPSUB": JUMPSUB, "PUSH1": PUSH1, "PUSH2": PUSH2, "PUSH3": PUSH3, @@ -542,6 +553,7 @@ var stringToOp = map[string]OpCode{ "CREATE": CREATE, "CREATE2": CREATE2, "CALL": CALL, + "EMC": EMC, "CALLEX": CALLEX, "RETURN": RETURN, "CALLCODE": CALLCODE, diff --git a/eth/api.go b/eth/api.go index b64090e..92ac928 100644 --- a/eth/api.go +++ b/eth/api.go @@ -34,10 +34,10 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/internal/ethapi" "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/hexutil" - "github.com/ava-labs/go-ethereum/rlp" - "github.com/ava-labs/go-ethereum/trie" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) // PublicEthereumAPI provides an API to access Ethereum full node-related @@ -166,8 +166,21 @@ func NewPrivateAdminAPI(eth *Ethereum) *PrivateAdminAPI { return &PrivateAdminAPI{eth: eth} } -// ExportChain exports the current blockchain into a local file. -func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { +// ExportChain exports the current blockchain into a local file, +// or a range of blocks if first and last are non-nil +func (api *PrivateAdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool, error) { + if first == nil && last != nil { + return false, errors.New("last cannot be specified without first") + } + if first != nil && last == nil { + head := api.eth.BlockChain().CurrentHeader().Number.Uint64() + last = &head + } + if _, err := os.Stat(file); err == nil { + // File already exists. Allowing overwrite could be a DoS vecotor, + // since the 'file' may point to arbitrary paths on the drive + return false, errors.New("location would overwrite an existing file") + } // Make sure we can create the file to export into out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) if err != nil { @@ -182,7 +195,11 @@ func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { } // Export the blockchain - if err := api.eth.BlockChain().Export(writer); err != nil { + if first != nil { + if err := api.eth.BlockChain().ExportN(writer, *first, *last); err != nil { + return false, err + } + } else if err := api.eth.BlockChain().Export(writer); err != nil { return false, err } return true, nil @@ -336,70 +353,52 @@ func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, return results, nil } -// AccountRangeResult returns a mapping from the hash of an account addresses -// to its preimage. It will return the JSON null if no preimage is found. -// Since a query can return a limited amount of results, a "next" field is -// also present for paging. -type AccountRangeResult struct { - Accounts map[common.Hash]*common.Address `json:"accounts"` - Next common.Hash `json:"next"` -} - -func accountRange(st state.Trie, start *common.Hash, maxResults int) (AccountRangeResult, error) { - if start == nil { - start = &common.Hash{0} - } - it := trie.NewIterator(st.NodeIterator(start.Bytes())) - result := AccountRangeResult{Accounts: make(map[common.Hash]*common.Address), Next: common.Hash{}} - - if maxResults > AccountRangeMaxResults { - maxResults = AccountRangeMaxResults - } - - for i := 0; i < maxResults && it.Next(); i++ { - if preimage := st.GetKey(it.Key); preimage != nil { - addr := &common.Address{} - addr.SetBytes(preimage) - result.Accounts[common.BytesToHash(it.Key)] = addr - } else { - result.Accounts[common.BytesToHash(it.Key)] = nil - } - } - - if it.Next() { - result.Next = common.BytesToHash(it.Key) - } - - return result, nil -} - // AccountRangeMaxResults is the maximum number of results to be returned per call const AccountRangeMaxResults = 256 -// AccountRange enumerates all accounts in the latest state -func (api *PrivateDebugAPI) AccountRange(ctx context.Context, start *common.Hash, maxResults int) (AccountRangeResult, error) { - var statedb *state.StateDB +// AccountRange enumerates all accounts in the given block and start point in paging request +func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) { + var stateDb *state.StateDB var err error - block := api.eth.blockchain.CurrentBlock() - if len(block.Transactions()) == 0 { - statedb, err = api.computeStateDB(block, defaultTraceReexec) - if err != nil { - return AccountRangeResult{}, err + if number, ok := blockNrOrHash.Number(); ok { + if number == rpc.PendingBlockNumber { + // If we're dumping the pending state, we need to request + // both the pending block as well as the pending state from + // the miner and operate on those + _, stateDb = api.eth.miner.Pending() + } else { + var block *types.Block + if number == rpc.LatestBlockNumber { + block = api.eth.blockchain.CurrentBlock() + } else if blockNr == rpc.AcceptedBlockNumber { + block = api.eth.AcceptedBlock() + } else { + block = api.eth.blockchain.GetBlockByNumber(uint64(number)) + } + if block == nil { + return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) + } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) + if err != nil { + return state.IteratorDump{}, err + } } - } else { - _, _, statedb, err = api.computeTxEnv(block.Hash(), len(block.Transactions())-1, 0) + } else if hash, ok := blockNrOrHash.Hash(); ok { + block := api.eth.blockchain.GetBlockByHash(hash) + if block == nil { + return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) + } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) if err != nil { - return AccountRangeResult{}, err + return state.IteratorDump{}, err } } - trie, err := statedb.Database().OpenTrie(block.Header().Root) - if err != nil { - return AccountRangeResult{}, err + if maxResults > AccountRangeMaxResults || maxResults <= 0 { + maxResults = AccountRangeMaxResults } - - return accountRange(trie, start, maxResults) + return stateDb.IteratorDump(nocode, nostorage, incompletes, start, maxResults), nil } // StorageRangeResult is the result of a debug_storageRangeAt API call. @@ -416,8 +415,13 @@ type storageEntry struct { } // StorageRangeAt returns the storage at the given block height and transaction index. -func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { - _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0) +func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { + // Retrieve the block + block := api.eth.blockchain.GetBlockByHash(blockHash) + if block == nil { + return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) + } + _, _, statedb, err := api.computeTxEnv(block, txIndex, 0) if err != nil { return StorageRangeResult{}, err } diff --git a/eth/api_backend.go b/eth/api_backend.go index d4061f8..65c3be4 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -22,20 +22,21 @@ import ( "math/big" "github.com/ava-labs/coreth/accounts" + "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/eth/gasprice" + "github.com/ava-labs/coreth/miner" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/math" - "github.com/ava-labs/go-ethereum/core/bloombits" - "github.com/ava-labs/go-ethereum/eth/downloader" - "github.com/ava-labs/go-ethereum/ethdb" - "github.com/ava-labs/go-ethereum/event" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" ) // EthAPIBackend implements ethapi.Backend for full nodes @@ -79,6 +80,23 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil } +func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { + if blockNr, ok := blockNrOrHash.Number(); ok { + return b.HeaderByNumber(ctx, blockNr) + } + if hash, ok := blockNrOrHash.Hash(); ok { + header := b.eth.blockchain.GetHeaderByHash(hash) + if header == nil { + return nil, errors.New("header for hash not found") + } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { + return nil, errors.New("hash is not currently canonical") + } + return header, nil + } + return nil, errors.New("invalid arguments; neither block nor hash specified") +} + func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { return b.eth.blockchain.GetHeaderByHash(hash), nil } @@ -103,6 +121,27 @@ func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*typ return b.eth.blockchain.GetBlockByHash(hash), nil } +func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { + if blockNr, ok := blockNrOrHash.Number(); ok { + return b.BlockByNumber(ctx, blockNr) + } + if hash, ok := blockNrOrHash.Hash(); ok { + header := b.eth.blockchain.GetHeaderByHash(hash) + if header == nil { + return nil, errors.New("header for hash not found") + } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { + return nil, errors.New("hash is not currently canonical") + } + block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64()) + if block == nil { + return nil, errors.New("header found, but block body is missing") + } + return block, nil + } + return nil, errors.New("invalid arguments; neither block nor hash specified") +} + func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { // Pending state is only known by the miner if number == rpc.PendingBlockNumber { @@ -121,6 +160,27 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B return stateDb, header, err } +func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { + if blockNr, ok := blockNrOrHash.Number(); ok { + return b.StateAndHeaderByNumber(ctx, blockNr) + } + if hash, ok := blockNrOrHash.Hash(); ok { + header, err := b.HeaderByHash(ctx, hash) + if err != nil { + return nil, nil, err + } + if header == nil { + return nil, nil, errors.New("header for hash not found") + } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { + return nil, nil, errors.New("hash is not currently canonical") + } + stateDb, err := b.eth.BlockChain().StateAt(header.Root) + return stateDb, header, err + } + return nil, nil, errors.New("invalid arguments; neither block nor hash specified") +} + func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { return b.eth.blockchain.GetReceiptsByHash(hash), nil } @@ -137,12 +197,11 @@ func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*typ return logs, nil } -func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { - return b.eth.blockchain.GetTdByHash(blockHash) +func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + return b.eth.blockchain.GetTdByHash(hash) } func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { - state.SetBalance(msg.From(), math.MaxBig256) vmError := func() error { return nil } context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) @@ -153,6 +212,10 @@ func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEven return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch) } +func (b *EthAPIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { + return b.eth.miner.SubscribePendingLogs(ch) +} + func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { return b.eth.BlockChain().SubscribeChainEvent(ch) } @@ -211,6 +274,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, return b.eth.TxPool().Content() } +func (b *EthAPIBackend) TxPool() *core.TxPool { + return b.eth.TxPool() +} + func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { return b.eth.TxPool().SubscribeNewTxsEvent(ch) } @@ -243,10 +310,14 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } -func (b *EthAPIBackend) RPCGasCap() *big.Int { +func (b *EthAPIBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } +func (b *EthAPIBackend) RPCTxFeeCap() float64 { + return b.eth.config.RPCTxFeeCap +} + func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections @@ -257,3 +328,19 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *EthAPIBackend) Engine() consensus.Engine { + return b.eth.engine +} + +func (b *EthAPIBackend) CurrentHeader() *types.Header { + return b.eth.blockchain.CurrentHeader() +} + +func (b *EthAPIBackend) Miner() *miner.Miner { + return b.eth.Miner() +} + +func (b *EthAPIBackend) StartMining(threads int) error { + return b.eth.StartMining(threads) +} diff --git a/eth/api_tracer.go b/eth/api_tracer.go index c8a5307..c044dcc 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -36,11 +36,11 @@ import ( "github.com/ava-labs/coreth/eth/tracers" "github.com/ava-labs/coreth/internal/ethapi" "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/hexutil" - "github.com/ava-labs/go-ethereum/log" - "github.com/ava-labs/go-ethereum/rlp" - "github.com/ava-labs/go-ethereum/trie" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) const ( @@ -151,7 +151,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Ensure we have a valid starting state before doing any work origin := start.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) // Chain tracing will probably start at genesis + database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16, "") // Chain tracing will probably start at genesis if number := start.NumberU64(); number > 0 { start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) @@ -159,7 +159,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl return nil, fmt.Errorf("parent block #%d not found", number-1) } } - statedb, err := state.New(start.Root(), database) + statedb, err := state.New(start.Root(), database, nil) if err != nil { // If the starting state is missing, allow some number of blocks to be reexecuted reexec := defaultTraceReexec @@ -172,7 +172,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl if start == nil { break } - if statedb, err = state.New(start.Root(), database); err == nil { + if statedb, err = state.New(start.Root(), database, nil); err == nil { break } } @@ -407,7 +407,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, return api.TraceBlock(ctx, blob, config) } -// TraceBadBlockByHash returns the structured logs created during the execution of +// TraceBadBlock returns the structured logs created during the execution of // EVM against a block pulled from the pool of bad ones and returns them as a JSON // object. func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { @@ -508,7 +508,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break } @@ -602,7 +602,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) - _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() } @@ -647,14 +647,14 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* } // Otherwise try to reexec blocks until we find a state or reach our limit origin := block.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) + database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16, "") for i := uint64(0); i < reexec; i++ { block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if block == nil { break } - if statedb, err = state.New(block.Root(), database); err == nil { + if statedb, err = state.New(block.Root(), database, nil); err == nil { break } } @@ -717,7 +717,12 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha if config != nil && config.Reexec != nil { reexec = *config.Reexec } - msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec) + // Retrieve the block + block := api.eth.blockchain.GetBlockByHash(blockHash) + if block == nil { + return nil, fmt.Errorf("block %#x not found", blockHash) + } + msg, vmctx, statedb, err := api.computeTxEnv(block, int(index), reexec) if err != nil { return nil, err } @@ -725,6 +730,40 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha return api.traceTx(ctx, msg, vmctx, statedb, config) } +// TraceCall lets you trace a given eth_call. It collects the structured logs created during the execution of EVM +// if the given transaction was added on top of the provided block and returns them as a JSON object. +// You can provide -2 as a block number to trace on top of the pending block. +func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { + // First try to retrieve the state + statedb, header, err := api.eth.APIBackend.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + // Try to retrieve the specified block + var block *types.Block + if hash, ok := blockNrOrHash.Hash(); ok { + block = api.eth.blockchain.GetBlockByHash(hash) + } else if number, ok := blockNrOrHash.Number(); ok { + block = api.eth.blockchain.GetBlockByNumber(uint64(number)) + } + if block == nil { + return nil, fmt.Errorf("block %v not found: %v", blockNrOrHash, err) + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + _, _, statedb, err = api.computeTxEnv(block, 0, reexec) + if err != nil { + return nil, err + } + } + + // Execute the trace + msg := args.ToMessage(api.eth.APIBackend.RPCGasCap()) + vmctx := core.NewEVMContext(msg, header, api.eth.blockchain, nil) + return api.traceTx(ctx, msg, vmctx, statedb, config) +} + // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. @@ -764,17 +803,22 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) - ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) + result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } // Depending on the tracer type, format and return the output switch tracer := tracer.(type) { case *vm.StructLogger: + // If the result contains a revert reason, return it. + returnVal := fmt.Sprintf("%x", result.Return()) + if len(result.Revert()) > 0 { + returnVal = fmt.Sprintf("%x", result.Revert()) + } return ðapi.ExecutionResult{ - Gas: gas, - Failed: failed, - ReturnValue: fmt.Sprintf("%x", ret), + Gas: result.UsedGas, + Failed: result.Failed(), + ReturnValue: returnVal, StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil @@ -787,12 +831,8 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } // computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { +func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { // Create the parent state database - block := api.eth.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash) - } parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) @@ -818,12 +858,12 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash) + return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/eth/backend.go b/eth/backend.go index 056f8cb..9222181 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -41,27 +41,18 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/go-ethereum/accounts/abi/bind" - "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/common/hexutil" - "github.com/ava-labs/go-ethereum/core/bloombits" - "github.com/ava-labs/go-ethereum/eth/downloader" - "github.com/ava-labs/go-ethereum/ethdb" - "github.com/ava-labs/go-ethereum/event" - "github.com/ava-labs/go-ethereum/log" - "github.com/ava-labs/go-ethereum/p2p" - "github.com/ava-labs/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" ) -type LesServer interface { - Start(srvr *p2p.Server) - Stop() - APIs() []rpc.API - Protocols() []p2p.Protocol - SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) - SetContractBackend(bind.ContractBackend) -} - type BackendCallbacks struct { OnQueryAcceptedBlock func() *types.Block } @@ -70,16 +61,11 @@ type BackendCallbacks struct { type Ethereum struct { config *Config - // Channel for shutting down the service - shutdownChan chan bool - - server *p2p.Server - // Handlers txPool *core.TxPool blockchain *core.BlockChain //protocolManager *ProtocolManager - lesServer LesServer + dialCandidates enode.Iterator // DB interfaces chainDb ethdb.Database // Block chain database @@ -88,8 +74,9 @@ type Ethereum struct { engine consensus.Engine accountManager *accounts.Manager - bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests - bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports + bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests + bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports + closeBloomHandler chan struct{} APIBackend *EthAPIBackend @@ -100,28 +87,17 @@ type Ethereum struct { networkID uint64 netRPCService *ethapi.PublicNetAPI + p2pServer *p2p.Server + lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) txSubmitChan chan struct{} bcb *BackendCallbacks } -func (s *Ethereum) AddLesServer(ls LesServer) { - s.lesServer = ls - ls.SetBloomBitsIndexer(s.bloomIndexer) -} - -// SetClient sets a rpc client which connecting to our local node. -func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) { - // Pass the rpc client to les server if it is enabled. - if s.lesServer != nil { - s.lesServer.SetContractBackend(backend) - } -} - // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config, +func New(stack *node.Node, config *Config, cb *dummy.ConsensusCallbacks, mcb *miner.MinerCallbacks, bcb *BackendCallbacks, @@ -138,7 +114,12 @@ func New(ctx *node.ServiceContext, config *Config, config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice) } if config.NoPruning && config.TrieDirtyCache > 0 { - config.TrieCleanCache += config.TrieDirtyCache + if config.SnapshotCache > 0 { + config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 + config.SnapshotCache += config.TrieDirtyCache * 2 / 5 + } else { + config.TrieCleanCache += config.TrieDirtyCache + } config.TrieDirtyCache = 0 } log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) @@ -151,26 +132,27 @@ func New(ctx *node.ServiceContext, config *Config, return nil, err } } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideIstanbul) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } log.Info("Initialised chain configuration", "config", chainConfig) eth := &Ethereum{ - config: config, - chainDb: chainDb, - eventMux: ctx.EventMux, - accountManager: ctx.AccountManager, - engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb, cb), - shutdownChan: make(chan bool), - networkID: config.NetworkId, - gasPrice: config.Miner.GasPrice, - etherbase: config.Miner.Etherbase, - bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), - txSubmitChan: make(chan struct{}, 1), - bcb: bcb, + config: config, + chainDb: chainDb, + eventMux: stack.EventMux(), + accountManager: stack.AccountManager(), + engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb, cb), + closeBloomHandler: make(chan struct{}), + networkID: config.NetworkId, + gasPrice: config.Miner.GasPrice, + etherbase: config.Miner.Etherbase, + bloomRequests: make(chan chan *bloombits.Retrieval), + bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + p2pServer: stack.Server(), + txSubmitChan: make(chan struct{}, 1), + bcb: bcb, } bcVersion := rawdb.ReadDatabaseVersion(chainDb) @@ -196,13 +178,16 @@ func New(ctx *node.ServiceContext, config *Config, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, + TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal), + TrieCleanRejournal: config.TrieCleanCacheRejournal, TrieCleanNoPrefetch: config.NoPrefetch, TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyDisabled: config.NoPruning, TrieTimeLimit: config.TrieTimeout, + SnapshotLimit: config.SnapshotCache, } ) - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, config.ManualCanonical) + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, config.ManualCanonical) if err != nil { return nil, err } @@ -215,12 +200,12 @@ func New(ctx *node.ServiceContext, config *Config, eth.bloomIndexer.Start(eth.blockchain) if config.TxPool.Journal != "" { - config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) + config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) } eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) //// Permit the downloader to use the trie cache allowance during fast sync - //cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + //cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit //checkpoint := config.Checkpoint //if checkpoint == nil { // checkpoint = params.TrustedCheckpoints[genesisHash] @@ -231,13 +216,25 @@ func New(ctx *node.ServiceContext, config *Config, eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, mcb) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) + eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) + if err != nil { + return nil, err + } + + // Start the RPC service + eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, eth.NetVersion()) + + // Register the backend on the node + stack.RegisterAPIs(eth.APIs()) + stack.RegisterProtocols(eth.Protocols()) + stack.RegisterLifecycle(eth) return eth, nil } @@ -259,7 +256,7 @@ func makeExtraData(extra []byte) []byte { } // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, cb *dummy.ConsensusCallbacks) consensus.Engine { +func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, cb *dummy.ConsensusCallbacks) consensus.Engine { return dummy.NewDummyEngine(cb) } @@ -268,18 +265,9 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo func (s *Ethereum) APIs() []rpc.API { apis := ethapi.GetAPIs(s.APIBackend) - // Append any APIs exposed explicitly by the les server - if s.lesServer != nil { - apis = append(apis, s.lesServer.APIs()...) - } // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) - // Append any APIs exposed explicitly by the les server - if s.lesServer != nil { - apis = append(apis, s.lesServer.APIs()...) - } - // Append all the local APIs and return return append(apis, []rpc.API{ { @@ -487,77 +475,68 @@ func (s *Ethereum) NetVersion() uint64 { return s.networkID } func (s *Ethereum) Downloader() *downloader.Downloader { return nil } // s.protocolManager.downloader } func (s *Ethereum) Synced() bool { return true } // atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } +func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } -// Protocols implements node.Service, returning all the currently configured +// Protocols returns all the currently configured // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { protos := make([]p2p.Protocol, len(ProtocolVersions)) //for i, vsn := range ProtocolVersions { // protos[i] = s.protocolManager.makeProtocol(vsn) // protos[i].Attributes = []enr.Entry{s.currentEthEntry()} - //} - //if s.lesServer != nil { - // protos = append(protos, s.lesServer.Protocols()...) + // protos[i].DialCandidates = s.dialCandidates //} return protos } -// Start implements node.Service, starting all internal goroutines needed by the +// Start implements node.Lifecycle, starting all internal goroutines needed by the // Ethereum protocol implementation. -func (s *Ethereum) Start(srvr *p2p.Server) error { - //s.startEthEntryUpdate(srvr.LocalNode()) +func (s *Ethereum) Start() error { + //s.startEthEntryUpdate(s.p2pServer.LocalNode()) // Start the bloom bits servicing goroutines s.startBloomHandlers(params.BloomBitsBlocks) - // Start the RPC service - s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion()) - // Figure out a max peers count based on the server limits - maxPeers := srvr.MaxPeers + maxPeers := s.p2pServer.MaxPeers if s.config.LightServ > 0 { - if s.config.LightPeers >= srvr.MaxPeers { - return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, srvr.MaxPeers) + if s.config.LightPeers >= s.p2pServer.MaxPeers { + return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers) } maxPeers -= s.config.LightPeers } // Start the networking layer and the light server if requested //s.protocolManager.Start(maxPeers) - //if s.lesServer != nil { - // s.lesServer.Start(srvr) - //} return nil } -// Stop implements node.Service, terminating all internal goroutines used by the +// Stop implements node.Lifecycle, terminating all internal goroutines used by the // Ethereum protocol. func (s *Ethereum) Stop() error { - s.bloomIndexer.Close() - s.blockchain.Stop() - s.engine.Close() + // Stop all the peer-related stuff first. //s.protocolManager.Stop() - //if s.lesServer != nil { - // s.lesServer.Stop() - //} + + // Then stop everything else. + s.bloomIndexer.Close() + close(s.closeBloomHandler) s.txPool.Stop() s.miner.Stop() - s.eventMux.Stop() - + s.blockchain.Stop() + s.engine.Close() s.chainDb.Close() - close(s.shutdownChan) + s.eventMux.Stop() return nil } func (s *Ethereum) StopPart() error { s.bloomIndexer.Close() - s.blockchain.Stop() - s.engine.Close() + close(s.closeBloomHandler) s.txPool.Stop() s.miner.Stop() - s.eventMux.Stop() - + s.blockchain.Stop() + s.engine.Close() s.chainDb.Close() - close(s.shutdownChan) + s.eventMux.Stop() return nil } diff --git a/hacked-list.txt b/hacked-list.txt index 92d0517..7fe011b 100644 --- a/hacked-list.txt +++ b/hacked-list.txt @@ -13,7 +13,6 @@ ./core/state_processor.go ./core/state/statedb.go ./core/state/state_object.go - ./core/tx_pool.go ./core/types/block.go ./core/vm/errors.go @@ -28,6 +27,7 @@ ./eth/api.go ./eth/api_tracer.go ./eth/backend.go + ./eth/config.go ./eth/gen_config.go ./eth/tracers/internal/tracers/assets.go -- cgit v1.2.3-70-g09d2 From b1ac5e6ce73c37378e575d6291e3c5f1ee170430 Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 16 Sep 2020 19:00:25 -0400 Subject: build success --- consensus/clique/clique.go | 2 +- consensus/dummy/consensus.go | 31 +- consensus/ethash/consensus.go | 2 +- core/blockchain.go | 2 +- core/forkid/forkid.go | 258 ++++++++ core/rawdb/accessors_state.go | 96 +++ core/rawdb/chain_iterator.go | 304 ++++++++++ core/state/snapshot/difflayer_test.go | 400 ------------- core/state/snapshot/disklayer_test.go | 511 ---------------- core/state/snapshot/iterator_test.go | 1046 --------------------------------- core/state/snapshot/snapshot_test.go | 371 ------------ core/state/snapshot/wipe_test.go | 124 ---- core/vm/instructions.go | 14 +- coreth.go | 19 +- eth/api.go | 2 +- eth/backend.go | 10 +- eth/protocol.go | 1 + internal/ethapi/api.go | 1 - internal/ethapi/backend.go | 1 + miner/miner.go | 3 +- node/api.go | 1 + node/node.go | 34 +- rpc/metrics.go | 39 ++ 23 files changed, 777 insertions(+), 2495 deletions(-) create mode 100644 core/forkid/forkid.go create mode 100644 core/rawdb/accessors_state.go create mode 100644 core/rawdb/chain_iterator.go delete mode 100644 core/state/snapshot/difflayer_test.go delete mode 100644 core/state/snapshot/disklayer_test.go delete mode 100644 core/state/snapshot/iterator_test.go delete mode 100644 core/state/snapshot/snapshot_test.go delete mode 100644 core/state/snapshot/wipe_test.go create mode 100644 rpc/metrics.go (limited to 'core/vm') diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index d239aca..b27525b 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -566,7 +566,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * header.UncleHash = types.CalcUncleHash(nil) // Assemble and return the final block for sealing - return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil + return types.NewBlock(header, txs, nil, receipts, new(trie.Trie), nil), nil } // Authorize injects a private key into the consensus engine to mint new blocks diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 2108684..da63673 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -13,14 +13,15 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" + mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/trie" ) -type OnFinalizeCallbackType = func(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) +type OnFinalizeCallbackType = func(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) type OnFinalizeAndAssembleCallbackType = func(state *state.StateDB, txs []*types.Transaction) ([]byte, error) -type OnAPIsCallbackType = func(consensus.ChainReader) []rpc.API +type OnAPIsCallbackType = func(consensus.ChainHeaderReader) []rpc.API type OnExtraStateChangeType = func(block *types.Block, statedb *state.StateDB) error type ConsensusCallbacks struct { @@ -55,7 +56,7 @@ var ( ) // modified from consensus.go -func (self *DummyEngine) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error { +func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error { // Ensure that the header's extra-data section is of a reasonable size if uint64(len(header.Extra)) > params.MaximumExtraDataSize { return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) @@ -103,7 +104,7 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainReader, header, paren return nil } -func (self *DummyEngine) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error { +func (self *DummyEngine) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int) error { var parent *types.Header if index == 0 { parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) @@ -123,7 +124,7 @@ func (self *DummyEngine) Author(header *types.Header) (common.Address, error) { return header.Coinbase, nil } -func (self *DummyEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { +func (self *DummyEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { // Short circuit if the header is known, or it's parent not number := header.Number.Uint64() if chain.GetHeader(header.Hash(), number) != nil { @@ -137,7 +138,7 @@ func (self *DummyEngine) VerifyHeader(chain consensus.ChainReader, header *types return self.verifyHeader(chain, header, parent, false, seal) } -func (self *DummyEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (self *DummyEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { // Spawn as many workers as allowed threads workers := runtime.GOMAXPROCS(0) if len(headers) < workers { @@ -239,17 +240,17 @@ func (self *DummyEngine) VerifyUncles(chain consensus.ChainReader, block *types. return nil } -func (self *DummyEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error { +func (self *DummyEngine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { return nil } -func (self *DummyEngine) Prepare(chain consensus.ChainReader, header *types.Header) error { +func (self *DummyEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { header.Difficulty = big.NewInt(1) return nil } func (self *DummyEngine) Finalize( - chain consensus.ChainReader, header *types.Header, + chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { if self.cb.OnFinalize != nil { @@ -259,7 +260,7 @@ func (self *DummyEngine) Finalize( header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) } -func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, +func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { var extdata []byte if self.cb.OnFinalizeAndAssemble != nil { @@ -273,10 +274,10 @@ func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainReader, header header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts, extdata), nil + return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie), extdata), nil } -func (self *DummyEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) (err error) { +func (self *DummyEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) (err error) { if self.cb.OnSeal != nil { err = self.cb.OnSeal(block) } else { @@ -314,11 +315,11 @@ func (self *DummyEngine) SealHash(header *types.Header) (hash common.Hash) { return hash } -func (self *DummyEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { +func (self *DummyEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { return big.NewInt(1) } -func (self *DummyEngine) APIs(chain consensus.ChainReader) (res []rpc.API) { +func (self *DummyEngine) APIs(chain consensus.ChainHeaderReader) (res []rpc.API) { res = nil if self.cb.OnAPIs != nil { res = self.cb.OnAPIs(chain) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 151761c..dc56b6f 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -584,7 +584,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil + return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie), nil), nil } // SealHash returns the hash of a block prior to it being sealed. diff --git a/core/blockchain.go b/core/blockchain.go index b861220..82e3b6c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2498,6 +2498,6 @@ func (bc *BlockChain) ManualHead(hash common.Hash) error { } bc.chainmu.Lock() defer bc.chainmu.Unlock() - bc.insert(block) + bc.writeHeadBlock(block) return nil } diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go new file mode 100644 index 0000000..1d6563d --- /dev/null +++ b/core/forkid/forkid.go @@ -0,0 +1,258 @@ +// 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 . + +// Package forkid implements EIP-2124 (https://eips.ethereum.org/EIPS/eip-2124). +package forkid + +import ( + "encoding/binary" + "errors" + "hash/crc32" + "math" + "math/big" + "reflect" + "strings" + + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +var ( + // ErrRemoteStale is returned by the validator if a remote fork checksum is a + // subset of our already applied forks, but the announced next fork block is + // not on our already passed chain. + ErrRemoteStale = errors.New("remote needs update") + + // ErrLocalIncompatibleOrStale is returned by the validator if a remote fork + // checksum does not match any local checksum variation, signalling that the + // two chains have diverged in the past at some point (possibly at genesis). + ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update") +) + +// Blockchain defines all necessary method to build a forkID. +type Blockchain interface { + // Config retrieves the chain's fork configuration. + Config() *params.ChainConfig + + // Genesis retrieves the chain's genesis block. + Genesis() *types.Block + + // CurrentHeader retrieves the current head header of the canonical chain. + CurrentHeader() *types.Header +} + +// ID is a fork identifier as defined by EIP-2124. +type ID struct { + Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers + Next uint64 // Block number of the next upcoming fork, or 0 if no forks are known +} + +// Filter is a fork id filter to validate a remotely advertised ID. +type Filter func(id ID) error + +// NewID calculates the Ethereum fork ID from the chain config and head. +func NewID(chain Blockchain) ID { + return newID( + chain.Config(), + chain.Genesis().Hash(), + chain.CurrentHeader().Number.Uint64(), + ) +} + +// newID is the internal version of NewID, which takes extracted values as its +// arguments instead of a chain. The reason is to allow testing the IDs without +// having to simulate an entire blockchain. +func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { + // Calculate the starting checksum from the genesis hash + hash := crc32.ChecksumIEEE(genesis[:]) + + // Calculate the current fork checksum and the next fork block + var next uint64 + for _, fork := range gatherForks(config) { + if fork <= head { + // Fork already passed, checksum the previous hash and the fork number + hash = checksumUpdate(hash, fork) + continue + } + next = fork + break + } + return ID{Hash: checksumToBytes(hash), Next: next} +} + +// NewFilter creates a filter that returns if a fork ID should be rejected or not +// based on the local chain's status. +func NewFilter(chain Blockchain) Filter { + return newFilter( + chain.Config(), + chain.Genesis().Hash(), + func() uint64 { + return chain.CurrentHeader().Number.Uint64() + }, + ) +} + +// NewStaticFilter creates a filter at block zero. +func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter { + head := func() uint64 { return 0 } + return newFilter(config, genesis, head) +} + +// newFilter is the internal version of NewFilter, taking closures as its arguments +// instead of a chain. The reason is to allow testing it without having to simulate +// an entire blockchain. +func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) Filter { + // Calculate the all the valid fork hash and fork next combos + var ( + forks = gatherForks(config) + sums = make([][4]byte, len(forks)+1) // 0th is the genesis + ) + hash := crc32.ChecksumIEEE(genesis[:]) + sums[0] = checksumToBytes(hash) + for i, fork := range forks { + hash = checksumUpdate(hash, fork) + sums[i+1] = checksumToBytes(hash) + } + // Add two sentries to simplify the fork checks and don't require special + // casing the last one. + forks = append(forks, math.MaxUint64) // Last fork will never be passed + + // Create a validator that will filter out incompatible chains + return func(id ID) error { + // Run the fork checksum validation ruleset: + // 1. If local and remote FORK_CSUM matches, compare local head to FORK_NEXT. + // The two nodes are in the same fork state currently. They might know + // of differing future forks, but that's not relevant until the fork + // triggers (might be postponed, nodes might be updated to match). + // 1a. A remotely announced but remotely not passed block is already passed + // locally, disconnect, since the chains are incompatible. + // 1b. No remotely announced fork; or not yet passed locally, connect. + // 2. If the remote FORK_CSUM is a subset of the local past forks and the + // remote FORK_NEXT matches with the locally following fork block number, + // connect. + // Remote node is currently syncing. It might eventually diverge from + // us, but at this current point in time we don't have enough information. + // 3. If the remote FORK_CSUM is a superset of the local past forks and can + // be completed with locally known future forks, connect. + // Local node is currently syncing. It might eventually diverge from + // the remote, but at this current point in time we don't have enough + // information. + // 4. Reject in all other cases. + head := headfn() + for i, fork := range forks { + // If our head is beyond this fork, continue to the next (we have a dummy + // fork of maxuint64 as the last item to always fail this check eventually). + if head > fork { + continue + } + // Found the first unpassed fork block, check if our current state matches + // the remote checksum (rule #1). + if sums[i] == id.Hash { + // Fork checksum matched, check if a remote future fork block already passed + // locally without the local node being aware of it (rule #1a). + if id.Next > 0 && head >= id.Next { + return ErrLocalIncompatibleOrStale + } + // Haven't passed locally a remote-only fork, accept the connection (rule #1b). + return nil + } + // The local and remote nodes are in different forks currently, check if the + // remote checksum is a subset of our local forks (rule #2). + for j := 0; j < i; j++ { + if sums[j] == id.Hash { + // Remote checksum is a subset, validate based on the announced next fork + if forks[j] != id.Next { + return ErrRemoteStale + } + return nil + } + } + // Remote chain is not a subset of our local one, check if it's a superset by + // any chance, signalling that we're simply out of sync (rule #3). + for j := i + 1; j < len(sums); j++ { + if sums[j] == id.Hash { + // Yay, remote checksum is a superset, ignore upcoming forks + return nil + } + } + // No exact, subset or superset match. We are on differing chains, reject. + return ErrLocalIncompatibleOrStale + } + log.Error("Impossible fork ID validation", "id", id) + return nil // Something's very wrong, accept rather than reject + } +} + +// checksumUpdate calculates the next IEEE CRC32 checksum based on the previous +// one and a fork block number (equivalent to CRC32(original-blob || fork)). +func checksumUpdate(hash uint32, fork uint64) uint32 { + var blob [8]byte + binary.BigEndian.PutUint64(blob[:], fork) + return crc32.Update(hash, crc32.IEEETable, blob[:]) +} + +// checksumToBytes converts a uint32 checksum into a [4]byte array. +func checksumToBytes(hash uint32) [4]byte { + var blob [4]byte + binary.BigEndian.PutUint32(blob[:], hash) + return blob +} + +// gatherForks gathers all the known forks and creates a sorted list out of them. +func gatherForks(config *params.ChainConfig) []uint64 { + // Gather all the fork block numbers via reflection + kind := reflect.TypeOf(params.ChainConfig{}) + conf := reflect.ValueOf(config).Elem() + + var forks []uint64 + for i := 0; i < kind.NumField(); i++ { + // Fetch the next field and skip non-fork rules + field := kind.Field(i) + if !strings.HasSuffix(field.Name, "Block") { + continue + } + if field.Type != reflect.TypeOf(new(big.Int)) { + continue + } + // Extract the fork rule block number and aggregate it + rule := conf.Field(i).Interface().(*big.Int) + if rule != nil { + forks = append(forks, rule.Uint64()) + } + } + // Sort the fork block numbers to permit chronological XOR + for i := 0; i < len(forks); i++ { + for j := i + 1; j < len(forks); j++ { + if forks[i] > forks[j] { + forks[i], forks[j] = forks[j], forks[i] + } + } + } + // Deduplicate block numbers applying multiple forks + for i := 1; i < len(forks); i++ { + if forks[i] == forks[i-1] { + forks = append(forks[:i], forks[i+1:]...) + i-- + } + } + // Skip any forks in block 0, that's the genesis ruleset + if len(forks) > 0 && forks[0] == 0 { + forks = forks[1:] + } + return forks +} diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go new file mode 100644 index 0000000..6112de0 --- /dev/null +++ b/core/rawdb/accessors_state.go @@ -0,0 +1,96 @@ +// 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 . + +package rawdb + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// ReadPreimage retrieves a single preimage of the provided hash. +func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(preimageKey(hash)) + return data +} + +// WritePreimages writes the provided set of preimages to the database. +func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) { + for hash, preimage := range preimages { + if err := db.Put(preimageKey(hash), preimage); err != nil { + log.Crit("Failed to store trie preimage", "err", err) + } + } + preimageCounter.Inc(int64(len(preimages))) + preimageHitCounter.Inc(int64(len(preimages))) +} + +// ReadCode retrieves the contract code of the provided code hash. +func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte { + // Try with the legacy code scheme first, if not then try with current + // scheme. Since most of the code will be found with legacy scheme. + // + // todo(rjl493456442) change the order when we forcibly upgrade the code + // scheme with snapshot. + data, _ := db.Get(hash[:]) + if len(data) != 0 { + return data + } + return ReadCodeWithPrefix(db, hash) +} + +// ReadCodeWithPrefix retrieves the contract code of the provided code hash. +// The main difference between this function and ReadCode is this function +// will only check the existence with latest scheme(with prefix). +func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(codeKey(hash)) + return data +} + +// WriteCode writes the provided contract code database. +func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) { + if err := db.Put(codeKey(hash), code); err != nil { + log.Crit("Failed to store contract code", "err", err) + } +} + +// DeleteCode deletes the specified contract code from the database. +func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(codeKey(hash)); err != nil { + log.Crit("Failed to delete contract code", "err", err) + } +} + +// ReadTrieNode retrieves the trie node of the provided hash. +func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(hash.Bytes()) + return data +} + +// WriteTrieNode writes the provided trie node database. +func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) { + if err := db.Put(hash.Bytes(), node); err != nil { + log.Crit("Failed to store trie node", "err", err) + } +} + +// DeleteTrieNode deletes the specified trie node from the database. +func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(hash.Bytes()); err != nil { + log.Crit("Failed to delete trie node", "err", err) + } +} diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go new file mode 100644 index 0000000..3130e92 --- /dev/null +++ b/core/rawdb/chain_iterator.go @@ -0,0 +1,304 @@ +// 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 . + +package rawdb + +import ( + "runtime" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// InitDatabaseFromFreezer reinitializes an empty database from a previous batch +// of frozen ancient blocks. The method iterates over all the frozen blocks and +// injects into the database the block hash->number mappings. +func InitDatabaseFromFreezer(db ethdb.Database) { + // If we can't access the freezer or it's empty, abort + frozen, err := db.Ancients() + if err != nil || frozen == 0 { + return + } + var ( + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log + hash common.Hash + ) + for i := uint64(0); i < frozen; i++ { + // Since the freezer has all data in sequential order on a file, + // it would be 'neat' to read more data in one go, and let the + // freezerdb return N items (e.g up to 1000 items per go) + // That would require an API change in Ancients though + if h, err := db.Ancient(freezerHashTable, i); err != nil { + log.Crit("Failed to init database from freezer", "err", err) + } else { + hash = common.BytesToHash(h) + } + WriteHeaderNumber(batch, hash, i) + // If enough data was accumulated in memory or we're at the last block, dump to disk + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write data to db", "err", err) + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Initializing database from freezer", "total", frozen, "number", i, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed to write data to db", "err", err) + } + batch.Reset() + + WriteHeadHeaderHash(db, hash) + WriteHeadFastBlockHash(db, hash) + log.Info("Initialized database from freezer", "blocks", frozen, "elapsed", common.PrettyDuration(time.Since(start))) +} + +type blockTxHashes struct { + number uint64 + hashes []common.Hash +} + +// iterateTransactions iterates over all transactions in the (canon) block +// number(s) given, and yields the hashes on a channel +func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool) (chan *blockTxHashes, chan struct{}) { + // One thread sequentially reads data from db + type numberRlp struct { + number uint64 + rlp rlp.RawValue + } + if to == from { + return nil, nil + } + threads := to - from + if cpus := runtime.NumCPU(); threads > uint64(cpus) { + threads = uint64(cpus) + } + var ( + rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel + hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh + abortCh = make(chan struct{}) + ) + // lookup runs in one instance + lookup := func() { + n, end := from, to + if reverse { + n, end = to-1, from-1 + } + defer close(rlpCh) + for n != end { + data := ReadCanonicalBodyRLP(db, n) + // Feed the block to the aggregator, or abort on interrupt + select { + case rlpCh <- &numberRlp{n, data}: + case <-abortCh: + return + } + if reverse { + n-- + } else { + n++ + } + } + } + // process runs in parallel + nThreadsAlive := int32(threads) + process := func() { + defer func() { + // Last processor closes the result channel + if atomic.AddInt32(&nThreadsAlive, -1) == 0 { + close(hashesCh) + } + }() + + var hasher = sha3.NewLegacyKeccak256() + for data := range rlpCh { + it, err := rlp.NewListIterator(data.rlp) + if err != nil { + log.Warn("tx iteration error", "error", err) + return + } + it.Next() + txs := it.Value() + txIt, err := rlp.NewListIterator(txs) + if err != nil { + log.Warn("tx iteration error", "error", err) + return + } + var hashes []common.Hash + for txIt.Next() { + if err := txIt.Err(); err != nil { + log.Warn("tx iteration error", "error", err) + return + } + var txHash common.Hash + hasher.Reset() + hasher.Write(txIt.Value()) + hasher.Sum(txHash[:0]) + hashes = append(hashes, txHash) + } + result := &blockTxHashes{ + hashes: hashes, + number: data.number, + } + // Feed the block to the aggregator, or abort on interrupt + select { + case hashesCh <- result: + case <-abortCh: + return + } + } + } + go lookup() // start the sequential db accessor + for i := 0; i < int(threads); i++ { + go process() + } + return hashesCh, abortCh +} + +// IndexTransactions creates txlookup indices of the specified block range. +// +// This function iterates canonical chain in reverse order, it has one main advantage: +// We can write tx index tail flag periodically even without the whole indexing +// procedure is finished. So that we can resume indexing procedure next time quickly. +func IndexTransactions(db ethdb.Database, from uint64, to uint64) { + // short circuit for invalid range + if from >= to { + return + } + var ( + hashesCh, abortCh = iterateTransactions(db, from, to, true) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + // Since we iterate in reverse, we expect the first number to come + // in to be [to-1]. Therefore, setting lastNum to means that the + // prqueue gap-evaluation will work correctly + lastNum = to + queue = prque.New(nil) + // for stats reporting + blocks, txs = 0, 0 + ) + defer close(abortCh) + + for chanDelivery := range hashesCh { + // Push the delivery into the queue and process contiguous ranges. + // Since we iterate in reverse, so lower numbers have lower prio, and + // we can use the number directly as prio marker + queue.Push(chanDelivery, int64(chanDelivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); priority != int64(lastNum-1) { + break + } + // Next block available, pop it off and index it + delivery := queue.PopItem().(*blockTxHashes) + lastNum = delivery.number + WriteTxLookupEntries(batch, delivery.number, delivery.hashes) + blocks++ + txs += len(delivery.hashes) + // If enough data was accumulated in memory or we're at the last block, dump to disk + if batch.ValueSize() > ethdb.IdealBatchSize { + // Also write the tail there + WriteTxIndexTail(batch, lastNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Indexing transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + if lastNum < to { + WriteTxIndexTail(batch, lastNum) + // No need to write the batch if we never entered the loop above... + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + } + log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) +} + +// UnindexTransactions removes txlookup indices of the specified block range. +func UnindexTransactions(db ethdb.Database, from uint64, to uint64) { + // short circuit for invalid range + if from >= to { + return + } + // Write flag first and then unindex the transaction indices. Some indices + // will be left in the database if crash happens but it's fine. + WriteTxIndexTail(db, to) + // If only one block is unindexed, do it directly + //if from+1 == to { + // data := ReadCanonicalBodyRLP(db, uint64(from)) + // DeleteTxLookupEntries(db, ReadBlock(db, ReadCanonicalHash(db, from), from)) + // log.Info("Unindexed transactions", "blocks", 1, "tail", to) + // return + //} + // TODO @holiman, add this back (if we want it) + var ( + hashesCh, abortCh = iterateTransactions(db, from, to, false) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + ) + defer close(abortCh) + // Otherwise spin up the concurrent iterator and unindexer + blocks, txs := 0, 0 + for delivery := range hashesCh { + DeleteTxLookupEntries(batch, delivery.hashes) + txs += len(delivery.hashes) + blocks++ + + // If enough data was accumulated in memory or we're at the last block, dump to disk + // A batch counts the size of deletion as '1', so we need to flush more + // often than that. + if blocks%1000 == 0 { + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) +} diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go deleted file mode 100644 index 31636ee..0000000 --- a/core/state/snapshot/difflayer_test.go +++ /dev/null @@ -1,400 +0,0 @@ -// 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 . - -package snapshot - -import ( - "bytes" - "math/rand" - "testing" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb/memorydb" -) - -func copyDestructs(destructs map[common.Hash]struct{}) map[common.Hash]struct{} { - copy := make(map[common.Hash]struct{}) - for hash := range destructs { - copy[hash] = struct{}{} - } - return copy -} - -func copyAccounts(accounts map[common.Hash][]byte) map[common.Hash][]byte { - copy := make(map[common.Hash][]byte) - for hash, blob := range accounts { - copy[hash] = blob - } - return copy -} - -func copyStorage(storage map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { - copy := make(map[common.Hash]map[common.Hash][]byte) - for accHash, slots := range storage { - copy[accHash] = make(map[common.Hash][]byte) - for slotHash, blob := range slots { - copy[accHash][slotHash] = blob - } - } - return copy -} - -// TestMergeBasics tests some simple merges -func TestMergeBasics(t *testing.T) { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - // Fill up a parent - for i := 0; i < 100; i++ { - h := randomHash() - data := randomAccount() - - accounts[h] = data - if rand.Intn(4) == 0 { - destructs[h] = struct{}{} - } - if rand.Intn(2) == 0 { - accStorage := make(map[common.Hash][]byte) - value := make([]byte, 32) - rand.Read(value) - accStorage[randomHash()] = value - storage[h] = accStorage - } - } - // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) - child := newDiffLayer(parent, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) - child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) - child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) - child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) - // And flatten - merged := (child.flatten()).(*diffLayer) - - { // Check account lists - if have, want := len(merged.accountList), 0; have != want { - t.Errorf("accountList wrong: have %v, want %v", have, want) - } - if have, want := len(merged.AccountList()), len(accounts); have != want { - t.Errorf("AccountList() wrong: have %v, want %v", have, want) - } - if have, want := len(merged.accountList), len(accounts); have != want { - t.Errorf("accountList [2] wrong: have %v, want %v", have, want) - } - } - { // Check account drops - if have, want := len(merged.destructSet), len(destructs); have != want { - t.Errorf("accountDrop wrong: have %v, want %v", have, want) - } - } - { // Check storage lists - i := 0 - for aHash, sMap := range storage { - if have, want := len(merged.storageList), i; have != want { - t.Errorf("[1] storageList wrong: have %v, want %v", have, want) - } - list, _ := merged.StorageList(aHash) - if have, want := len(list), len(sMap); have != want { - t.Errorf("[2] StorageList() wrong: have %v, want %v", have, want) - } - if have, want := len(merged.storageList[aHash]), len(sMap); have != want { - t.Errorf("storageList wrong: have %v, want %v", have, want) - } - i++ - } - } -} - -// TestMergeDelete tests some deletion -func TestMergeDelete(t *testing.T) { - var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - // Fill up a parent - h1 := common.HexToHash("0x01") - h2 := common.HexToHash("0x02") - - flipDrops := func() map[common.Hash]struct{} { - return map[common.Hash]struct{}{ - h2: {}, - } - } - flipAccs := func() map[common.Hash][]byte { - return map[common.Hash][]byte{ - h1: randomAccount(), - } - } - flopDrops := func() map[common.Hash]struct{} { - return map[common.Hash]struct{}{ - h1: {}, - } - } - flopAccs := func() map[common.Hash][]byte { - return map[common.Hash][]byte{ - h2: randomAccount(), - } - } - // Add some flipAccs-flopping layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, flipDrops(), flipAccs(), storage) - child := parent.Update(common.Hash{}, flopDrops(), flopAccs(), storage) - child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) - child = child.Update(common.Hash{}, flopDrops(), flopAccs(), storage) - child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) - child = child.Update(common.Hash{}, flopDrops(), flopAccs(), storage) - child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) - - if data, _ := child.Account(h1); data == nil { - t.Errorf("last diff layer: expected %x account to be non-nil", h1) - } - if data, _ := child.Account(h2); data != nil { - t.Errorf("last diff layer: expected %x account to be nil", h2) - } - if _, ok := child.destructSet[h1]; ok { - t.Errorf("last diff layer: expected %x drop to be missing", h1) - } - if _, ok := child.destructSet[h2]; !ok { - t.Errorf("last diff layer: expected %x drop to be present", h1) - } - // And flatten - merged := (child.flatten()).(*diffLayer) - - if data, _ := merged.Account(h1); data == nil { - t.Errorf("merged layer: expected %x account to be non-nil", h1) - } - if data, _ := merged.Account(h2); data != nil { - t.Errorf("merged layer: expected %x account to be nil", h2) - } - if _, ok := merged.destructSet[h1]; !ok { // Note, drops stay alive until persisted to disk! - t.Errorf("merged diff layer: expected %x drop to be present", h1) - } - if _, ok := merged.destructSet[h2]; !ok { // Note, drops stay alive until persisted to disk! - t.Errorf("merged diff layer: expected %x drop to be present", h1) - } - // If we add more granular metering of memory, we can enable this again, - // but it's not implemented for now - //if have, want := merged.memory, child.memory; have != want { - // t.Errorf("mem wrong: have %d, want %d", have, want) - //} -} - -// This tests that if we create a new account, and set a slot, and then merge -// it, the lists will be correct. -func TestInsertAndMerge(t *testing.T) { - // Fill up a parent - var ( - acc = common.HexToHash("0x01") - slot = common.HexToHash("0x02") - parent *diffLayer - child *diffLayer - ) - { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - parent = newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) - } - { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - accounts[acc] = randomAccount() - storage[acc] = make(map[common.Hash][]byte) - storage[acc][slot] = []byte{0x01} - child = newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) - } - // And flatten - merged := (child.flatten()).(*diffLayer) - { // Check that slot value is present - have, _ := merged.Storage(acc, slot) - if want := []byte{0x01}; !bytes.Equal(have, want) { - t.Errorf("merged slot value wrong: have %x, want %x", have, want) - } - } -} - -func emptyLayer() *diskLayer { - return &diskLayer{ - diskdb: memorydb.New(), - cache: fastcache.New(500 * 1024), - } -} - -// BenchmarkSearch checks how long it takes to find a non-existing key -// BenchmarkSearch-6 200000 10481 ns/op (1K per layer) -// BenchmarkSearch-6 200000 10760 ns/op (10K per layer) -// BenchmarkSearch-6 100000 17866 ns/op -// -// BenchmarkSearch-6 500000 3723 ns/op (10k per layer, only top-level RLock() -func BenchmarkSearch(b *testing.B) { - // First, we set up 128 diff layers, with 1K items each - fill := func(parent snapshot) *diffLayer { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - for i := 0; i < 10000; i++ { - accounts[randomHash()] = randomAccount() - } - return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) - } - var layer snapshot - layer = emptyLayer() - for i := 0; i < 128; i++ { - layer = fill(layer) - } - key := crypto.Keccak256Hash([]byte{0x13, 0x38}) - b.ResetTimer() - for i := 0; i < b.N; i++ { - layer.AccountRLP(key) - } -} - -// BenchmarkSearchSlot checks how long it takes to find a non-existing key -// - Number of layers: 128 -// - Each layers contains the account, with a couple of storage slots -// BenchmarkSearchSlot-6 100000 14554 ns/op -// BenchmarkSearchSlot-6 100000 22254 ns/op (when checking parent root using mutex) -// BenchmarkSearchSlot-6 100000 14551 ns/op (when checking parent number using atomic) -// With bloom filter: -// BenchmarkSearchSlot-6 3467835 351 ns/op -func BenchmarkSearchSlot(b *testing.B) { - // First, we set up 128 diff layers, with 1K items each - accountKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) - storageKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) - accountRLP := randomAccount() - fill := func(parent snapshot) *diffLayer { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - accounts[accountKey] = accountRLP - - accStorage := make(map[common.Hash][]byte) - for i := 0; i < 5; i++ { - value := make([]byte, 32) - rand.Read(value) - accStorage[randomHash()] = value - storage[accountKey] = accStorage - } - return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) - } - var layer snapshot - layer = emptyLayer() - for i := 0; i < 128; i++ { - layer = fill(layer) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - layer.Storage(accountKey, storageKey) - } -} - -// With accountList and sorting -// BenchmarkFlatten-6 50 29890856 ns/op -// -// Without sorting and tracking accountlist -// BenchmarkFlatten-6 300 5511511 ns/op -func BenchmarkFlatten(b *testing.B) { - fill := func(parent snapshot) *diffLayer { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - for i := 0; i < 100; i++ { - accountKey := randomHash() - accounts[accountKey] = randomAccount() - - accStorage := make(map[common.Hash][]byte) - for i := 0; i < 20; i++ { - value := make([]byte, 32) - rand.Read(value) - accStorage[randomHash()] = value - - } - storage[accountKey] = accStorage - } - return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - b.StopTimer() - var layer snapshot - layer = emptyLayer() - for i := 1; i < 128; i++ { - layer = fill(layer) - } - b.StartTimer() - - for i := 1; i < 128; i++ { - dl, ok := layer.(*diffLayer) - if !ok { - break - } - layer = dl.flatten() - } - b.StopTimer() - } -} - -// This test writes ~324M of diff layers to disk, spread over -// - 128 individual layers, -// - each with 200 accounts -// - containing 200 slots -// -// BenchmarkJournal-6 1 1471373923 ns/ops -// BenchmarkJournal-6 1 1208083335 ns/op // bufio writer -func BenchmarkJournal(b *testing.B) { - fill := func(parent snapshot) *diffLayer { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - for i := 0; i < 200; i++ { - accountKey := randomHash() - accounts[accountKey] = randomAccount() - - accStorage := make(map[common.Hash][]byte) - for i := 0; i < 200; i++ { - value := make([]byte, 32) - rand.Read(value) - accStorage[randomHash()] = value - - } - storage[accountKey] = accStorage - } - return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) - } - layer := snapshot(new(diskLayer)) - for i := 1; i < 128; i++ { - layer = fill(layer) - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - layer.Journal(new(bytes.Buffer)) - } -} diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go deleted file mode 100644 index 5df5efc..0000000 --- a/core/state/snapshot/disklayer_test.go +++ /dev/null @@ -1,511 +0,0 @@ -// 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 . - -package snapshot - -import ( - "bytes" - "io/ioutil" - "os" - "testing" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/leveldb" - "github.com/ethereum/go-ethereum/ethdb/memorydb" -) - -// reverse reverses the contents of a byte slice. It's used to update random accs -// with deterministic changes. -func reverse(blob []byte) []byte { - res := make([]byte, len(blob)) - for i, b := range blob { - res[len(blob)-1-i] = b - } - return res -} - -// Tests that merging something into a disk layer persists it into the database -// and invalidates any previously written and cached values. -func TestDiskMerge(t *testing.T) { - // Create some accounts in the disk layer - db := memorydb.New() - - var ( - accNoModNoCache = common.Hash{0x1} - accNoModCache = common.Hash{0x2} - accModNoCache = common.Hash{0x3} - accModCache = common.Hash{0x4} - accDelNoCache = common.Hash{0x5} - accDelCache = common.Hash{0x6} - conNoModNoCache = common.Hash{0x7} - conNoModNoCacheSlot = common.Hash{0x70} - conNoModCache = common.Hash{0x8} - conNoModCacheSlot = common.Hash{0x80} - conModNoCache = common.Hash{0x9} - conModNoCacheSlot = common.Hash{0x90} - conModCache = common.Hash{0xa} - conModCacheSlot = common.Hash{0xa0} - conDelNoCache = common.Hash{0xb} - conDelNoCacheSlot = common.Hash{0xb0} - conDelCache = common.Hash{0xc} - conDelCacheSlot = common.Hash{0xc0} - conNukeNoCache = common.Hash{0xd} - conNukeNoCacheSlot = common.Hash{0xd0} - conNukeCache = common.Hash{0xe} - conNukeCacheSlot = common.Hash{0xe0} - baseRoot = randomHash() - diffRoot = randomHash() - ) - - rawdb.WriteAccountSnapshot(db, accNoModNoCache, accNoModNoCache[:]) - rawdb.WriteAccountSnapshot(db, accNoModCache, accNoModCache[:]) - rawdb.WriteAccountSnapshot(db, accModNoCache, accModNoCache[:]) - rawdb.WriteAccountSnapshot(db, accModCache, accModCache[:]) - rawdb.WriteAccountSnapshot(db, accDelNoCache, accDelNoCache[:]) - rawdb.WriteAccountSnapshot(db, accDelCache, accDelCache[:]) - - rawdb.WriteAccountSnapshot(db, conNoModNoCache, conNoModNoCache[:]) - rawdb.WriteStorageSnapshot(db, conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) - rawdb.WriteAccountSnapshot(db, conNoModCache, conNoModCache[:]) - rawdb.WriteStorageSnapshot(db, conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - rawdb.WriteAccountSnapshot(db, conModNoCache, conModNoCache[:]) - rawdb.WriteStorageSnapshot(db, conModNoCache, conModNoCacheSlot, conModNoCacheSlot[:]) - rawdb.WriteAccountSnapshot(db, conModCache, conModCache[:]) - rawdb.WriteStorageSnapshot(db, conModCache, conModCacheSlot, conModCacheSlot[:]) - rawdb.WriteAccountSnapshot(db, conDelNoCache, conDelNoCache[:]) - rawdb.WriteStorageSnapshot(db, conDelNoCache, conDelNoCacheSlot, conDelNoCacheSlot[:]) - rawdb.WriteAccountSnapshot(db, conDelCache, conDelCache[:]) - rawdb.WriteStorageSnapshot(db, conDelCache, conDelCacheSlot, conDelCacheSlot[:]) - - rawdb.WriteAccountSnapshot(db, conNukeNoCache, conNukeNoCache[:]) - rawdb.WriteStorageSnapshot(db, conNukeNoCache, conNukeNoCacheSlot, conNukeNoCacheSlot[:]) - rawdb.WriteAccountSnapshot(db, conNukeCache, conNukeCache[:]) - rawdb.WriteStorageSnapshot(db, conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) - - rawdb.WriteSnapshotRoot(db, baseRoot) - - // Create a disk layer based on the above and cache in some data - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - baseRoot: &diskLayer{ - diskdb: db, - cache: fastcache.New(500 * 1024), - root: baseRoot, - }, - }, - } - base := snaps.Snapshot(baseRoot) - base.AccountRLP(accNoModCache) - base.AccountRLP(accModCache) - base.AccountRLP(accDelCache) - base.Storage(conNoModCache, conNoModCacheSlot) - base.Storage(conModCache, conModCacheSlot) - base.Storage(conDelCache, conDelCacheSlot) - base.Storage(conNukeCache, conNukeCacheSlot) - - // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ - accDelNoCache: {}, - accDelCache: {}, - conNukeNoCache: {}, - conNukeCache: {}, - }, map[common.Hash][]byte{ - accModNoCache: reverse(accModNoCache[:]), - accModCache: reverse(accModCache[:]), - }, map[common.Hash]map[common.Hash][]byte{ - conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, - conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, - conDelNoCache: {conDelNoCacheSlot: nil}, - conDelCache: {conDelCacheSlot: nil}, - }); err != nil { - t.Fatalf("failed to update snapshot tree: %v", err) - } - if err := snaps.Cap(diffRoot, 0); err != nil { - t.Fatalf("failed to flatten snapshot tree: %v", err) - } - // Retrieve all the data through the disk layer and validate it - base = snaps.Snapshot(diffRoot) - if _, ok := base.(*diskLayer); !ok { - t.Fatalf("update not flattend into the disk layer") - } - - // assertAccount ensures that an account matches the given blob. - assertAccount := func(account common.Hash, data []byte) { - t.Helper() - blob, err := base.AccountRLP(account) - if err != nil { - t.Errorf("account access (%x) failed: %v", account, err) - } else if !bytes.Equal(blob, data) { - t.Errorf("account access (%x) mismatch: have %x, want %x", account, blob, data) - } - } - assertAccount(accNoModNoCache, accNoModNoCache[:]) - assertAccount(accNoModCache, accNoModCache[:]) - assertAccount(accModNoCache, reverse(accModNoCache[:])) - assertAccount(accModCache, reverse(accModCache[:])) - assertAccount(accDelNoCache, nil) - assertAccount(accDelCache, nil) - - // assertStorage ensures that a storage slot matches the given blob. - assertStorage := func(account common.Hash, slot common.Hash, data []byte) { - t.Helper() - blob, err := base.Storage(account, slot) - if err != nil { - t.Errorf("storage access (%x:%x) failed: %v", account, slot, err) - } else if !bytes.Equal(blob, data) { - t.Errorf("storage access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) - } - } - assertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) - assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - assertStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) - assertStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) - assertStorage(conDelNoCache, conDelNoCacheSlot, nil) - assertStorage(conDelCache, conDelCacheSlot, nil) - assertStorage(conNukeNoCache, conNukeNoCacheSlot, nil) - assertStorage(conNukeCache, conNukeCacheSlot, nil) - - // Retrieve all the data directly from the database and validate it - - // assertDatabaseAccount ensures that an account from the database matches the given blob. - assertDatabaseAccount := func(account common.Hash, data []byte) { - t.Helper() - if blob := rawdb.ReadAccountSnapshot(db, account); !bytes.Equal(blob, data) { - t.Errorf("account database access (%x) mismatch: have %x, want %x", account, blob, data) - } - } - assertDatabaseAccount(accNoModNoCache, accNoModNoCache[:]) - assertDatabaseAccount(accNoModCache, accNoModCache[:]) - assertDatabaseAccount(accModNoCache, reverse(accModNoCache[:])) - assertDatabaseAccount(accModCache, reverse(accModCache[:])) - assertDatabaseAccount(accDelNoCache, nil) - assertDatabaseAccount(accDelCache, nil) - - // assertDatabaseStorage ensures that a storage slot from the database matches the given blob. - assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { - t.Helper() - if blob := rawdb.ReadStorageSnapshot(db, account, slot); !bytes.Equal(blob, data) { - t.Errorf("storage database access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) - } - } - assertDatabaseStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) - assertDatabaseStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - assertDatabaseStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) - assertDatabaseStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) - assertDatabaseStorage(conDelNoCache, conDelNoCacheSlot, nil) - assertDatabaseStorage(conDelCache, conDelCacheSlot, nil) - assertDatabaseStorage(conNukeNoCache, conNukeNoCacheSlot, nil) - assertDatabaseStorage(conNukeCache, conNukeCacheSlot, nil) -} - -// Tests that merging something into a disk layer persists it into the database -// and invalidates any previously written and cached values, discarding anything -// after the in-progress generation marker. -func TestDiskPartialMerge(t *testing.T) { - // Iterate the test a few times to ensure we pick various internal orderings - // for the data slots as well as the progress marker. - for i := 0; i < 1024; i++ { - // Create some accounts in the disk layer - db := memorydb.New() - - var ( - accNoModNoCache = randomHash() - accNoModCache = randomHash() - accModNoCache = randomHash() - accModCache = randomHash() - accDelNoCache = randomHash() - accDelCache = randomHash() - conNoModNoCache = randomHash() - conNoModNoCacheSlot = randomHash() - conNoModCache = randomHash() - conNoModCacheSlot = randomHash() - conModNoCache = randomHash() - conModNoCacheSlot = randomHash() - conModCache = randomHash() - conModCacheSlot = randomHash() - conDelNoCache = randomHash() - conDelNoCacheSlot = randomHash() - conDelCache = randomHash() - conDelCacheSlot = randomHash() - conNukeNoCache = randomHash() - conNukeNoCacheSlot = randomHash() - conNukeCache = randomHash() - conNukeCacheSlot = randomHash() - baseRoot = randomHash() - diffRoot = randomHash() - genMarker = append(randomHash().Bytes(), randomHash().Bytes()...) - ) - - // insertAccount injects an account into the database if it's after the - // generator marker, drops the op otherwise. This is needed to seed the - // database with a valid starting snapshot. - insertAccount := func(account common.Hash, data []byte) { - if bytes.Compare(account[:], genMarker) <= 0 { - rawdb.WriteAccountSnapshot(db, account, data[:]) - } - } - insertAccount(accNoModNoCache, accNoModNoCache[:]) - insertAccount(accNoModCache, accNoModCache[:]) - insertAccount(accModNoCache, accModNoCache[:]) - insertAccount(accModCache, accModCache[:]) - insertAccount(accDelNoCache, accDelNoCache[:]) - insertAccount(accDelCache, accDelCache[:]) - - // insertStorage injects a storage slot into the database if it's after - // the generator marker, drops the op otherwise. This is needed to seed - // the database with a valid starting snapshot. - insertStorage := func(account common.Hash, slot common.Hash, data []byte) { - if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 { - rawdb.WriteStorageSnapshot(db, account, slot, data[:]) - } - } - insertAccount(conNoModNoCache, conNoModNoCache[:]) - insertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) - insertAccount(conNoModCache, conNoModCache[:]) - insertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - insertAccount(conModNoCache, conModNoCache[:]) - insertStorage(conModNoCache, conModNoCacheSlot, conModNoCacheSlot[:]) - insertAccount(conModCache, conModCache[:]) - insertStorage(conModCache, conModCacheSlot, conModCacheSlot[:]) - insertAccount(conDelNoCache, conDelNoCache[:]) - insertStorage(conDelNoCache, conDelNoCacheSlot, conDelNoCacheSlot[:]) - insertAccount(conDelCache, conDelCache[:]) - insertStorage(conDelCache, conDelCacheSlot, conDelCacheSlot[:]) - - insertAccount(conNukeNoCache, conNukeNoCache[:]) - insertStorage(conNukeNoCache, conNukeNoCacheSlot, conNukeNoCacheSlot[:]) - insertAccount(conNukeCache, conNukeCache[:]) - insertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) - - rawdb.WriteSnapshotRoot(db, baseRoot) - - // Create a disk layer based on the above using a random progress marker - // and cache in some data. - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - baseRoot: &diskLayer{ - diskdb: db, - cache: fastcache.New(500 * 1024), - root: baseRoot, - }, - }, - } - snaps.layers[baseRoot].(*diskLayer).genMarker = genMarker - base := snaps.Snapshot(baseRoot) - - // assertAccount ensures that an account matches the given blob if it's - // already covered by the disk snapshot, and errors out otherwise. - assertAccount := func(account common.Hash, data []byte) { - t.Helper() - blob, err := base.AccountRLP(account) - if bytes.Compare(account[:], genMarker) > 0 && err != ErrNotCoveredYet { - t.Fatalf("test %d: post-marker (%x) account access (%x) succeeded: %x", i, genMarker, account, blob) - } - if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { - t.Fatalf("test %d: pre-marker (%x) account access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) - } - } - assertAccount(accNoModCache, accNoModCache[:]) - assertAccount(accModCache, accModCache[:]) - assertAccount(accDelCache, accDelCache[:]) - - // assertStorage ensures that a storage slot matches the given blob if - // it's already covered by the disk snapshot, and errors out otherwise. - assertStorage := func(account common.Hash, slot common.Hash, data []byte) { - t.Helper() - blob, err := base.Storage(account, slot) - if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && err != ErrNotCoveredYet { - t.Fatalf("test %d: post-marker (%x) storage access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) - } - if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { - t.Fatalf("test %d: pre-marker (%x) storage access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) - } - } - assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - assertStorage(conModCache, conModCacheSlot, conModCacheSlot[:]) - assertStorage(conDelCache, conDelCacheSlot, conDelCacheSlot[:]) - assertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) - - // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ - accDelNoCache: {}, - accDelCache: {}, - conNukeNoCache: {}, - conNukeCache: {}, - }, map[common.Hash][]byte{ - accModNoCache: reverse(accModNoCache[:]), - accModCache: reverse(accModCache[:]), - }, map[common.Hash]map[common.Hash][]byte{ - conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, - conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, - conDelNoCache: {conDelNoCacheSlot: nil}, - conDelCache: {conDelCacheSlot: nil}, - }); err != nil { - t.Fatalf("test %d: failed to update snapshot tree: %v", i, err) - } - if err := snaps.Cap(diffRoot, 0); err != nil { - t.Fatalf("test %d: failed to flatten snapshot tree: %v", i, err) - } - // Retrieve all the data through the disk layer and validate it - base = snaps.Snapshot(diffRoot) - if _, ok := base.(*diskLayer); !ok { - t.Fatalf("test %d: update not flattend into the disk layer", i) - } - assertAccount(accNoModNoCache, accNoModNoCache[:]) - assertAccount(accNoModCache, accNoModCache[:]) - assertAccount(accModNoCache, reverse(accModNoCache[:])) - assertAccount(accModCache, reverse(accModCache[:])) - assertAccount(accDelNoCache, nil) - assertAccount(accDelCache, nil) - - assertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) - assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - assertStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) - assertStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) - assertStorage(conDelNoCache, conDelNoCacheSlot, nil) - assertStorage(conDelCache, conDelCacheSlot, nil) - assertStorage(conNukeNoCache, conNukeNoCacheSlot, nil) - assertStorage(conNukeCache, conNukeCacheSlot, nil) - - // Retrieve all the data directly from the database and validate it - - // assertDatabaseAccount ensures that an account inside the database matches - // the given blob if it's already covered by the disk snapshot, and does not - // exist otherwise. - assertDatabaseAccount := func(account common.Hash, data []byte) { - t.Helper() - blob := rawdb.ReadAccountSnapshot(db, account) - if bytes.Compare(account[:], genMarker) > 0 && blob != nil { - t.Fatalf("test %d: post-marker (%x) account database access (%x) succeeded: %x", i, genMarker, account, blob) - } - if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { - t.Fatalf("test %d: pre-marker (%x) account database access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) - } - } - assertDatabaseAccount(accNoModNoCache, accNoModNoCache[:]) - assertDatabaseAccount(accNoModCache, accNoModCache[:]) - assertDatabaseAccount(accModNoCache, reverse(accModNoCache[:])) - assertDatabaseAccount(accModCache, reverse(accModCache[:])) - assertDatabaseAccount(accDelNoCache, nil) - assertDatabaseAccount(accDelCache, nil) - - // assertDatabaseStorage ensures that a storage slot inside the database - // matches the given blob if it's already covered by the disk snapshot, - // and does not exist otherwise. - assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { - t.Helper() - blob := rawdb.ReadStorageSnapshot(db, account, slot) - if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && blob != nil { - t.Fatalf("test %d: post-marker (%x) storage database access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) - } - if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { - t.Fatalf("test %d: pre-marker (%x) storage database access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) - } - } - assertDatabaseStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) - assertDatabaseStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) - assertDatabaseStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) - assertDatabaseStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) - assertDatabaseStorage(conDelNoCache, conDelNoCacheSlot, nil) - assertDatabaseStorage(conDelCache, conDelCacheSlot, nil) - assertDatabaseStorage(conNukeNoCache, conNukeNoCacheSlot, nil) - assertDatabaseStorage(conNukeCache, conNukeCacheSlot, nil) - } -} - -// Tests that merging something into a disk layer persists it into the database -// and invalidates any previously written and cached values, discarding anything -// after the in-progress generation marker. -// -// This test case is a tiny specialized case of TestDiskPartialMerge, which tests -// some very specific cornercases that random tests won't ever trigger. -func TestDiskMidAccountPartialMerge(t *testing.T) { - // TODO(@karalabe) ? -} - -// TestDiskSeek tests that seek-operations work on the disk layer -func TestDiskSeek(t *testing.T) { - // Create some accounts in the disk layer - var db ethdb.Database - - if dir, err := ioutil.TempDir("", "disklayer-test"); err != nil { - t.Fatal(err) - } else { - defer os.RemoveAll(dir) - diskdb, err := leveldb.New(dir, 256, 0, "") - if err != nil { - t.Fatal(err) - } - db = rawdb.NewDatabase(diskdb) - } - // Fill even keys [0,2,4...] - for i := 0; i < 0xff; i += 2 { - acc := common.Hash{byte(i)} - rawdb.WriteAccountSnapshot(db, acc, acc[:]) - } - // Add an 'higher' key, with incorrect (higher) prefix - highKey := []byte{rawdb.SnapshotAccountPrefix[0] + 1} - db.Put(highKey, []byte{0xff, 0xff}) - - baseRoot := randomHash() - rawdb.WriteSnapshotRoot(db, baseRoot) - - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - baseRoot: &diskLayer{ - diskdb: db, - cache: fastcache.New(500 * 1024), - root: baseRoot, - }, - }, - } - // Test some different seek positions - type testcase struct { - pos byte - expkey byte - } - var cases = []testcase{ - {0xff, 0x55}, // this should exit immediately without checking key - {0x01, 0x02}, - {0xfe, 0xfe}, - {0xfd, 0xfe}, - {0x00, 0x00}, - } - for i, tc := range cases { - it, err := snaps.AccountIterator(baseRoot, common.Hash{tc.pos}) - if err != nil { - t.Fatalf("case %d, error: %v", i, err) - } - count := 0 - for it.Next() { - k, v, err := it.Hash()[0], it.Account()[0], it.Error() - if err != nil { - t.Fatalf("test %d, item %d, error: %v", i, count, err) - } - // First item in iterator should have the expected key - if count == 0 && k != tc.expkey { - t.Fatalf("test %d, item %d, got %v exp %v", i, count, k, tc.expkey) - } - count++ - if v != k { - t.Fatalf("test %d, item %d, value wrong, got %v exp %v", i, count, v, k) - } - } - } -} diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go deleted file mode 100644 index ef4859c..0000000 --- a/core/state/snapshot/iterator_test.go +++ /dev/null @@ -1,1046 +0,0 @@ -// 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 . - -package snapshot - -import ( - "bytes" - "encoding/binary" - "fmt" - "math/rand" - "testing" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ethereum/go-ethereum/common" -) - -// TestAccountIteratorBasics tests some simple single-layer(diff and disk) iteration -func TestAccountIteratorBasics(t *testing.T) { - var ( - destructs = make(map[common.Hash]struct{}) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - // Fill up a parent - for i := 0; i < 100; i++ { - h := randomHash() - data := randomAccount() - - accounts[h] = data - if rand.Intn(4) == 0 { - destructs[h] = struct{}{} - } - if rand.Intn(2) == 0 { - accStorage := make(map[common.Hash][]byte) - value := make([]byte, 32) - rand.Read(value) - accStorage[randomHash()] = value - storage[h] = accStorage - } - } - // Add some (identical) layers on top - diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) - it := diffLayer.AccountIterator(common.Hash{}) - verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator - - diskLayer := diffToDisk(diffLayer) - it = diskLayer.AccountIterator(common.Hash{}) - verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator -} - -// TestStorageIteratorBasics tests some simple single-layer(diff and disk) iteration for storage -func TestStorageIteratorBasics(t *testing.T) { - var ( - nilStorage = make(map[common.Hash]int) - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - // Fill some random data - for i := 0; i < 10; i++ { - h := randomHash() - accounts[h] = randomAccount() - - accStorage := make(map[common.Hash][]byte) - value := make([]byte, 32) - - var nilstorage int - for i := 0; i < 100; i++ { - rand.Read(value) - if rand.Intn(2) == 0 { - accStorage[randomHash()] = common.CopyBytes(value) - } else { - accStorage[randomHash()] = nil // delete slot - nilstorage += 1 - } - } - storage[h] = accStorage - nilStorage[h] = nilstorage - } - // Add some (identical) layers on top - diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, nil, copyAccounts(accounts), copyStorage(storage)) - for account := range accounts { - it, _ := diffLayer.StorageIterator(account, common.Hash{}) - verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator - } - - diskLayer := diffToDisk(diffLayer) - for account := range accounts { - it, _ := diskLayer.StorageIterator(account, common.Hash{}) - verifyIterator(t, 100-nilStorage[account], it, verifyNothing) // Nil is allowed for single layer iterator - } -} - -type testIterator struct { - values []byte -} - -func newTestIterator(values ...byte) *testIterator { - return &testIterator{values} -} - -func (ti *testIterator) Seek(common.Hash) { - panic("implement me") -} - -func (ti *testIterator) Next() bool { - ti.values = ti.values[1:] - return len(ti.values) > 0 -} - -func (ti *testIterator) Error() error { - return nil -} - -func (ti *testIterator) Hash() common.Hash { - return common.BytesToHash([]byte{ti.values[0]}) -} - -func (ti *testIterator) Account() []byte { - return nil -} - -func (ti *testIterator) Slot() []byte { - return nil -} - -func (ti *testIterator) Release() {} - -func TestFastIteratorBasics(t *testing.T) { - type testCase struct { - lists [][]byte - expKeys []byte - } - for i, tc := range []testCase{ - {lists: [][]byte{{0, 1, 8}, {1, 2, 8}, {2, 9}, {4}, - {7, 14, 15}, {9, 13, 15, 16}}, - expKeys: []byte{0, 1, 2, 4, 7, 8, 9, 13, 14, 15, 16}}, - {lists: [][]byte{{0, 8}, {1, 2, 8}, {7, 14, 15}, {8, 9}, - {9, 10}, {10, 13, 15, 16}}, - expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, - } { - var iterators []*weightedIterator - for i, data := range tc.lists { - it := newTestIterator(data...) - iterators = append(iterators, &weightedIterator{it, i}) - } - fi := &fastIterator{ - iterators: iterators, - initiated: false, - } - count := 0 - for fi.Next() { - if got, exp := fi.Hash()[31], tc.expKeys[count]; exp != got { - t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) - } - count++ - } - } -} - -type verifyContent int - -const ( - verifyNothing verifyContent = iota - verifyAccount - verifyStorage -) - -func verifyIterator(t *testing.T, expCount int, it Iterator, verify verifyContent) { - t.Helper() - - var ( - count = 0 - last = common.Hash{} - ) - for it.Next() { - hash := it.Hash() - if bytes.Compare(last[:], hash[:]) >= 0 { - t.Errorf("wrong order: %x >= %x", last, hash) - } - count++ - if verify == verifyAccount && len(it.(AccountIterator).Account()) == 0 { - t.Errorf("iterator returned nil-value for hash %x", hash) - } else if verify == verifyStorage && len(it.(StorageIterator).Slot()) == 0 { - t.Errorf("iterator returned nil-value for hash %x", hash) - } - last = hash - } - if count != expCount { - t.Errorf("iterator count mismatch: have %d, want %d", count, expCount) - } - if err := it.Error(); err != nil { - t.Errorf("iterator failed: %v", err) - } -} - -// TestAccountIteratorTraversal tests some simple multi-layer iteration. -func TestAccountIteratorTraversal(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, - randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, - randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, - randomAccountSet("0xcc", "0xf0", "0xff"), nil) - - // Verify the single and multi-layer iterators - head := snaps.Snapshot(common.HexToHash("0x04")) - - verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing) - verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) - - it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) - verifyIterator(t, 7, it, verifyAccount) - it.Release() - - // Test after persist some bottom-most layers into the disk, - // the functionalities still work. - limit := aggregatorMemoryLimit - defer func() { - aggregatorMemoryLimit = limit - }() - aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk - snaps.Cap(common.HexToHash("0x04"), 2) - verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) - - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) - verifyIterator(t, 7, it, verifyAccount) - it.Release() -} - -func TestStorageIteratorTraversal(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil)) - - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x04", "0x05", "0x06"}}, nil)) - - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil)) - - // Verify the single and multi-layer iterators - head := snaps.Snapshot(common.HexToHash("0x04")) - - diffIter, _ := head.(snapshot).StorageIterator(common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 3, diffIter, verifyNothing) - verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) - - it, _ := snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 6, it, verifyStorage) - it.Release() - - // Test after persist some bottom-most layers into the disk, - // the functionalities still work. - limit := aggregatorMemoryLimit - defer func() { - aggregatorMemoryLimit = limit - }() - aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk - snaps.Cap(common.HexToHash("0x04"), 2) - verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) - - it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 6, it, verifyStorage) - it.Release() -} - -// TestAccountIteratorTraversalValues tests some multi-layer iteration, where we -// also expect the correct values to show up. -func TestAccountIteratorTraversalValues(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Create a batch of account sets to seed subsequent layers with - var ( - a = make(map[common.Hash][]byte) - b = make(map[common.Hash][]byte) - c = make(map[common.Hash][]byte) - d = make(map[common.Hash][]byte) - e = make(map[common.Hash][]byte) - f = make(map[common.Hash][]byte) - g = make(map[common.Hash][]byte) - h = make(map[common.Hash][]byte) - ) - for i := byte(2); i < 0xff; i++ { - a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) - if i > 20 && i%2 == 0 { - b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) - } - if i%4 == 0 { - c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) - } - if i%7 == 0 { - d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) - } - if i%8 == 0 { - e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) - } - if i > 50 || i < 85 { - f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) - } - if i%64 == 0 { - g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) - } - if i%128 == 0 { - h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) - } - } - // Assemble a stack of snapshots from the account layers - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, a, nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, b, nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, c, nil) - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, d, nil) - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, e, nil) - snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, f, nil) - snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, g, nil) - snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil) - - it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) - head := snaps.Snapshot(common.HexToHash("0x09")) - for it.Next() { - hash := it.Hash() - want, err := head.AccountRLP(hash) - if err != nil { - t.Fatalf("failed to retrieve expected account: %v", err) - } - if have := it.Account(); !bytes.Equal(want, have) { - t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) - } - } - it.Release() - - // Test after persist some bottom-most layers into the disk, - // the functionalities still work. - limit := aggregatorMemoryLimit - defer func() { - aggregatorMemoryLimit = limit - }() - aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk - snaps.Cap(common.HexToHash("0x09"), 2) - - it, _ = snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) - for it.Next() { - hash := it.Hash() - want, err := head.AccountRLP(hash) - if err != nil { - t.Fatalf("failed to retrieve expected account: %v", err) - } - if have := it.Account(); !bytes.Equal(want, have) { - t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) - } - } - it.Release() -} - -func TestStorageIteratorTraversalValues(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - wrapStorage := func(storage map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { - return map[common.Hash]map[common.Hash][]byte{ - common.HexToHash("0xaa"): storage, - } - } - // Create a batch of storage sets to seed subsequent layers with - var ( - a = make(map[common.Hash][]byte) - b = make(map[common.Hash][]byte) - c = make(map[common.Hash][]byte) - d = make(map[common.Hash][]byte) - e = make(map[common.Hash][]byte) - f = make(map[common.Hash][]byte) - g = make(map[common.Hash][]byte) - h = make(map[common.Hash][]byte) - ) - for i := byte(2); i < 0xff; i++ { - a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) - if i > 20 && i%2 == 0 { - b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) - } - if i%4 == 0 { - c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) - } - if i%7 == 0 { - d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) - } - if i%8 == 0 { - e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) - } - if i > 50 || i < 85 { - f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) - } - if i%64 == 0 { - g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) - } - if i%128 == 0 { - h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) - } - } - // Assemble a stack of snapshots from the account layers - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), wrapStorage(a)) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), wrapStorage(b)) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), wrapStorage(c)) - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, randomAccountSet("0xaa"), wrapStorage(d)) - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, randomAccountSet("0xaa"), wrapStorage(e)) - snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, randomAccountSet("0xaa"), wrapStorage(e)) - snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, randomAccountSet("0xaa"), wrapStorage(g)) - snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, randomAccountSet("0xaa"), wrapStorage(h)) - - it, _ := snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) - head := snaps.Snapshot(common.HexToHash("0x09")) - for it.Next() { - hash := it.Hash() - want, err := head.Storage(common.HexToHash("0xaa"), hash) - if err != nil { - t.Fatalf("failed to retrieve expected storage slot: %v", err) - } - if have := it.Slot(); !bytes.Equal(want, have) { - t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want) - } - } - it.Release() - - // Test after persist some bottom-most layers into the disk, - // the functionalities still work. - limit := aggregatorMemoryLimit - defer func() { - aggregatorMemoryLimit = limit - }() - aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk - snaps.Cap(common.HexToHash("0x09"), 2) - - it, _ = snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) - for it.Next() { - hash := it.Hash() - want, err := head.Storage(common.HexToHash("0xaa"), hash) - if err != nil { - t.Fatalf("failed to retrieve expected slot: %v", err) - } - if have := it.Slot(); !bytes.Equal(want, have) { - t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want) - } - } - it.Release() -} - -// This testcase is notorious, all layers contain the exact same 200 accounts. -func TestAccountIteratorLargeTraversal(t *testing.T) { - // Create a custom account factory to recreate the same addresses - makeAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - // Build up a large stack of snapshots - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - for i := 1; i < 128; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) - } - // Iterate the entire stack and ensure everything is hit only once - head := snaps.Snapshot(common.HexToHash("0x80")) - verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing) - verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) - - it, _ := snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{}) - verifyIterator(t, 200, it, verifyAccount) - it.Release() - - // Test after persist some bottom-most layers into the disk, - // the functionalities still work. - limit := aggregatorMemoryLimit - defer func() { - aggregatorMemoryLimit = limit - }() - aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk - snaps.Cap(common.HexToHash("0x80"), 2) - - verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) - - it, _ = snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{}) - verifyIterator(t, 200, it, verifyAccount) - it.Release() -} - -// TestAccountIteratorFlattening tests what happens when we -// - have a live iterator on child C (parent C1 -> C2 .. CN) -// - flattens C2 all the way into CN -// - continues iterating -func TestAccountIteratorFlattening(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Create a stack of diffs on top - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, - randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, - randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, - randomAccountSet("0xcc", "0xf0", "0xff"), nil) - - // Create an iterator and flatten the data from underneath it - it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) - defer it.Release() - - if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { - t.Fatalf("failed to flatten snapshot stack: %v", err) - } - //verifyIterator(t, 7, it) -} - -func TestAccountIteratorSeek(t *testing.T) { - // Create a snapshot stack with some initial data - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, - randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, - randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, - randomAccountSet("0xcc", "0xf0", "0xff"), nil) - - // Account set is now - // 02: aa, ee, f0, ff - // 03: aa, bb, dd, ee, f0 (, f0), ff - // 04: aa, bb, cc, dd, ee, f0 (, f0), ff (, ff) - // Construct various iterators and ensure their traversal is correct - it, _ := snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd")) - defer it.Release() - verifyIterator(t, 3, it, verifyAccount) // expected: ee, f0, ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa")) - defer it.Release() - verifyIterator(t, 4, it, verifyAccount) // expected: aa, ee, f0, ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff")) - defer it.Release() - verifyIterator(t, 1, it, verifyAccount) // expected: ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff1")) - defer it.Release() - verifyIterator(t, 0, it, verifyAccount) // expected: nothing - - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb")) - defer it.Release() - verifyIterator(t, 6, it, verifyAccount) // expected: bb, cc, dd, ee, f0, ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef")) - defer it.Release() - verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0")) - defer it.Release() - verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff")) - defer it.Release() - verifyIterator(t, 1, it, verifyAccount) // expected: ff - - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff1")) - defer it.Release() - verifyIterator(t, 0, it, verifyAccount) // expected: nothing -} - -func TestStorageIteratorSeek(t *testing.T) { - // Create a snapshot stack with some initial data - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil)) - - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x05", "0x06"}}, nil)) - - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x05", "0x08"}}, nil)) - - // Account set is now - // 02: 01, 03, 05 - // 03: 01, 02, 03, 05 (, 05), 06 - // 04: 01(, 01), 02, 03, 05(, 05, 05), 06, 08 - // Construct various iterators and ensure their traversal is correct - it, _ := snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x01")) - defer it.Release() - verifyIterator(t, 3, it, verifyStorage) // expected: 01, 03, 05 - - it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x02")) - defer it.Release() - verifyIterator(t, 2, it, verifyStorage) // expected: 03, 05 - - it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x5")) - defer it.Release() - verifyIterator(t, 1, it, verifyStorage) // expected: 05 - - it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x6")) - defer it.Release() - verifyIterator(t, 0, it, verifyStorage) // expected: nothing - - it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x01")) - defer it.Release() - verifyIterator(t, 6, it, verifyStorage) // expected: 01, 02, 03, 05, 06, 08 - - it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x05")) - defer it.Release() - verifyIterator(t, 3, it, verifyStorage) // expected: 05, 06, 08 - - it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x08")) - defer it.Release() - verifyIterator(t, 1, it, verifyStorage) // expected: 08 - - it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x09")) - defer it.Release() - verifyIterator(t, 0, it, verifyStorage) // expected: nothing -} - -// TestAccountIteratorDeletions tests that the iterator behaves correct when there are -// deleted accounts (where the Account() value is nil). The iterator -// should not output any accounts or nil-values for those cases. -func TestAccountIteratorDeletions(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), - nil, randomAccountSet("0x11", "0x22", "0x33"), nil) - - deleted := common.HexToHash("0x22") - destructed := map[common.Hash]struct{}{ - deleted: {}, - } - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), - destructed, randomAccountSet("0x11", "0x33"), nil) - - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), - nil, randomAccountSet("0x33", "0x44", "0x55"), nil) - - // The output should be 11,33,44,55 - it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) - // Do a quick check - verifyIterator(t, 4, it, verifyAccount) - it.Release() - - // And a more detailed verification that we indeed do not see '0x22' - it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) - defer it.Release() - for it.Next() { - hash := it.Hash() - if it.Account() == nil { - t.Errorf("iterator returned nil-value for hash %x", hash) - } - if hash == deleted { - t.Errorf("expected deleted elem %x to not be returned by iterator", deleted) - } - } -} - -func TestStorageIteratorDeletions(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil)) - - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x04", "0x06"}}, [][]string{{"0x01", "0x03"}})) - - // The output should be 02,04,05,06 - it, _ := snaps.StorageIterator(common.HexToHash("0x03"), common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 4, it, verifyStorage) - it.Release() - - // The output should be 04,05,06 - it, _ = snaps.StorageIterator(common.HexToHash("0x03"), common.HexToHash("0xaa"), common.HexToHash("0x03")) - verifyIterator(t, 3, it, verifyStorage) - it.Release() - - // Destruct the whole storage - destructed := map[common.Hash]struct{}{ - common.HexToHash("0xaa"): {}, - } - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), destructed, nil, nil) - - it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 0, it, verifyStorage) - it.Release() - - // Re-insert the slots of the same account - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, - randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x07", "0x08", "0x09"}}, nil)) - - // The output should be 07,08,09 - it, _ = snaps.StorageIterator(common.HexToHash("0x05"), common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 3, it, verifyStorage) - it.Release() - - // Destruct the whole storage but re-create the account in the same layer - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), destructed, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x11", "0x12"}}, nil)) - it, _ = snaps.StorageIterator(common.HexToHash("0x06"), common.HexToHash("0xaa"), common.Hash{}) - verifyIterator(t, 2, it, verifyStorage) // The output should be 11,12 - it.Release() - - verifyIterator(t, 2, snaps.Snapshot(common.HexToHash("0x06")).(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) -} - -// BenchmarkAccountIteratorTraversal is a bit a bit notorious -- all layers contain the -// exact same 200 accounts. That means that we need to process 2000 items, but -// only spit out 200 values eventually. -// -// The value-fetching benchmark is easy on the binary iterator, since it never has to reach -// down at any depth for retrieving the values -- all are on the toppmost layer -// -// BenchmarkAccountIteratorTraversal/binary_iterator_keys-6 2239 483674 ns/op -// BenchmarkAccountIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op -// BenchmarkAccountIteratorTraversal/fast_iterator_keys-6 1923 677966 ns/op -// BenchmarkAccountIteratorTraversal/fast_iterator_values-6 1741 649967 ns/op -func BenchmarkAccountIteratorTraversal(b *testing.B) { - // Create a custom account factory to recreate the same addresses - makeAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - // Build up a large stack of snapshots - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - for i := 1; i <= 100; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) - } - // We call this once before the benchmark, so the creation of - // sorted accountlists are not included in the results. - head := snaps.Snapshot(common.HexToHash("0x65")) - head.(*diffLayer).newBinaryAccountIterator() - - b.Run("binary iterator keys", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := head.(*diffLayer).newBinaryAccountIterator() - for it.Next() { - got++ - } - if exp := 200; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("binary iterator values", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := head.(*diffLayer).newBinaryAccountIterator() - for it.Next() { - got++ - head.(*diffLayer).accountRLP(it.Hash(), 0) - } - if exp := 200; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator keys", func(b *testing.B) { - for i := 0; i < b.N; i++ { - it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) - defer it.Release() - - got := 0 - for it.Next() { - got++ - } - if exp := 200; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator values", func(b *testing.B) { - for i := 0; i < b.N; i++ { - it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) - defer it.Release() - - got := 0 - for it.Next() { - got++ - it.Account() - } - if exp := 200; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) -} - -// BenchmarkAccountIteratorLargeBaselayer is a pretty realistic benchmark, where -// the baselayer is a lot larger than the upper layer. -// -// This is heavy on the binary iterator, which in most cases will have to -// call recursively 100 times for the majority of the values -// -// BenchmarkAccountIteratorLargeBaselayer/binary_iterator_(keys)-6 514 1971999 ns/op -// BenchmarkAccountIteratorLargeBaselayer/binary_iterator_(values)-6 61 18997492 ns/op -// BenchmarkAccountIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op -// BenchmarkAccountIteratorLargeBaselayer/fast_iterator_(values)-6 4047 296823 ns/op -func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { - // Create a custom account factory to recreate the same addresses - makeAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - // Build up a large stack of snapshots - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(2000), nil) - for i := 2; i <= 100; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(20), nil) - } - // We call this once before the benchmark, so the creation of - // sorted accountlists are not included in the results. - head := snaps.Snapshot(common.HexToHash("0x65")) - head.(*diffLayer).newBinaryAccountIterator() - - b.Run("binary iterator (keys)", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := head.(*diffLayer).newBinaryAccountIterator() - for it.Next() { - got++ - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("binary iterator (values)", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := head.(*diffLayer).newBinaryAccountIterator() - for it.Next() { - got++ - v := it.Hash() - head.(*diffLayer).accountRLP(v, 0) - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator (keys)", func(b *testing.B) { - for i := 0; i < b.N; i++ { - it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) - defer it.Release() - - got := 0 - for it.Next() { - got++ - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator (values)", func(b *testing.B) { - for i := 0; i < b.N; i++ { - it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) - defer it.Release() - - got := 0 - for it.Next() { - it.Account() - got++ - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) -} - -/* -func BenchmarkBinaryAccountIteration(b *testing.B) { - benchmarkAccountIteration(b, func(snap snapshot) AccountIterator { - return snap.(*diffLayer).newBinaryAccountIterator() - }) -} - -func BenchmarkFastAccountIteration(b *testing.B) { - benchmarkAccountIteration(b, newFastAccountIterator) -} - -func benchmarkAccountIteration(b *testing.B, iterator func(snap snapshot) AccountIterator) { - // Create a diff stack and randomize the accounts across them - layers := make([]map[common.Hash][]byte, 128) - for i := 0; i < len(layers); i++ { - layers[i] = make(map[common.Hash][]byte) - } - for i := 0; i < b.N; i++ { - depth := rand.Intn(len(layers)) - layers[depth][randomHash()] = randomAccount() - } - stack := snapshot(emptyLayer()) - for _, layer := range layers { - stack = stack.Update(common.Hash{}, layer, nil, nil) - } - // Reset the timers and report all the stats - it := iterator(stack) - - b.ResetTimer() - b.ReportAllocs() - - for it.Next() { - } -} -*/ diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go deleted file mode 100644 index 94e3610..0000000 --- a/core/state/snapshot/snapshot_test.go +++ /dev/null @@ -1,371 +0,0 @@ -// 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 . - -package snapshot - -import ( - "fmt" - "math/big" - "math/rand" - "testing" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" -) - -// randomHash generates a random blob of data and returns it as a hash. -func randomHash() common.Hash { - var hash common.Hash - if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { - panic(err) - } - return hash -} - -// randomAccount generates a random account and returns it RLP encoded. -func randomAccount() []byte { - root := randomHash() - a := Account{ - Balance: big.NewInt(rand.Int63()), - Nonce: rand.Uint64(), - Root: root[:], - CodeHash: emptyCode[:], - } - data, _ := rlp.EncodeToBytes(a) - return data -} - -// randomAccountSet generates a set of random accounts with the given strings as -// the account address hashes. -func randomAccountSet(hashes ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, hash := range hashes { - accounts[common.HexToHash(hash)] = randomAccount() - } - return accounts -} - -// randomStorageSet generates a set of random slots with the given strings as -// the slot addresses. -func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]string) map[common.Hash]map[common.Hash][]byte { - storages := make(map[common.Hash]map[common.Hash][]byte) - for index, account := range accounts { - storages[common.HexToHash(account)] = make(map[common.Hash][]byte) - - if index < len(hashes) { - hashes := hashes[index] - for _, hash := range hashes { - storages[common.HexToHash(account)][common.HexToHash(hash)] = randomHash().Bytes() - } - } - if index < len(nilStorage) { - nils := nilStorage[index] - for _, hash := range nils { - storages[common.HexToHash(account)][common.HexToHash(hash)] = nil - } - } - } - return storages -} - -// Tests that if a disk layer becomes stale, no active external references will -// be returned with junk data. This version of the test flattens every diff layer -// to check internal corner case around the bottom-most memory accumulator. -func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Retrieve a reference to the base and commit a diff on top - ref := snaps.Snapshot(base.root) - - accounts := map[common.Hash][]byte{ - common.HexToHash("0xa1"): randomAccount(), - } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if n := len(snaps.layers); n != 2 { - t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) - } - // Commit the diff layer onto the disk and ensure it's persisted - if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil { - t.Fatalf("failed to merge diff layer onto disk: %v", err) - } - // Since the base layer was modified, ensure that data retrieval on the external reference fail - if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { - t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) - } - if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { - t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) - } - if n := len(snaps.layers); n != 1 { - t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1) - fmt.Println(snaps.layers) - } -} - -// Tests that if a disk layer becomes stale, no active external references will -// be returned with junk data. This version of the test retains the bottom diff -// layer to check the usual mode of operation where the accumulator is retained. -func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Retrieve a reference to the base and commit two diffs on top - ref := snaps.Snapshot(base.root) - - accounts := map[common.Hash][]byte{ - common.HexToHash("0xa1"): randomAccount(), - } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if n := len(snaps.layers); n != 3 { - t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) - } - // Commit the diff layer onto the disk and ensure it's persisted - defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) - aggregatorMemoryLimit = 0 - - if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil { - t.Fatalf("failed to merge diff layer onto disk: %v", err) - } - // Since the base layer was modified, ensure that data retrievald on the external reference fail - if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { - t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) - } - if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { - t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) - } - if n := len(snaps.layers); n != 2 { - t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) - fmt.Println(snaps.layers) - } -} - -// Tests that if a diff layer becomes stale, no active external references will -// be returned with junk data. This version of the test flattens every diff layer -// to check internal corner case around the bottom-most memory accumulator. -func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Commit two diffs on top and retrieve a reference to the bottommost - accounts := map[common.Hash][]byte{ - common.HexToHash("0xa1"): randomAccount(), - } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if n := len(snaps.layers); n != 3 { - t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) - } - ref := snaps.Snapshot(common.HexToHash("0x02")) - - // Flatten the diff layer into the bottom accumulator - if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { - t.Fatalf("failed to flatten diff layer into accumulator: %v", err) - } - // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail - if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { - t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) - } - if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { - t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) - } - if n := len(snaps.layers); n != 2 { - t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) - fmt.Println(snaps.layers) - } -} - -// Tests that if a diff layer becomes stale, no active external references will -// be returned with junk data. This version of the test retains the bottom diff -// layer to check the usual mode of operation where the accumulator is retained. -func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Commit three diffs on top and retrieve a reference to the bottommost - accounts := map[common.Hash][]byte{ - common.HexToHash("0xa1"): randomAccount(), - } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if n := len(snaps.layers); n != 4 { - t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4) - } - ref := snaps.Snapshot(common.HexToHash("0x02")) - - // Doing a Cap operation with many allowed layers should be a no-op - exp := len(snaps.layers) - if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil { - t.Fatalf("failed to flatten diff layer into accumulator: %v", err) - } - if got := len(snaps.layers); got != exp { - t.Errorf("layers modified, got %d exp %d", got, exp) - } - // Flatten the diff layer into the bottom accumulator - if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil { - t.Fatalf("failed to flatten diff layer into accumulator: %v", err) - } - // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail - if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { - t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) - } - if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { - t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) - } - if n := len(snaps.layers); n != 3 { - t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3) - fmt.Println(snaps.layers) - } -} - -// TestPostCapBasicDataAccess tests some functionality regarding capping/flattening. -func TestPostCapBasicDataAccess(t *testing.T) { - // setAccount is a helper to construct a random account entry and assign it to - // an account slot in a snapshot - setAccount := func(accKey string) map[common.Hash][]byte { - return map[common.Hash][]byte{ - common.HexToHash(accKey): randomAccount(), - } - } - // Create a starting base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // The lowest difflayer - snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) - snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) - snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) - - snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) - snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) - - // checkExist verifies if an account exiss in a snapshot - checkExist := func(layer *diffLayer, key string) error { - if data, _ := layer.Account(common.HexToHash(key)); data == nil { - return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) - } - return nil - } - // shouldErr checks that an account access errors as expected - shouldErr := func(layer *diffLayer, key string) error { - if data, err := layer.Account(common.HexToHash(key)); err == nil { - return fmt.Errorf("expected error, got data %x", data) - } - return nil - } - // check basics - snap := snaps.Snapshot(common.HexToHash("0xb3")).(*diffLayer) - - if err := checkExist(snap, "0xa1"); err != nil { - t.Error(err) - } - if err := checkExist(snap, "0xb2"); err != nil { - t.Error(err) - } - if err := checkExist(snap, "0xb3"); err != nil { - t.Error(err) - } - // Cap to a bad root should fail - if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil { - t.Errorf("expected error, got none") - } - // Now, merge the a-chain - snaps.Cap(common.HexToHash("0xa3"), 0) - - // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is - // the parent of b2, b2 should no longer be able to iterate into parent. - - // These should still be accessible - if err := checkExist(snap, "0xb2"); err != nil { - t.Error(err) - } - if err := checkExist(snap, "0xb3"); err != nil { - t.Error(err) - } - // But these would need iteration into the modified parent - if err := shouldErr(snap, "0xa1"); err != nil { - t.Error(err) - } - if err := shouldErr(snap, "0xa2"); err != nil { - t.Error(err) - } - if err := shouldErr(snap, "0xa3"); err != nil { - t.Error(err) - } - // Now, merge it again, just for fun. It should now error, since a3 - // is a disk layer - if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil { - t.Error("expected error capping the disk layer, got none") - } -} diff --git a/core/state/snapshot/wipe_test.go b/core/state/snapshot/wipe_test.go deleted file mode 100644 index a656982..0000000 --- a/core/state/snapshot/wipe_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// 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 . - -package snapshot - -import ( - "math/rand" - "testing" - - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb/memorydb" -) - -// Tests that given a database with random data content, all parts of a snapshot -// can be crrectly wiped without touching anything else. -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.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) - 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()) - } - } - // 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 hash := rawdb.ReadSnapshotRoot(db); hash == (common.Hash{}) { - t.Errorf("snapshot block marker mismatch: have %#x, want ", hash) - } - // Wipe all snapshot entries from the database - <-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) - } - } - it = db.NewIterator(rawdb.SnapshotStoragePrefix, 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) - } - } - if hash := rawdb.ReadSnapshotRoot(db); hash != (common.Hash{}) { - t.Errorf("snapshot block 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) - } -} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 35ce39f..abfa2aa 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,7 @@ package vm import ( + "errors" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" "github.com/ethereum/go-ethereum/common" @@ -265,7 +266,12 @@ func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ func opBalanceMultiCoin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { addr, cid := callContext.stack.pop(), callContext.stack.pop() - callContext.stack.push(interpreter.evm.StateDB.GetBalanceMultiCoin(common.BigToAddress(addr), common.BigToHash(cid))) + res, err := uint256.FromBig(interpreter.evm.StateDB.GetBalanceMultiCoin( + common.BigToAddress(addr.ToBig()), common.BigToHash(cid.ToBig()))) + if err { + return nil, errors.New("balance overflow") + } + callContext.stack.push(res) return nil, nil } @@ -725,7 +731,7 @@ func opCallExpert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) // Pop other call parameters. addr, value, cid, value2, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.Address(addr.Bytes20()) - coinID := common.BigToHash(cid) + coinID := common.BigToHash(cid.ToBig()) // Get the arguments from the memory. args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) @@ -875,8 +881,8 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ return nil, nil } -func opEMC(pc *uint64, interpreter *EVMInterpreter, contract *Contract, callContext *callCtx) ([]byte, error) { - return nil, interpreter.evm.StateDB.EnableMultiCoin(contract.Address()) +func opEMC(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + return nil, interpreter.evm.StateDB.EnableMultiCoin(callContext.contract.Address()) } // following functions are used by the instruction jump table diff --git a/coreth.go b/coreth.go index 1fe2613..4d0c2ee 100644 --- a/coreth.go +++ b/coreth.go @@ -2,7 +2,7 @@ package coreth import ( "crypto/ecdsa" - "fmt" + //"fmt" "io" "os" @@ -17,7 +17,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/trie" + //"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/mattn/go-isatty" ) @@ -52,18 +53,18 @@ func NewETHChain(config *eth.Config, nodecfg *node.Config, etherBase *common.Add if nodecfg == nil { nodecfg = &node.Config{} } - mux := new(event.TypeMux) - ctx, ep, err := node.NewServiceContext(nodecfg, mux) + //mux := new(event.TypeMux) + node, err := node.New(nodecfg) if err != nil { panic(err) } - if ep != "" { - log.Info(fmt.Sprintf("temporary keystore = %s", ep)) - } + //if ep != "" { + // log.Info(fmt.Sprintf("temporary keystore = %s", ep)) + //} cb := new(dummy.ConsensusCallbacks) mcb := new(miner.MinerCallbacks) bcb := new(eth.BackendCallbacks) - backend, _ := eth.New(&ctx, config, cb, mcb, bcb, chainDB) + backend, _ := eth.New(node, config, cb, mcb, bcb, chainDB) chain := ÐChain{backend: backend, cb: cb, mcb: mcb, bcb: bcb} if etherBase == nil { etherBase = &BlackholeAddr @@ -85,7 +86,7 @@ func (self *ETHChain) GenBlock() { } func (self *ETHChain) VerifyBlock(block *types.Block) bool { - txnHash := types.DeriveSha(block.Transactions()) + txnHash := types.DeriveSha(block.Transactions(), new(trie.Trie)) uncleHash := types.CalcUncleHash(block.Uncles()) ethHeader := block.Header() if txnHash != ethHeader.TxHash || uncleHash != ethHeader.UncleHash { diff --git a/eth/api.go b/eth/api.go index 92ac928..ada2a97 100644 --- a/eth/api.go +++ b/eth/api.go @@ -371,7 +371,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta var block *types.Block if number == rpc.LatestBlockNumber { block = api.eth.blockchain.CurrentBlock() - } else if blockNr == rpc.AcceptedBlockNumber { + } else if number == rpc.AcceptedBlockNumber { block = api.eth.AcceptedBlock() } else { block = api.eth.blockchain.GetBlockByNumber(uint64(number)) diff --git a/eth/backend.go b/eth/backend.go index 9222181..b1c29a5 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -41,7 +41,6 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/bloombits" @@ -50,6 +49,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" ) @@ -127,7 +127,7 @@ func New(stack *node.Node, config *Config, var err error if chainDb == nil { // Assemble the Ethereum object - chainDb, err = ctx.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") + chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") if err != nil { return nil, err } @@ -223,7 +223,7 @@ func New(stack *node.Node, config *Config, } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) + //eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) if err != nil { return nil, err } @@ -233,8 +233,8 @@ func New(stack *node.Node, config *Config, // Register the backend on the node stack.RegisterAPIs(eth.APIs()) - stack.RegisterProtocols(eth.Protocols()) - stack.RegisterLifecycle(eth) + //stack.RegisterProtocols(eth.Protocols()) + //stack.RegisterLifecycle(eth) return eth, nil } diff --git a/eth/protocol.go b/eth/protocol.go index 94260c2..ef5dcde 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/forkid" "github.com/ava-labs/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8753c05..9915ad4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -30,7 +30,6 @@ import ( "github.com/ava-labs/coreth/accounts/scwallet" "github.com/ava-labs/coreth/consensus/ethash" "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index ae4196f..cee6b57 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ava-labs/coreth/accounts" + "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" diff --git a/miner/miner.go b/miner/miner.go index 57938e1..e8e59a4 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" ) // Backend wraps all methods required for mining. @@ -60,7 +59,7 @@ type Miner struct { func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool, mcb *MinerCallbacks) *Miner { return &Miner{ - worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, mcb), + worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true, mcb), } } diff --git a/node/api.go b/node/api.go index e74d10b..4589d25 100644 --- a/node/api.go +++ b/node/api.go @@ -21,6 +21,7 @@ import ( "fmt" //"strings" + "github.com/ava-labs/coreth/internal/debug" "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/node/node.go b/node/node.go index c6e4181..3ed89ed 100644 --- a/node/node.go +++ b/node/node.go @@ -18,14 +18,13 @@ package node import ( "errors" + "os" "path/filepath" - "reflect" "strings" "sync" "github.com/ava-labs/coreth/accounts" "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/internal/debug" "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -48,7 +47,6 @@ type Node struct { state int // Tracks state of node lifecycle lock sync.Mutex - lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -61,6 +59,25 @@ const ( closedState ) +func (n *Node) openDataDir() error { + if n.config.DataDir == "" { + return nil // ephemeral + } + + instdir := filepath.Join(n.config.DataDir, n.config.name()) + if err := os.MkdirAll(instdir, 0700); err != nil { + return err + } + // Lock the instance directory to prevent concurrent use by another instance as well as + // accidental use of the instance directory as a database. + release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK")) + if err != nil { + return convertFileLockError(err) + } + n.dirLock = release + return nil +} + // New creates a new P2P node, ready for protocol registration. func New(conf *Config) (*Node, error) { // Copy config and resolve the datadir so future changes to the current @@ -266,3 +283,14 @@ func (n *Node) closeDatabases() (errors []error) { } return errors } + +// RegisterAPIs registers the APIs a service provides on the node. +func (n *Node) RegisterAPIs(apis []rpc.API) { + n.lock.Lock() + defer n.lock.Unlock() + + if n.state != initializingState { + panic("can't register APIs on running/stopped node") + } + n.rpcAPIs = append(n.rpcAPIs, apis...) +} diff --git a/rpc/metrics.go b/rpc/metrics.go new file mode 100644 index 0000000..7fb6fc0 --- /dev/null +++ b/rpc/metrics.go @@ -0,0 +1,39 @@ +// 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 . + +package rpc + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + rpcRequestGauge = metrics.NewRegisteredGauge("rpc/requests", nil) + successfulRequestGauge = metrics.NewRegisteredGauge("rpc/success", nil) + failedReqeustGauge = metrics.NewRegisteredGauge("rpc/failure", nil) + rpcServingTimer = metrics.NewRegisteredTimer("rpc/duration/all", nil) +) + +func newRPCServingTimer(method string, valid bool) metrics.Timer { + flag := "success" + if !valid { + flag = "failure" + } + m := fmt.Sprintf("rpc/duration/%s/%s", method, flag) + return metrics.GetOrRegisterTimer(m, nil) +} -- cgit v1.2.3-70-g09d2 From ed839907e592ad25e6119e145e7e05ca78b00fcd Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 16 Sep 2020 22:54:37 -0400 Subject: ... --- accounts/external/backend.go | 2 +- consensus/dummy/consensus.go | 4 ++-- core/blockchain.go | 17 ++++++++-------- core/state/database.go | 2 +- core/state/dump.go | 43 ++++++++++++++++++++++------------------- core/state/snapshot/account.go | 20 ++++++++++--------- core/state/snapshot/generate.go | 11 ++++++----- core/state/statedb.go | 5 +++-- core/types/block.go | 32 +++++++++++++++++++----------- core/vm/evm.go | 12 +++++++----- eth/api_backend.go | 2 +- eth/backend.go | 2 +- eth/filters/filter.go | 2 +- examples/chain/main.go | 4 +++- examples/counter/counter.sol | 2 +- examples/multicoin/main.go | 4 ++-- internal/ethapi/backend.go | 2 +- plugin/evm/vm.go | 2 +- 18 files changed, 95 insertions(+), 73 deletions(-) (limited to 'core/vm') diff --git a/accounts/external/backend.go b/accounts/external/backend.go index f3744cf..e0fc91d 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -23,12 +23,12 @@ import ( "github.com/ava-labs/coreth/accounts" "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core" ) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index da63673..42e224d 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -262,10 +262,10 @@ func (self *DummyEngine) Finalize( func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - var extdata []byte + var extdata *[]byte if self.cb.OnFinalizeAndAssemble != nil { ret, err := self.cb.OnFinalizeAndAssemble(state, txs) - extdata = ret + extdata = &ret if err != nil { return nil, err } diff --git a/core/blockchain.go b/core/blockchain.go index 82e3b6c..b3a7ffa 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2492,12 +2492,13 @@ func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscr } func (bc *BlockChain) ManualHead(hash common.Hash) error { - block := bc.GetBlockByHash(hash) - if block == nil { - return errors.New("block not found") - } - bc.chainmu.Lock() - defer bc.chainmu.Unlock() - bc.writeHeadBlock(block) - return nil + return bc.FastSyncCommitHead(hash) + //block := bc.GetBlockByHash(hash) + //if block == nil { + // return errors.New("block not found") + //} + //bc.chainmu.Lock() + //defer bc.chainmu.Unlock() + //bc.writeHeadBlock(block) + //return nil } diff --git a/core/state/database.go b/core/state/database.go index a9342f5..385c25d 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -21,8 +21,8 @@ import ( "fmt" "github.com/VictoriaMetrics/fastcache" + "github.com/ava-labs/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" lru "github.com/hashicorp/golang-lru" diff --git a/core/state/dump.go b/core/state/dump.go index 9bb946d..6f09398 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -37,14 +37,15 @@ type DumpCollector interface { // DumpAccount represents an account in the state. type DumpAccount struct { - Balance string `json:"balance"` - Nonce uint64 `json:"nonce"` - Root string `json:"root"` - CodeHash string `json:"codeHash"` - Code string `json:"code,omitempty"` - Storage map[common.Hash]string `json:"storage,omitempty"` - Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode - SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key + Balance string `json:"balance"` + Nonce uint64 `json:"nonce"` + Root string `json:"root"` + CodeHash string `json:"codeHash"` + IsMultiCoin bool `json:"isMultiCoin"` + Code string `json:"code,omitempty"` + Storage map[common.Hash]string `json:"storage,omitempty"` + Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode + SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key } @@ -89,14 +90,15 @@ type iterativeDump struct { // OnAccount implements DumpCollector interface func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) { dumpAccount := &DumpAccount{ - Balance: account.Balance, - Nonce: account.Nonce, - Root: account.Root, - CodeHash: account.CodeHash, - Code: account.Code, - Storage: account.Storage, - SecureKey: account.SecureKey, - Address: nil, + Balance: account.Balance, + Nonce: account.Nonce, + Root: account.Root, + CodeHash: account.CodeHash, + IsMultiCoin: account.IsMultiCoin, + Code: account.Code, + Storage: account.Storage, + SecureKey: account.SecureKey, + Address: nil, } if addr != (common.Address{}) { dumpAccount.Address = &addr @@ -123,10 +125,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, panic(err) } account := DumpAccount{ - Balance: data.Balance.String(), - Nonce: data.Nonce, - Root: common.Bytes2Hex(data.Root[:]), - CodeHash: common.Bytes2Hex(data.CodeHash), + Balance: data.Balance.String(), + Nonce: data.Nonce, + Root: common.Bytes2Hex(data.Root[:]), + CodeHash: common.Bytes2Hex(data.CodeHash), + IsMultiCoin: data.IsMultiCoin, } addrBytes := s.trie.GetKey(it.Key) if addrBytes == nil { diff --git a/core/state/snapshot/account.go b/core/state/snapshot/account.go index b92e942..303c2fc 100644 --- a/core/state/snapshot/account.go +++ b/core/state/snapshot/account.go @@ -29,17 +29,19 @@ import ( // or slim-snapshot format which replaces the empty root and code hash as nil // byte slice. type Account struct { - Nonce uint64 - Balance *big.Int - Root []byte - CodeHash []byte + Nonce uint64 + Balance *big.Int + Root []byte + CodeHash []byte + IsMultiCoin bool } // SlimAccount converts a state.Account content into a slim snapshot account -func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) Account { +func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte, isMultiCoin bool) Account { slim := Account{ - Nonce: nonce, - Balance: balance, + Nonce: nonce, + Balance: balance, + IsMultiCoin: isMultiCoin, } if root != emptyRoot { slim.Root = root[:] @@ -52,8 +54,8 @@ func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []by // SlimAccountRLP converts a state.Account content into a slim snapshot // version RLP encoded. -func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte { - data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash)) +func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte, isMultiCoin bool) []byte { + data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash, isMultiCoin)) if err != nil { panic(err) } diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index dac782f..27f8dfc 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -161,15 +161,16 @@ func (dl *diskLayer) generate(stats *generatorStats) { accountHash := common.BytesToHash(accIt.Key) var acc struct { - Nonce uint64 - Balance *big.Int - Root common.Hash - CodeHash []byte + Nonce uint64 + Balance *big.Int + Root common.Hash + CodeHash []byte + IsMultiCoin bool } if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { log.Crit("Invalid account encountered during snapshot creation", "err", err) } - data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) + data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash, acc.IsMultiCoin) // If the account is not yet in-progress, write it out if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { diff --git a/core/state/statedb.go b/core/state/statedb.go index 805c607..b472bd7 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -270,11 +270,12 @@ func (self *StateDB) GetBalanceMultiCoin(addr common.Address, coinID common.Hash func (self *StateDB) EnableMultiCoin(addr common.Address) error { stateObject := self.GetOrNewStateObject(addr) if stateObject.data.Root != emptyRoot && stateObject.data.Root != zeroRoot { - return errors.New("not a fresh account") + return errors.New(fmt.Sprintf("not a fresh account: %s", stateObject.data.Root.Hex())) } if !stateObject.EnableMultiCoin() { return errors.New("multi-coin mode already enabled") } + log.Debug(fmt.Sprintf("enabled MC for %s", addr.Hex())) return nil } @@ -527,7 +528,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) { // enough to track account updates at commit time, deletions need tracking // at transaction boundary level to ensure we capture state clearing. if s.snap != nil { - s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) + s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash, obj.data.IsMultiCoin) } } diff --git a/core/types/block.go b/core/types/block.go index 37f3464..99d6cc8 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -166,7 +166,7 @@ type Body struct { Transactions []*Transaction Uncles []*Header Version uint32 - ExtData []byte `rlp:"nil"` + ExtData *[]byte `rlp:"nil"` } // Block represents an entire block in the Ethereum blockchain. @@ -175,7 +175,7 @@ type Block struct { uncles []*Header transactions Transactions version uint32 - extdata []byte + extdata *[]byte // caches hash atomic.Value @@ -216,7 +216,7 @@ type myextblock struct { Txs []*Transaction Uncles []*Header Version uint32 - ExtData []byte `rlp:"nil"` + ExtData *[]byte `rlp:"nil"` } // [deprecated by eth/63] @@ -235,7 +235,7 @@ type storageblock struct { // The values of TxHash, UncleHash, ReceiptHash and Bloom in header // are ignored and set to values derived from the given txs, uncles // and receipts. -func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher Hasher, extdata []byte) *Block { +func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher Hasher, extdata *[]byte) *Block { b := &Block{header: CopyHeader(header), td: new(big.Int)} // TODO: panic if len(txs) != len(receipts) @@ -264,8 +264,11 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* } } - b.extdata = make([]byte, len(extdata)) - copy(b.extdata, extdata) + if extdata != nil { + data := make([]byte, len(*extdata)) + b.extdata = &data + copy(*b.extdata, *extdata) + } return b } @@ -321,13 +324,13 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { } func (b *Block) SetExtraData(data []byte) { - b.extdata = data + b.extdata = &data b.header.ExtDataHash = rlpHash(data) b.hash = atomic.Value{} } func (b *Block) ExtraData() []byte { - return b.extdata + return *b.extdata } func (b *Block) SetVersion(ver uint32) { @@ -460,16 +463,23 @@ func (b *Block) WithSeal(header *Header) *Block { } // WithBody returns a new block with the given transaction and uncle contents. -func (b *Block) WithBody(transactions []*Transaction, uncles []*Header, version uint32, extdata []byte) *Block { +func (b *Block) WithBody(transactions []*Transaction, uncles []*Header, version uint32, extdata *[]byte) *Block { + var data *[]byte + if extdata != nil { + _data := make([]byte, len(*extdata)) + data = &_data + } block := &Block{ header: CopyHeader(b.header), transactions: make([]*Transaction, len(transactions)), uncles: make([]*Header, len(uncles)), - extdata: make([]byte, len(extdata)), + extdata: data, version: version, } copy(block.transactions, transactions) - copy(block.extdata, extdata) + if data != nil { + copy(*block.extdata, *extdata) + } for i := range uncles { block.uncles[i] = CopyHeader(uncles[i]) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 85b7ba7..e895211 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -275,17 +275,19 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte return nil, gas, ErrDepth } - mcerr := evm.Context.CanTransferMC(evm.StateDB, caller.Address(), addr, coinID, value2) + // Fail if we're trying to transfer more than the available balance + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + + var to = AccountRef(addr) + mcerr := evm.Context.CanTransferMC(evm.StateDB, caller.Address(), to.Address(), coinID, value2) if mcerr == 1 { return nil, gas, ErrInsufficientBalance } else if mcerr != 0 { return nil, gas, ErrIncompatibleAccount } - // Fail if we're trying to transfer more than the available balance - if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, gas, ErrInsufficientBalance - } snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) diff --git a/eth/api_backend.go b/eth/api_backend.go index 65c3be4..bbc8691 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/coreth/accounts" "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/bloombits" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" @@ -33,7 +34,6 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" diff --git a/eth/backend.go b/eth/backend.go index b1c29a5..728ec4d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -31,6 +31,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/consensus/ethash" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/bloombits" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" @@ -43,7 +44,6 @@ import ( "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" diff --git a/eth/filters/filter.go b/eth/filters/filter.go index e189391..48a76c7 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -22,10 +22,10 @@ import ( "math/big" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/bloombits" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" ) diff --git a/examples/chain/main.go b/examples/chain/main.go index 17003b5..94c5d6b 100644 --- a/examples/chain/main.go +++ b/examples/chain/main.go @@ -138,6 +138,8 @@ func run(config *eth.Config, a1, a2, b1, b2 int) { bob := NewTestChain("bob", config, aliceBlk, bobBlk, aliceAck, bobAck) alice.Start() bob.Start() + log.Info("alice genesis", "block", alice.chain.GetGenesisBlock().Hash().Hex()) + log.Info("bob genesis", "block", bob.chain.GetGenesisBlock().Hash().Hex()) alice.GenRandomTree(a1, a2) log.Info("alice finished generating the tree") //time.Sleep(1 * time.Second) @@ -205,7 +207,7 @@ func main() { ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), - IstanbulBlock: nil, + IstanbulBlock: big.NewInt(0), Ethash: nil, } diff --git a/examples/counter/counter.sol b/examples/counter/counter.sol index b21c3bf..952b925 100644 --- a/examples/counter/counter.sol +++ b/examples/counter/counter.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.6.0; +pragma solidity >=0.6.0; contract Counter { uint256 x; diff --git a/examples/multicoin/main.go b/examples/multicoin/main.go index 1f96647..fc379d4 100644 --- a/examples/multicoin/main.go +++ b/examples/multicoin/main.go @@ -47,12 +47,12 @@ func main() { ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), - IstanbulBlock: nil, + IstanbulBlock: big.NewInt(0), Ethash: nil, } // configure the genesis block - genesisJSON := `{"config":{"chainId":1,"homesteadBlock":0,"daoForkBlock":0,"daoForkSupport":true,"eip150Block":0,"eip150Hash":"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"751a0b96e1042bee789452ecb20253fba40dbe85":{"balance":"0x1000000000000000", "mcbalance": {"0x0000000000000000000000000000000000000000000000000000000000000000": 1000000000000000000}}, "0100000000000000000000000000000000000000": {"code": "0x730000000000000000000000000000000000000000301460806040526004361061004b5760003560e01c80631e01043914610050578063abb24ba014610092578063b6510bb3146100a9575b600080fd5b61007c6004803603602081101561006657600080fd5b8101908080359060200190929190505050610118565b6040518082815260200191505060405180910390f35b81801561009e57600080fd5b506100a761013b565b005b8180156100b557600080fd5b50610116600480360360808110156100cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001909291908035906020019092919050505061013e565b005b60003073ffffffffffffffffffffffffffffffffffffffff1682905d9050919050565b5c565b8373ffffffffffffffffffffffffffffffffffffffff1681836108fc8690811502906040516000604051808303818888878c8af69550505050505015801561018a573d6000803e3d6000fd5b505050505056fea2646970667358221220ed2100d6623a884d196eceefabe5e03da4309a2562bb25262f3874f1acb31cd764736f6c634300060a0033", "balance": "0x0", "mcbalance": {}}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}` + genesisJSON := `{"config":{"chainId":1,"homesteadBlock":0,"daoForkBlock":0,"daoForkSupport":true,"eip150Block":0,"eip150Hash":"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"751a0b96e1042bee789452ecb20253fba40dbe85":{"balance":"0x1000000000000000", "mcbalance": {"0x0000000000000000000000000000000000000000000000000000000000000000": 1000000000000000000}}, "0100000000000000000000000000000000000000": {"code": "0x730000000000000000000000000000000000000000301460806040526004361061004b5760003560e01c80631e01043914610050578063abb24ba014610092578063b6510bb3146100a9575b600080fd5b61007c6004803603602081101561006657600080fd5b8101908080359060200190929190505050610118565b6040518082815260200191505060405180910390f35b81801561009e57600080fd5b506100a761013b565b005b8180156100b557600080fd5b50610116600480360360808110156100cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001909291908035906020019092919050505061013e565b005b60003073ffffffffffffffffffffffffffffffffffffffff168290cd9050919050565bce565b8373ffffffffffffffffffffffffffffffffffffffff1681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801561018a573d6000803e3d6000fd5b505050505056fea26469706673582212205c4d96aa2a6488c426daa9567616a383dd6156eb3fe84f4905239179e553fc0f64736f6c634300060a0033", "balance": "0x0", "mcbalance": {}}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}` mcAbiJSON := `[{"inputs":[],"name":"enableMultiCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"coinid","type":"uint256"}],"name":"getBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"coinid","type":"uint256"},{"internalType":"uint256","name":"amount2","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"}]` genesisKey := "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1" diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index cee6b57..f89a3aa 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -24,13 +24,13 @@ import ( "github.com/ava-labs/coreth/accounts" "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/bloombits" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 5c5a5b5..993727e 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -22,9 +22,9 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" ethcrypto "github.com/ethereum/go-ethereum/crypto" -- cgit v1.2.3-70-g09d2