Merge branch 'master' into lean_reindex

This commit is contained in:
Martin Holst Swende 2020-05-10 18:50:26 +02:00 committed by GitHub
commit 87b1ffaf36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
216 changed files with 11661 additions and 3554 deletions

View file

@ -5,7 +5,7 @@ jobs:
allow_failures: allow_failures:
- stage: build - stage: build
os: osx os: osx
go: 1.13.x go: 1.14.x
env: env:
- azure-osx - azure-osx
- azure-ios - azure-ios
@ -16,7 +16,7 @@ jobs:
- stage: lint - stage: lint
os: linux os: linux
dist: xenial dist: xenial
go: 1.13.x go: 1.14.x
env: env:
- lint - lint
git: git:
@ -44,12 +44,24 @@ jobs:
- go run build/ci.go install - go run build/ci.go install
- go run build/ci.go test -coverage $TEST_PACKAGES - go run build/ci.go test -coverage $TEST_PACKAGES
- stage: build
os: linux
dist: xenial
go: 1.13.x
env:
- GO111MODULE=on
script:
- go run build/ci.go install
- go run build/ci.go test -coverage $TEST_PACKAGES
# These are the latest Go versions. # These are the latest Go versions.
- stage: build - stage: build
os: linux os: linux
arch: amd64 arch: amd64
dist: xenial dist: xenial
go: 1.13.x go: 1.14.x
env:
- GO111MODULE=on
script: script:
- go run build/ci.go install - go run build/ci.go install
- go run build/ci.go test -coverage $TEST_PACKAGES - go run build/ci.go test -coverage $TEST_PACKAGES
@ -59,7 +71,9 @@ jobs:
os: linux os: linux
arch: arm64 arch: arm64
dist: xenial dist: xenial
go: 1.13.x go: 1.14.x
env:
- GO111MODULE=on
script: script:
- go run build/ci.go install - go run build/ci.go install
- go run build/ci.go test -coverage $TEST_PACKAGES - go run build/ci.go test -coverage $TEST_PACKAGES
@ -67,7 +81,9 @@ jobs:
- stage: build - stage: build
os: osx os: osx
osx_image: xcode11.3 osx_image: xcode11.3
go: 1.13.x go: 1.14.x
env:
- GO111MODULE=on
script: script:
- echo "Increase the maximum number of open file descriptors on macOS" - echo "Increase the maximum number of open file descriptors on macOS"
- NOFILE=20480 - NOFILE=20480
@ -86,9 +102,10 @@ jobs:
if: type = push if: type = push
os: linux os: linux
dist: xenial dist: xenial
go: 1.13.x go: 1.14.x
env: env:
- ubuntu-ppa - ubuntu-ppa
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
addons: addons:
@ -102,7 +119,7 @@ jobs:
- python-paramiko - python-paramiko
script: script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts - echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- go run build/ci.go debsrc -goversion 1.13.8 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>" - go run build/ci.go debsrc -goversion 1.14.2 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
# This builder does the Linux Azure uploads # This builder does the Linux Azure uploads
- stage: build - stage: build
@ -110,9 +127,10 @@ jobs:
os: linux os: linux
dist: xenial dist: xenial
sudo: required sudo: required
go: 1.13.x go: 1.14.x
env: env:
- azure-linux - azure-linux
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
addons: addons:
@ -146,9 +164,10 @@ jobs:
dist: xenial dist: xenial
services: services:
- docker - docker
go: 1.13.x go: 1.14.x
env: env:
- azure-linux-mips - azure-linux-mips
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:
@ -189,10 +208,11 @@ jobs:
env: env:
- azure-android - azure-android
- maven-android - maven-android
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
before_install: before_install:
- curl https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz | tar -xz - curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -xz
- export PATH=`pwd`/go/bin:$PATH - export PATH=`pwd`/go/bin:$PATH
- export GOROOT=`pwd`/go - export GOROOT=`pwd`/go
- export GOPATH=$HOME/go - export GOPATH=$HOME/go
@ -210,11 +230,12 @@ jobs:
- stage: build - stage: build
if: type = push if: type = push
os: osx os: osx
go: 1.13.x go: 1.14.x
env: env:
- azure-osx - azure-osx
- azure-ios - azure-ios
- cocoapods-ios - cocoapods-ios
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:
@ -241,9 +262,10 @@ jobs:
if: type = cron if: type = cron
os: linux os: linux
dist: xenial dist: xenial
go: 1.13.x go: 1.14.x
env: env:
- azure-purge - azure-purge
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:

View file

@ -1,5 +1,5 @@
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.13-alpine as builder FROM golang:1.14-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git RUN apk add --no-cache make gcc musl-dev linux-headers git

View file

@ -1,5 +1,5 @@
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.13-alpine as builder FROM golang:1.14-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git RUN apk add --no-cache make gcc musl-dev linux-headers git

View file

@ -16,7 +16,7 @@ archives are published at https://geth.ethereum.org/downloads/.
For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki.
Building `geth` requires both a Go (version 1.10 or later) and a C compiler. You can install Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install
them using your favourite package manager. Once the dependencies are installed, run them using your favourite package manager. Once the dependencies are installed, run
```shell ```shell
@ -72,7 +72,7 @@ This command will:
This tool is optional and if you leave it out you can always attach to an already running This tool is optional and if you leave it out you can always attach to an already running
`geth` instance with `geth attach`. `geth` instance with `geth attach`.
### A Full node on the Ethereum test network ### A Full node on the Görli test network
Transitioning towards developers, if you'd like to play around with creating Ethereum Transitioning towards developers, if you'd like to play around with creating Ethereum
contracts, you almost certainly would like to do that without any real money involved until contracts, you almost certainly would like to do that without any real money involved until
@ -81,23 +81,24 @@ network, you want to join the **test** network with your node, which is fully eq
the main network, but with play-Ether only. the main network, but with play-Ether only.
```shell ```shell
$ geth --testnet console $ geth --goerli console
``` ```
The `console` subcommand has the exact same meaning as above and they are equally The `console` subcommand has the exact same meaning as above and they are equally
useful on the testnet too. Please see above for their explanations if you've skipped here. useful on the testnet too. Please, see above for their explanations if you've skipped here.
Specifying the `--testnet` flag, however, will reconfigure your `geth` instance a bit: Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit:
* Instead of connecting the main Ethereum network, the client will connect to the Görli
test network, which uses different P2P bootnodes, different network IDs and genesis
states.
* Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth` * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth`
will nest itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on
Linux). Note, on OSX and Linux this also means that attaching to a running testnet node Linux). Note, on OSX and Linux this also means that attaching to a running testnet node
requires the use of a custom endpoint since `geth attach` will try to attach to a requires the use of a custom endpoint since `geth attach` will try to attach to a
production node endpoint by default. E.g. production node endpoint by default, e.g.,
`geth attach <datadir>/testnet/geth.ipc`. Windows users are not affected by `geth attach <datadir>/goerli/geth.ipc`. Windows users are not affected by
this. this.
* Instead of connecting the main Ethereum network, the client will connect to the test
network, which uses different P2P bootnodes, different network IDs and genesis states.
*Note: Although there are some internal protective measures to prevent transactions from *Note: Although there are some internal protective measures to prevent transactions from
crossing over between the main network and test network, you should make sure to always crossing over between the main network and test network, you should make sure to always
@ -107,17 +108,26 @@ accounts available between them.*
### Full node on the Rinkeby test network ### Full node on the Rinkeby test network
The above test network is a cross-client one based on the ethash proof-of-work consensus Go Ethereum also supports connecting to the older proof-of-authority based test network
algorithm. As such, it has certain extra overhead and is more susceptible to reorganization called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community.
attacks due to the network's low difficulty/security. Go Ethereum also supports connecting
to a proof-of-authority based test network called [*Rinkeby*](https://www.rinkeby.io)
(operated by members of the community). This network is lighter, more secure, but is only
supported by go-ethereum.
```shell ```shell
$ geth --rinkeby console $ geth --rinkeby console
``` ```
### Full node on the Ropsten test network
In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The
Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such,
it has certain extra overhead and is more susceptible to reorganization attacks due to the
network's low difficulty/security.
```shell
$ geth --ropsten console
```
*Note: Older Geth configurations store the Ropsten database in the `testnet` subdirectory.*
### Configuration ### Configuration
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a As an alternative to passing the numerous flags to the `geth` binary, you can also pass a

View file

@ -19,10 +19,12 @@ package abi
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
// The ABI holds information about a contract's context and available // The ABI holds information about a contract's context and available
@ -32,6 +34,12 @@ type ABI struct {
Constructor Method Constructor Method
Methods map[string]Method Methods map[string]Method
Events map[string]Event Events map[string]Event
// Additional "special" functions introduced in solidity v0.6.0.
// It's separated from the original default fallback. Each contract
// can only define one fallback and receive function.
Fallback Method // Note it's also used to represent legacy fallback before v0.6.0
Receive Method
} }
// JSON returns a parsed ABI interface and error if it failed. // JSON returns a parsed ABI interface and error if it failed.
@ -42,7 +50,6 @@ func JSON(reader io.Reader) (ABI, error) {
if err := dec.Decode(&abi); err != nil { if err := dec.Decode(&abi); err != nil {
return ABI{}, err return ABI{}, err
} }
return abi, nil return abi, nil
} }
@ -70,7 +77,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
return nil, err return nil, err
} }
// Pack up the method ID too if not a constructor and return // Pack up the method ID too if not a constructor and return
return append(method.ID(), arguments...), nil return append(method.ID, arguments...), nil
} }
// Unpack output in v according to the abi specification // Unpack output in v according to the abi specification
@ -108,13 +115,22 @@ func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte)
// UnmarshalJSON implements json.Unmarshaler interface // UnmarshalJSON implements json.Unmarshaler interface
func (abi *ABI) UnmarshalJSON(data []byte) error { func (abi *ABI) UnmarshalJSON(data []byte) error {
var fields []struct { var fields []struct {
Type string Type string
Name string Name string
Constant bool Inputs []Argument
Outputs []Argument
// Status indicator which can be: "pure", "view",
// "nonpayable" or "payable".
StateMutability string StateMutability string
Anonymous bool
Inputs []Argument // Deprecated Status indicators, but removed in v0.6.0.
Outputs []Argument Constant bool // True if function is either pure or view
Payable bool // True if function is payable
// Event relevant indicator represents the event is
// declared as anonymous.
Anonymous bool
} }
if err := json.Unmarshal(data, &fields); err != nil { if err := json.Unmarshal(data, &fields); err != nil {
return err return err
@ -124,44 +140,67 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
for _, field := range fields { for _, field := range fields {
switch field.Type { switch field.Type {
case "constructor": case "constructor":
abi.Constructor = Method{ abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil)
Inputs: field.Inputs, case "function":
name := abi.overloadedMethodName(field.Name)
abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs)
case "fallback":
// New introduced function type in v0.6.0, check more detail
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
if abi.HasFallback() {
return errors.New("only single fallback is allowed")
} }
// empty defaults to function according to the abi spec abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil)
case "function", "": case "receive":
name := field.Name // New introduced function type in v0.6.0, check more detail
_, ok := abi.Methods[name] // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
for idx := 0; ok; idx++ { if abi.HasReceive() {
name = fmt.Sprintf("%s%d", field.Name, idx) return errors.New("only single receive is allowed")
_, ok = abi.Methods[name]
} }
isConst := field.Constant || field.StateMutability == "pure" || field.StateMutability == "view" if field.StateMutability != "payable" {
abi.Methods[name] = Method{ return errors.New("the statemutability of receive can only be payable")
Name: name,
RawName: field.Name,
Const: isConst,
Inputs: field.Inputs,
Outputs: field.Outputs,
} }
abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil)
case "event": case "event":
name := field.Name name := abi.overloadedEventName(field.Name)
_, ok := abi.Events[name] abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs)
for idx := 0; ok; idx++ { default:
name = fmt.Sprintf("%s%d", field.Name, idx) return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name)
_, ok = abi.Events[name]
}
abi.Events[name] = Event{
Name: name,
RawName: field.Name,
Anonymous: field.Anonymous,
Inputs: field.Inputs,
}
} }
} }
return nil return nil
} }
// overloadedMethodName returns the next available name for a given function.
// Needed since solidity allows for function overload.
//
// e.g. if the abi contains Methods send, send1
// overloadedMethodName would return send2 for input send.
func (abi *ABI) overloadedMethodName(rawName string) string {
name := rawName
_, ok := abi.Methods[name]
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
_, ok = abi.Methods[name]
}
return name
}
// overloadedEventName returns the next available name for a given event.
// Needed since solidity allows for event overload.
//
// e.g. if the abi contains events received, received1
// overloadedEventName would return received2 for input received.
func (abi *ABI) overloadedEventName(rawName string) string {
name := rawName
_, ok := abi.Events[name]
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
_, ok = abi.Events[name]
}
return name
}
// MethodById looks up a method by the 4-byte id // MethodById looks up a method by the 4-byte id
// returns nil if none found // returns nil if none found
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
@ -169,7 +208,7 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata))
} }
for _, method := range abi.Methods { for _, method := range abi.Methods {
if bytes.Equal(method.ID(), sigdata[:4]) { if bytes.Equal(method.ID, sigdata[:4]) {
return &method, nil return &method, nil
} }
} }
@ -180,9 +219,41 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
// ABI and returns nil if none found. // ABI and returns nil if none found.
func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
for _, event := range abi.Events { for _, event := range abi.Events {
if bytes.Equal(event.ID().Bytes(), topic.Bytes()) { if bytes.Equal(event.ID.Bytes(), topic.Bytes()) {
return &event, nil return &event, nil
} }
} }
return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
} }
// HasFallback returns an indicator whether a fallback function is included.
func (abi *ABI) HasFallback() bool {
return abi.Fallback.Type == Fallback
}
// HasReceive returns an indicator whether a receive function is included.
func (abi *ABI) HasReceive() bool {
return abi.Receive.Type == Receive
}
// revertSelector is a special function selector for revert reason unpacking.
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
// the provided revert reason is abi-encoded as if it were a call to a function
// `Error(string)`. So it's a special tool for it.
func UnpackRevert(data []byte) (string, error) {
if len(data) < 4 {
return "", errors.New("invalid data for unpacking")
}
if !bytes.Equal(data[:4], revertSelector) {
return "", errors.New("invalid data for unpacking")
}
var reason string
typ, _ := NewType("string", "", nil)
if err := (Arguments{{Type: typ}}).Unpack(&reason, data[4:]); err != nil {
return "", err
}
return reason, nil
}

View file

@ -19,6 +19,7 @@ package abi
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"reflect" "reflect"
@ -26,57 +27,99 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "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"
) )
const jsondata = ` const jsondata = `
[ [
{ "type" : "function", "name" : "balance", "constant" : true }, { "type" : "function", "name" : "", "stateMutability" : "view" },
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } { "type" : "function", "name" : "balance", "stateMutability" : "view" },
{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
{ "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
{ "type" : "function", "name" : "string", "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
{ "type" : "function", "name" : "bool", "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
{ "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
{ "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
{ "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
{ "type" : "function", "name" : "int8", "inputs" : [ { "name" : "inputs", "type" : "int8" } ] },
{ "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
{ "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
{ "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
{ "type" : "function", "name" : "slice256", "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "sliceAddress", "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
{ "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
{ "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
{ "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
{ "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] },
{ "type" : "function", "name" : "receive", "inputs" : [ { "name" : "memo", "type" : "bytes" }], "outputs" : [], "payable" : true, "stateMutability" : "payable" },
{ "type" : "function", "name" : "fixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "fixedArrBytes", "stateMutability" : "view", "inputs" : [ { "name" : "bytes", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "mixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" } ] },
{ "type" : "function", "name" : "doubleFixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] },
{ "type" : "function", "name" : "multipleMixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
]` ]`
const jsondata2 = ` var (
[ Uint256, _ = NewType("uint256", "", nil)
{ "type" : "function", "name" : "balance", "constant" : true }, Uint32, _ = NewType("uint32", "", nil)
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, Uint16, _ = NewType("uint16", "", nil)
{ "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, String, _ = NewType("string", "", nil)
{ "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, Bool, _ = NewType("bool", "", nil)
{ "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, Bytes, _ = NewType("bytes", "", nil)
{ "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, Address, _ = NewType("address", "", nil)
{ "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, Uint64Arr, _ = NewType("uint64[]", "", nil)
{ "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, AddressArr, _ = NewType("address[]", "", nil)
{ "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, Int8, _ = NewType("int8", "", nil)
{ "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, // Special types for testing
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, Uint32Arr2, _ = NewType("uint32[2]", "", nil)
{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] }, Uint64Arr2, _ = NewType("uint64[2]", "", nil)
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] }, Uint256Arr, _ = NewType("uint256[]", "", nil)
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }, Uint256Arr2, _ = NewType("uint256[2]", "", nil)
{ "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] }, Uint256Arr3, _ = NewType("uint256[3]", "", nil)
{ "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] }, Uint256ArrNested, _ = NewType("uint256[2][2]", "", nil)
{ "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] } Uint8ArrNested, _ = NewType("uint8[][2]", "", nil)
]` Uint8SliceNested, _ = NewType("uint8[][]", "", nil)
)
var methods = map[string]Method{
"": NewMethod("", "", Function, "view", false, false, nil, nil),
"balance": NewMethod("balance", "balance", Function, "view", false, false, nil, nil),
"send": NewMethod("send", "send", Function, "", false, false, []Argument{{"amount", Uint256, false}}, nil),
"test": NewMethod("test", "test", Function, "", false, false, []Argument{{"number", Uint32, false}}, nil),
"string": NewMethod("string", "string", Function, "", false, false, []Argument{{"inputs", String, false}}, nil),
"bool": NewMethod("bool", "bool", Function, "", false, false, []Argument{{"inputs", Bool, false}}, nil),
"address": NewMethod("address", "address", Function, "", false, false, []Argument{{"inputs", Address, false}}, nil),
"uint64[]": NewMethod("uint64[]", "uint64[]", Function, "", false, false, []Argument{{"inputs", Uint64Arr, false}}, nil),
"uint64[2]": NewMethod("uint64[2]", "uint64[2]", Function, "", false, false, []Argument{{"inputs", Uint64Arr2, false}}, nil),
"int8": NewMethod("int8", "int8", Function, "", false, false, []Argument{{"inputs", Int8, false}}, nil),
"foo": NewMethod("foo", "foo", Function, "", false, false, []Argument{{"inputs", Uint32, false}}, nil),
"bar": NewMethod("bar", "bar", Function, "", false, false, []Argument{{"inputs", Uint32, false}, {"string", Uint16, false}}, nil),
"slice": NewMethod("slice", "slice", Function, "", false, false, []Argument{{"inputs", Uint32Arr2, false}}, nil),
"slice256": NewMethod("slice256", "slice256", Function, "", false, false, []Argument{{"inputs", Uint256Arr2, false}}, nil),
"sliceAddress": NewMethod("sliceAddress", "sliceAddress", Function, "", false, false, []Argument{{"inputs", AddressArr, false}}, nil),
"sliceMultiAddress": NewMethod("sliceMultiAddress", "sliceMultiAddress", Function, "", false, false, []Argument{{"a", AddressArr, false}, {"b", AddressArr, false}}, nil),
"nestedArray": NewMethod("nestedArray", "nestedArray", Function, "", false, false, []Argument{{"a", Uint256ArrNested, false}, {"b", AddressArr, false}}, nil),
"nestedArray2": NewMethod("nestedArray2", "nestedArray2", Function, "", false, false, []Argument{{"a", Uint8ArrNested, false}}, nil),
"nestedSlice": NewMethod("nestedSlice", "nestedSlice", Function, "", false, false, []Argument{{"a", Uint8SliceNested, false}}, nil),
"receive": NewMethod("receive", "receive", Function, "payable", false, true, []Argument{{"memo", Bytes, false}}, []Argument{}),
"fixedArrStr": NewMethod("fixedArrStr", "fixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}}, nil),
"fixedArrBytes": NewMethod("fixedArrBytes", "fixedArrBytes", Function, "view", false, false, []Argument{{"bytes", Bytes, false}, {"fixedArr", Uint256Arr2, false}}, nil),
"mixedArrStr": NewMethod("mixedArrStr", "mixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}}, nil),
"doubleFixedArrStr": NewMethod("doubleFixedArrStr", "doubleFixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"fixedArr2", Uint256Arr3, false}}, nil),
"multipleMixedArrStr": NewMethod("multipleMixedArrStr", "multipleMixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}, {"fixedArr2", Uint256Arr3, false}}, nil),
}
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
Uint256, _ := NewType("uint256", "", nil) abi := ABI{
exp := ABI{ Methods: methods,
Methods: map[string]Method{
"balance": {
"balance", "balance", true, nil, nil,
},
"send": {
"send", "send", false, []Argument{
{"amount", Uint256, false},
}, nil,
},
},
} }
abi, err := JSON(strings.NewReader(jsondata)) exp, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
// deep equal fails for some reason
for name, expM := range exp.Methods { for name, expM := range exp.Methods {
gotM, exist := abi.Methods[name] gotM, exist := abi.Methods[name]
if !exist { if !exist {
@ -98,8 +141,58 @@ func TestReader(t *testing.T) {
} }
} }
func TestInvalidABI(t *testing.T) {
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
_, err := JSON(strings.NewReader(json))
if err == nil {
t.Fatal("invalid json should produce error")
}
json2 := `[{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "typ" : "uint256" } ] }]`
_, err = JSON(strings.NewReader(json2))
if err == nil {
t.Fatal("invalid json should produce error")
}
}
// TestConstructor tests a constructor function.
// The test is based on the following contract:
// contract TestConstructor {
// constructor(uint256 a, uint256 b) public{}
// }
func TestConstructor(t *testing.T) {
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
// Test from JSON
abi, err := JSON(strings.NewReader(json))
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(abi.Constructor, method) {
t.Error("Missing expected constructor")
}
// Test pack/unpack
packed, err := abi.Pack("", big.NewInt(1), big.NewInt(2))
if err != nil {
t.Error(err)
}
v := struct {
A *big.Int
B *big.Int
}{new(big.Int), new(big.Int)}
//abi.Unpack(&v, "", packed)
if err := abi.Constructor.Inputs.Unpack(&v, packed); err != nil {
t.Error(err)
}
if !reflect.DeepEqual(v.A, big.NewInt(1)) {
t.Error("Unable to pack/unpack from constructor")
}
if !reflect.DeepEqual(v.B, big.NewInt(2)) {
t.Error("Unable to pack/unpack from constructor")
}
}
func TestTestNumbers(t *testing.T) { func TestTestNumbers(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -135,60 +228,22 @@ func TestTestNumbers(t *testing.T) {
} }
} }
func TestTestString(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Fatal(err)
}
if _, err := abi.Pack("string", "hello world"); err != nil {
t.Error(err)
}
}
func TestTestBool(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Fatal(err)
}
if _, err := abi.Pack("bool", true); err != nil {
t.Error(err)
}
}
func TestTestSlice(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2))
if err != nil {
t.Fatal(err)
}
slice := make([]uint64, 2)
if _, err := abi.Pack("uint64[2]", slice); err != nil {
t.Error(err)
}
if _, err := abi.Pack("uint64[]", slice); err != nil {
t.Error(err)
}
}
func TestMethodSignature(t *testing.T) { func TestMethodSignature(t *testing.T) {
String, _ := NewType("string", "", nil) m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil)
m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
exp := "foo(string,string)" exp := "foo(string,string)"
if m.Sig() != exp { if m.Sig != exp {
t.Error("signature mismatch", exp, "!=", m.Sig()) t.Error("signature mismatch", exp, "!=", m.Sig)
} }
idexp := crypto.Keccak256([]byte(exp))[:4] idexp := crypto.Keccak256([]byte(exp))[:4]
if !bytes.Equal(m.ID(), idexp) { if !bytes.Equal(m.ID, idexp) {
t.Errorf("expected ids to match %x != %x", m.ID(), idexp) t.Errorf("expected ids to match %x != %x", m.ID, idexp)
} }
uintt, _ := NewType("uint256", "", nil) m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", Uint256, false}}, nil)
m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil}
exp = "foo(uint256)" exp = "foo(uint256)"
if m.Sig() != exp { if m.Sig != exp {
t.Error("signature mismatch", exp, "!=", m.Sig()) t.Error("signature mismatch", exp, "!=", m.Sig)
} }
// Method with tuple arguments // Method with tuple arguments
@ -204,10 +259,10 @@ func TestMethodSignature(t *testing.T) {
{Name: "y", Type: "int256"}, {Name: "y", Type: "int256"},
}}, }},
}) })
m = Method{"foo", "foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil} m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil)
exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)"
if m.Sig() != exp { if m.Sig != exp {
t.Error("signature mismatch", exp, "!=", m.Sig()) t.Error("signature mismatch", exp, "!=", m.Sig)
} }
} }
@ -219,12 +274,12 @@ func TestOverloadedMethodSignature(t *testing.T) {
} }
check := func(name string, expect string, method bool) { check := func(name string, expect string, method bool) {
if method { if method {
if abi.Methods[name].Sig() != expect { if abi.Methods[name].Sig != expect {
t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig()) t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig)
} }
} else { } else {
if abi.Events[name].Sig() != expect { if abi.Events[name].Sig != expect {
t.Fatalf("The signature of overloaded event mismatch, want %s, have %s", expect, abi.Events[name].Sig()) t.Fatalf("The signature of overloaded event mismatch, want %s, have %s", expect, abi.Events[name].Sig)
} }
} }
} }
@ -235,7 +290,7 @@ func TestOverloadedMethodSignature(t *testing.T) {
} }
func TestMultiPack(t *testing.T) { func TestMultiPack(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -400,15 +455,7 @@ func TestInputVariableInputLength(t *testing.T) {
} }
func TestInputFixedArrayAndVariableInputLength(t *testing.T) { func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
const definition = `[ abi, err := JSON(strings.NewReader(jsondata))
{ "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] },
{ "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] },
{ "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
]`
abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -555,7 +602,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
strvalue = common.RightPadBytes([]byte(strin), 32) strvalue = common.RightPadBytes([]byte(strin), 32)
fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32) fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32) fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
dynarroffset = U256(big.NewInt(int64(256 + ((len(strin)/32)+1)*32))) dynarroffset = math.U256Bytes(big.NewInt(int64(256 + ((len(strin)/32)+1)*32)))
dynarrlength = make([]byte, 32) dynarrlength = make([]byte, 32)
dynarrlength[31] = byte(len(dynarrin)) dynarrlength[31] = byte(len(dynarrin))
dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32) dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32)
@ -582,7 +629,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
} }
func TestDefaultFunctionParsing(t *testing.T) { func TestDefaultFunctionParsing(t *testing.T) {
const definition = `[{ "name" : "balance" }]` const definition = `[{ "name" : "balance", "type" : "function" }]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
@ -602,8 +649,6 @@ func TestBareEvents(t *testing.T) {
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] } { "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
]` ]`
arg0, _ := NewType("uint256", "", nil)
arg1, _ := NewType("address", "", nil)
tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}}) tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
expectedEvents := map[string]struct { expectedEvents := map[string]struct {
@ -613,12 +658,12 @@ func TestBareEvents(t *testing.T) {
"balance": {false, nil}, "balance": {false, nil},
"anon": {true, nil}, "anon": {true, nil},
"args": {false, []Argument{ "args": {false, []Argument{
{Name: "arg0", Type: arg0, Indexed: false}, {Name: "arg0", Type: Uint256, Indexed: false},
{Name: "arg1", Type: arg1, Indexed: true}, {Name: "arg1", Type: Address, Indexed: true},
}}, }},
"tuple": {false, []Argument{ "tuple": {false, []Argument{
{Name: "t", Type: tuple, Indexed: false}, {Name: "t", Type: tuple, Indexed: false},
{Name: "arg1", Type: arg1, Indexed: true}, {Name: "arg1", Type: Address, Indexed: true},
}}, }},
} }
@ -891,45 +936,25 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) {
} }
func TestABI_MethodById(t *testing.T) { func TestABI_MethodById(t *testing.T) {
const abiJSON = `[ abi, err := JSON(strings.NewReader(jsondata))
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
{"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]},
{"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]},
{"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]},
{"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]},
{"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]},
{"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]},
{"type":"function","name":"balance","constant":true},
{"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]},
{"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]},
{"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]},
{"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]},
{"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]},
{"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]},
{"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]},
{"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]},
{"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]},
{"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]},
{"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]},
{"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]},
{"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]}
]
`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for name, m := range abi.Methods { for name, m := range abi.Methods {
a := fmt.Sprintf("%v", m) a := fmt.Sprintf("%v", m)
m2, err := abi.MethodById(m.ID()) m2, err := abi.MethodById(m.ID)
if err != nil { if err != nil {
t.Fatalf("Failed to look up ABI method: %v", err) t.Fatalf("Failed to look up ABI method: %v", err)
} }
b := fmt.Sprintf("%v", m2) b := fmt.Sprintf("%v", m2)
if a != b { if a != b {
t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID()) t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID)
} }
} }
// test unsuccessful lookups
if _, err = abi.MethodById(crypto.Keccak256()); err == nil {
t.Error("Expected error: no method with this id")
}
// Also test empty // Also test empty
if _, err := abi.MethodById([]byte{0x00}); err == nil { if _, err := abi.MethodById([]byte{0x00}); err == nil {
t.Errorf("Expected error, too short to decode data") t.Errorf("Expected error, too short to decode data")
@ -995,8 +1020,8 @@ func TestABI_EventById(t *testing.T) {
t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum) t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum)
} }
if event.ID() != topicID { if event.ID != topicID {
t.Errorf("Event id %s does not match topic %s, test #%d", event.ID().Hex(), topicID.Hex(), testnum) t.Errorf("Event id %s does not match topic %s, test #%d", event.ID.Hex(), topicID.Hex(), testnum)
} }
unknowntopicID := crypto.Keccak256Hash([]byte("unknownEvent")) unknowntopicID := crypto.Keccak256Hash([]byte("unknownEvent"))
@ -1010,26 +1035,6 @@ func TestABI_EventById(t *testing.T) {
} }
} }
func TestDuplicateMethodNames(t *testing.T) {
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
t.Fatal(err)
}
if _, ok := contractAbi.Methods["transfer"]; !ok {
t.Fatalf("Could not find original method")
}
if _, ok := contractAbi.Methods["transfer0"]; !ok {
t.Fatalf("Could not find duplicate method")
}
if _, ok := contractAbi.Methods["transfer1"]; !ok {
t.Fatalf("Could not find duplicate method")
}
if _, ok := contractAbi.Methods["transfer2"]; ok {
t.Fatalf("Should not have found extra method")
}
}
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name // TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
// conflict and that the second transfer method will be renamed transfer1. // conflict and that the second transfer method will be renamed transfer1.
func TestDoubleDuplicateMethodNames(t *testing.T) { func TestDoubleDuplicateMethodNames(t *testing.T) {
@ -1051,3 +1056,87 @@ func TestDoubleDuplicateMethodNames(t *testing.T) {
t.Fatalf("Should not have found extra method") t.Fatalf("Should not have found extra method")
} }
} }
// TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name
// conflict and that the second send event will be renamed send1.
// The test runs the abi of the following contract.
// contract DuplicateEvent {
// event send(uint256 a);
// event send0();
// event send();
// }
func TestDoubleDuplicateEventNames(t *testing.T) {
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
t.Fatal(err)
}
if _, ok := contractAbi.Events["send"]; !ok {
t.Fatalf("Could not find original event")
}
if _, ok := contractAbi.Events["send0"]; !ok {
t.Fatalf("Could not find duplicate event")
}
if _, ok := contractAbi.Events["send1"]; !ok {
t.Fatalf("Could not find duplicate event")
}
if _, ok := contractAbi.Events["send2"]; ok {
t.Fatalf("Should not have found extra event")
}
}
// TestUnnamedEventParam checks that an event with unnamed parameters is
// correctly handled
// The test runs the abi of the following contract.
// contract TestEvent {
// event send(uint256, uint256);
// }
func TestUnnamedEventParam(t *testing.T) {
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
t.Fatal(err)
}
event, ok := contractAbi.Events["send"]
if !ok {
t.Fatalf("Could not find event")
}
if event.Inputs[0].Name != "arg0" {
t.Fatalf("Could not find input")
}
if event.Inputs[1].Name != "arg1" {
t.Fatalf("Could not find input")
}
}
func TestUnpackRevert(t *testing.T) {
t.Parallel()
var cases = []struct {
input string
expect string
expectErr error
}{
{"", "", errors.New("invalid data for unpacking")},
{"08c379a1", "", errors.New("invalid data for unpacking")},
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
}
for index, c := range cases {
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
got, err := UnpackRevert(common.Hex2Bytes(c.input))
if c.expectErr != nil {
if err == nil {
t.Fatalf("Expected non-nil error")
}
if err.Error() != c.expectErr.Error() {
t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err)
}
return
}
if c.expect != got {
t.Fatalf("Output mismatch, want %v, got %v", c.expect, got)
}
})
}
}

View file

@ -59,18 +59,6 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
func (arguments Arguments) LengthNonIndexed() int {
out := 0
for _, arg := range arguments {
if !arg.Indexed {
out++
}
}
return out
}
// NonIndexed returns the arguments with indexed arguments filtered out // NonIndexed returns the arguments with indexed arguments filtered out
func (arguments Arguments) NonIndexed() Arguments { func (arguments Arguments) NonIndexed() Arguments {
var ret []Argument var ret []Argument
@ -92,9 +80,8 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
if len(data) == 0 { if len(data) == 0 {
if len(arguments) != 0 { if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
} else {
return nil // Nothing to unmarshal, return
} }
return nil // Nothing to unmarshal, return
} }
// make sure the passed value is arguments pointer // make sure the passed value is arguments pointer
if reflect.Ptr != reflect.ValueOf(v).Kind() { if reflect.Ptr != reflect.ValueOf(v).Kind() {
@ -104,6 +91,9 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
if err != nil { if err != nil {
return err return err
} }
if len(marshalledValues) == 0 {
return fmt.Errorf("abi: Unpack(no-values unmarshalled %T)", v)
}
if arguments.isTuple() { if arguments.isTuple() {
return arguments.unpackTuple(v, marshalledValues) return arguments.unpackTuple(v, marshalledValues)
} }
@ -112,18 +102,24 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value // UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
// Make sure map is not nil
if v == nil {
return fmt.Errorf("abi: cannot unpack into a nil map")
}
if len(data) == 0 { if len(data) == 0 {
if len(arguments) != 0 { if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
} else {
return nil // Nothing to unmarshal, return
} }
return nil // Nothing to unmarshal, return
} }
marshalledValues, err := arguments.UnpackValues(data) marshalledValues, err := arguments.UnpackValues(data)
if err != nil { if err != nil {
return err return err
} }
return arguments.unpackIntoMap(v, marshalledValues) for i, arg := range arguments.NonIndexed() {
v[arg.Name] = marshalledValues[i]
}
return nil
} }
// unpack sets the unmarshalled value to go format. // unpack sets the unmarshalled value to go format.
@ -195,25 +191,13 @@ func unpack(t *Type, dst interface{}, src interface{}) error {
return nil return nil
} }
// unpackIntoMap unpacks marshalledValues into the provided map[string]interface{}
func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error {
// Make sure map is not nil
if v == nil {
return fmt.Errorf("abi: cannot unpack into a nil map")
}
for i, arg := range arguments.NonIndexed() {
v[arg.Name] = marshalledValues[i]
}
return nil
}
// unpackAtomic unpacks ( hexdata -> go ) a single value // unpackAtomic unpacks ( hexdata -> go ) a single value
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error {
if arguments.LengthNonIndexed() == 0 { nonIndexedArgs := arguments.NonIndexed()
if len(nonIndexedArgs) == 0 {
return nil return nil
} }
argument := arguments.NonIndexed()[0] argument := nonIndexedArgs[0]
elem := reflect.ValueOf(v).Elem() elem := reflect.ValueOf(v).Elem()
if elem.Kind() == reflect.Struct && argument.Type.T != TupleTy { if elem.Kind() == reflect.Struct && argument.Type.T != TupleTy {
@ -233,30 +217,28 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interfac
// unpackTuple unpacks ( hexdata -> go ) a batch of values. // unpackTuple unpacks ( hexdata -> go ) a batch of values.
func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error {
var ( var (
value = reflect.ValueOf(v).Elem() value = reflect.ValueOf(v).Elem()
typ = value.Type() typ = value.Type()
kind = value.Kind() kind = value.Kind()
nonIndexedArgs = arguments.NonIndexed()
) )
if err := requireUnpackKind(value, typ, kind, arguments); err != nil { if err := requireUnpackKind(value, len(nonIndexedArgs), arguments); err != nil {
return err return err
} }
// If the interface is a struct, get of abi->struct_field mapping // If the interface is a struct, get of abi->struct_field mapping
var abi2struct map[string]string var abi2struct map[string]string
if kind == reflect.Struct { if kind == reflect.Struct {
var ( argNames := make([]string, len(nonIndexedArgs))
argNames []string for i, arg := range nonIndexedArgs {
err error argNames[i] = arg.Name
)
for _, arg := range arguments.NonIndexed() {
argNames = append(argNames, arg.Name)
} }
abi2struct, err = mapArgNamesToStructFields(argNames, value) var err error
if err != nil { if abi2struct, err = mapArgNamesToStructFields(argNames, value); err != nil {
return err return err
} }
} }
for i, arg := range arguments.NonIndexed() { for i, arg := range nonIndexedArgs {
switch kind { switch kind {
case reflect.Struct: case reflect.Struct:
field := value.FieldByName(abi2struct[arg.Name]) field := value.FieldByName(abi2struct[arg.Name])
@ -289,10 +271,11 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
// without supplying a struct to unpack into. Instead, this method returns a list containing the // without supplying a struct to unpack into. Instead, this method returns a list containing the
// values. An atomic argument will be a list with one element. // values. An atomic argument will be a list with one element.
func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
retval := make([]interface{}, 0, arguments.LengthNonIndexed()) nonIndexedArgs := arguments.NonIndexed()
retval := make([]interface{}, 0, len(nonIndexedArgs))
virtualArgs := 0 virtualArgs := 0
for index, arg := range arguments.NonIndexed() { for index, arg := range nonIndexedArgs {
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) marshalledValue, err := ToGoType((index+virtualArgs)*32, arg.Type, data)
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
// If we have a static array, like [3]uint256, these are coded as // If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256. // just like uint256,uint256,uint256.

View file

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -49,7 +50,6 @@ var (
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
errBlockDoesNotExist = errors.New("block does not exist in blockchain") errBlockDoesNotExist = errors.New("block does not exist in blockchain")
errTransactionDoesNotExist = errors.New("transaction does not exist") errTransactionDoesNotExist = errors.New("transaction does not exist")
errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
) )
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
@ -349,8 +349,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
if err != nil { if err != nil {
return nil, err return nil, err
} }
rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
return rval, err if err != nil {
return nil, err
}
return res.Return(), nil
} }
// PendingCallContract executes a contract call on the pending state. // PendingCallContract executes a contract call on the pending state.
@ -359,8 +362,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu
defer b.mu.Unlock() defer b.mu.Unlock()
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
return rval, err if err != nil {
return nil, err
}
return res.Return(), nil
} }
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
@ -398,22 +404,33 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
cap = hi cap = hi
// Create a helper to check if a gas allowance results in an executable transaction // Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool { executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
call.Gas = gas call.Gas = gas
snapshot := b.pendingState.Snapshot() snapshot := b.pendingState.Snapshot()
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
b.pendingState.RevertToSnapshot(snapshot) b.pendingState.RevertToSnapshot(snapshot)
if err != nil || failed { if err != nil {
return false if err == core.ErrIntrinsicGas {
return true, nil, nil // Special case, raise gas limit
}
return true, nil, err // Bail out
} }
return true return res.Failed(), res, nil
} }
// Execute the binary search and hone in on an executable gas limit // Execute the binary search and hone in on an executable gas limit
for lo+1 < hi { for lo+1 < hi {
mid := (hi + lo) / 2 mid := (hi + lo) / 2
if !executable(mid) { failed, _, err := executable(mid)
// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigned. Return the error directly, don't struggle any more
if err != nil {
return 0, err
}
if failed {
lo = mid lo = mid
} else { } else {
hi = mid hi = mid
@ -421,8 +438,25 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
} }
// Reject the transaction as invalid if it still fails at the highest allowance // Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap { if hi == cap {
if !executable(hi) { failed, result, err := executable(hi)
return 0, errGasEstimationFailed if err != nil {
return 0, err
}
if failed {
if result != nil && result.Err != vm.ErrOutOfGas {
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
if len(result.Revert()) > 0 {
ret, err := abi.UnpackRevert(result.Revert())
if err != nil {
errMsg += fmt.Sprintf(" (%#x)", result.Revert())
} else {
errMsg += fmt.Sprintf(" (%s)", ret)
}
}
return 0, errors.New(errMsg)
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
} }
} }
return hi, nil return hi, nil
@ -430,7 +464,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
// callContract implements common code between normal and pending contract calls. // callContract implements common code between normal and pending contract calls.
// state is modified during execution, make sure to copy it if necessary. // state is modified during execution, make sure to copy it if necessary.
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) {
// Ensure message is initialized properly. // Ensure message is initialized properly.
if call.GasPrice == nil { if call.GasPrice == nil {
call.GasPrice = big.NewInt(1) call.GasPrice = big.NewInt(1)

View file

@ -19,6 +19,7 @@ package backends
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"math/big" "math/big"
"strings" "strings"
"testing" "testing"
@ -356,25 +357,112 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) {
} }
func TestSimulatedBackend_EstimateGas(t *testing.T) { func TestSimulatedBackend_EstimateGas(t *testing.T) {
sim := NewSimulatedBackend( /*
core.GenesisAlloc{}, 10000000, pragma solidity ^0.6.4;
) contract GasEstimation {
function PureRevert() public { revert(); }
function Revert() public { revert("revert reason");}
function OOG() public { for (uint i = 0; ; i++) {}}
function Assert() public { assert(false);}
function Valid() public {}
}*/
const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033"
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
opts := bind.NewKeyedTransactor(key)
sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000)
defer sim.Close() defer sim.Close()
bgCtx := context.Background()
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
gas, err := sim.EstimateGas(bgCtx, ethereum.CallMsg{ parsed, _ := abi.JSON(strings.NewReader(contractAbi))
From: testAddr, contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim)
To: &testAddr, sim.Commit()
Value: big.NewInt(1000),
Data: []byte{}, var cases = []struct {
}) name string
if err != nil { message ethereum.CallMsg
t.Errorf("could not estimate gas: %v", err) expect uint64
expectError error
}{
{"plain transfer(valid)", ethereum.CallMsg{
From: addr,
To: &addr,
Gas: 0,
GasPrice: big.NewInt(0),
Value: big.NewInt(1),
Data: nil,
}, params.TxGas, nil},
{"plain transfer(invalid)", ethereum.CallMsg{
From: addr,
To: &contractAddr,
Gas: 0,
GasPrice: big.NewInt(0),
Value: big.NewInt(1),
Data: nil,
}, 0, errors.New("always failing transaction (execution reverted)")},
{"Revert", ethereum.CallMsg{
From: addr,
To: &contractAddr,
Gas: 0,
GasPrice: big.NewInt(0),
Value: nil,
Data: common.Hex2Bytes("d8b98391"),
}, 0, errors.New("always failing transaction (execution reverted) (revert reason)")},
{"PureRevert", ethereum.CallMsg{
From: addr,
To: &contractAddr,
Gas: 0,
GasPrice: big.NewInt(0),
Value: nil,
Data: common.Hex2Bytes("aa8b1d30"),
}, 0, errors.New("always failing transaction (execution reverted)")},
{"OOG", ethereum.CallMsg{
From: addr,
To: &contractAddr,
Gas: 100000,
GasPrice: big.NewInt(0),
Value: nil,
Data: common.Hex2Bytes("50f6fe34"),
}, 0, errors.New("gas required exceeds allowance (100000)")},
{"Assert", ethereum.CallMsg{
From: addr,
To: &contractAddr,
Gas: 100000,
GasPrice: big.NewInt(0),
Value: nil,
Data: common.Hex2Bytes("b9b046f9"),
}, 0, errors.New("always failing transaction (invalid opcode: opcode 0xfe not defined)")},
{"Valid", ethereum.CallMsg{
From: addr,
To: &contractAddr,
Gas: 100000,
GasPrice: big.NewInt(0),
Value: nil,
Data: common.Hex2Bytes("e09fface"),
}, 21275, nil},
} }
for _, c := range cases {
if gas != params.TxGas { got, err := sim.EstimateGas(context.Background(), c.message)
t.Errorf("expected 21000 gas cost for a transaction got %v", gas) if c.expectError != nil {
if err == nil {
t.Fatalf("Expect error, got nil")
}
if c.expectError.Error() != err.Error() {
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
}
continue
}
if got != c.expect {
t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got)
}
} }
} }

View file

@ -171,12 +171,24 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
if err != nil { if err != nil {
return nil, err return nil, err
} }
// todo(rjl493456442) check the method is payable or not,
// reject invalid transaction at the first place
return c.transact(opts, &c.address, input) return c.transact(opts, &c.address, input)
} }
// RawTransact initiates a transaction with the given raw calldata as the input.
// It's usually used to initiates transaction for invoking **Fallback** function.
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
// todo(rjl493456442) check the method is payable or not,
// reject invalid transaction at the first place
return c.transact(opts, &c.address, calldata)
}
// Transfer initiates a plain transaction to move funds to the contract, calling // Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available. // its default method if one is available.
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
// todo(rjl493456442) check the payable fallback or receive is defined
// or not, reject invalid transaction at the first place
return c.transact(opts, &c.address, nil) return c.transact(opts, &c.address, nil)
} }
@ -252,7 +264,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
opts = new(FilterOpts) opts = new(FilterOpts)
} }
// Append the event selector to the query parameters and construct the topic set // Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...) query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
topics, err := makeTopics(query...) topics, err := makeTopics(query...)
if err != nil { if err != nil {
@ -301,7 +313,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
opts = new(WatchOpts) opts = new(WatchOpts)
} }
// Append the event selector to the query parameters and construct the topic set // Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...) query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
topics, err := makeTopics(query...) topics, err := makeTopics(query...)
if err != nil { if err != nil {

View file

@ -17,9 +17,9 @@
package bind_test package bind_test
import ( import (
"bytes"
"context" "context"
"math/big" "math/big"
"reflect"
"strings" "strings"
"testing" "testing"
@ -88,51 +88,23 @@ const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
hash := crypto.Keccak256Hash([]byte("testName")) hash := crypto.Keccak256Hash([]byte("testName"))
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), common.HexToHash("0x0"),
Topics: []common.Hash{ hash,
common.HexToHash("0x0"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x0"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x0"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"name": hash, "name": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["name"] != expectedReceivedMap["name"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
@ -141,51 +113,23 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
hash := crypto.Keccak256Hash(sliceBytes) hash := crypto.Keccak256Hash(sliceBytes)
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), common.HexToHash("0x0"),
Topics: []common.Hash{ hash,
common.HexToHash("0x0"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x0"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x0"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"names": hash, "names": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["names"] != expectedReceivedMap["names"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
@ -194,51 +138,23 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
hash := crypto.Keccak256Hash(arrBytes) hash := crypto.Keccak256Hash(arrBytes)
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), common.HexToHash("0x0"),
Topics: []common.Hash{ hash,
common.HexToHash("0x0"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x0"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x0"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"addresses": hash, "addresses": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["addresses"] != expectedReceivedMap["addresses"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
@ -249,99 +165,72 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
functionTyBytes := append(addrBytes, functionSelector...) functionTyBytes := append(addrBytes, functionSelector...)
var functionTy [24]byte var functionTy [24]byte
copy(functionTy[:], functionTyBytes[0:24]) copy(functionTy[:], functionTyBytes[0:24])
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
Topics: []common.Hash{ common.BytesToHash(functionTyBytes),
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
common.BytesToHash(functionTyBytes),
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"function": functionTy, "function": functionTy,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["function"] != expectedReceivedMap["function"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
byts := []byte{1, 2, 3, 4, 5} bytes := []byte{1, 2, 3, 4, 5}
hash := crypto.Keccak256Hash(byts) hash := crypto.Keccak256Hash(bytes)
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
Topics: []common.Hash{ hash,
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"content": hash, "content": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
}
func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) {
received := make(map[string]interface{})
if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil {
t.Error(err) t.Error(err)
} }
if len(receivedMap) != 4 { if len(received) != len(expected) {
t.Fatal("unpacked map expected to have length 4") t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected))
} }
if receivedMap["content"] != expectedReceivedMap["content"] { for name, elem := range expected {
t.Error("unpacked map does not match expected map") if !reflect.DeepEqual(elem, received[name]) {
} t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name])
if receivedMap["sender"] != expectedReceivedMap["sender"] { }
t.Error("unpacked map does not match expected map") }
} }
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map") func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
} return types.Log{
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { Address: common.HexToAddress("0x0"),
t.Error("unpacked map does not match expected map") Topics: topics,
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: txHash,
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
} }

View file

@ -77,6 +77,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
calls = make(map[string]*tmplMethod) calls = make(map[string]*tmplMethod)
transacts = make(map[string]*tmplMethod) transacts = make(map[string]*tmplMethod)
events = make(map[string]*tmplEvent) events = make(map[string]*tmplEvent)
fallback *tmplMethod
receive *tmplMethod
// identifiers are used to detect duplicated identifier of function // identifiers are used to detect duplicated identifier of function
// and event. For all calls, transacts and events, abigen will generate // and event. For all calls, transacts and events, abigen will generate
@ -92,7 +94,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
// Ensure there is no duplicated identifier // Ensure there is no duplicated identifier
var identifiers = callIdentifiers var identifiers = callIdentifiers
if !original.Const { if !original.IsConstant() {
identifiers = transactIdentifiers identifiers = transactIdentifiers
} }
if identifiers[normalizedName] { if identifiers[normalizedName] {
@ -121,7 +123,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
} }
} }
// Append the methods to the call or transact lists // Append the methods to the call or transact lists
if original.Const { if original.IsConstant() {
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
} else { } else {
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
@ -156,7 +158,13 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// Append the event to the accumulator list // Append the event to the accumulator list
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
} }
// Add two special fallback functions if they exist
if evmABI.HasFallback() {
fallback = &tmplMethod{Original: evmABI.Fallback}
}
if evmABI.HasReceive() {
receive = &tmplMethod{Original: evmABI.Receive}
}
// There is no easy way to pass arbitrary java objects to the Go side. // There is no easy way to pass arbitrary java objects to the Go side.
if len(structs) > 0 && lang == LangJava { if len(structs) > 0 && lang == LangJava {
return "", errors.New("java binding for tuple arguments is not supported yet") return "", errors.New("java binding for tuple arguments is not supported yet")
@ -169,6 +177,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
Constructor: evmABI.Constructor, Constructor: evmABI.Constructor,
Calls: calls, Calls: calls,
Transacts: transacts, Transacts: transacts,
Fallback: fallback,
Receive: receive,
Events: events, Events: events,
Libraries: make(map[string]string), Libraries: make(map[string]string),
} }
@ -619,11 +629,22 @@ func formatMethod(method abi.Method, structs map[string]*tmplStruct) string {
outputs[i] += fmt.Sprintf(" %v", output.Name) outputs[i] += fmt.Sprintf(" %v", output.Name)
} }
} }
constant := "" // Extract meaningful state mutability of solidity method.
if method.Const { // If it's default value, never print it.
constant = "constant " state := method.StateMutability
if state == "nonpayable" {
state = ""
} }
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", ")) if state != "" {
state = state + " "
}
identity := fmt.Sprintf("function %v", method.RawName)
if method.Type == abi.Fallback {
identity = "fallback"
} else if method.Type == abi.Receive {
identity = "receive"
}
return fmt.Sprintf("%s(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", "))
} }
// formatEvent transforms raw event representation into a user friendly one. // formatEvent transforms raw event representation into a user friendly one.

File diff suppressed because one or more lines are too long

View file

@ -35,6 +35,8 @@ type tmplContract struct {
Constructor abi.Method // Contract constructor for deploy parametrization Constructor abi.Method // Contract constructor for deploy parametrization
Calls map[string]*tmplMethod // Contract calls that only read state data Calls map[string]*tmplMethod // Contract calls that only read state data
Transacts map[string]*tmplMethod // Contract calls that write state data Transacts map[string]*tmplMethod // Contract calls that write state data
Fallback *tmplMethod // Additional special fallback function
Receive *tmplMethod // Additional special receive function
Events map[string]*tmplEvent // Contract events accessors Events map[string]*tmplEvent // Contract events accessors
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
Library bool // Indicator whether the contract is a library Library bool // Indicator whether the contract is a library
@ -101,7 +103,6 @@ var (
_ = big.NewInt _ = big.NewInt
_ = strings.NewReader _ = strings.NewReader
_ = ethereum.NotFound _ = ethereum.NotFound
_ = abi.U256
_ = bind.Bind _ = bind.Bind
_ = common.Big1 _ = common.Big1
_ = types.BloomLookup _ = types.BloomLookup
@ -351,6 +352,52 @@ var (
} }
{{end}} {{end}}
{{if .Fallback}}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
return _{{$contract.Type}}.contract.RawTransact(opts, calldata)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
}
{{end}}
{{if .Receive}}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
}
{{end}}
{{range .Events}} {{range .Events}}
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract. // {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct { type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
@ -577,7 +624,7 @@ import java.util.*;
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
// //
// Solidity: {{.Original.String}} // Solidity: {{.Original.String}}
public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else if eq (len .Normalized.Outputs) 0}}void{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); {{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
{{end}} {{end}}
@ -611,6 +658,24 @@ import java.util.*;
return this.Contract.transact(opts, "{{.Original.Name}}" , args); return this.Contract.transact(opts, "{{.Original.Name}}" , args);
} }
{{end}} {{end}}
{{if .Fallback}}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception {
return this.Contract.rawTransact(opts, calldata);
}
{{end}}
{{if .Receive}}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
public Transaction Receive(TransactOpts opts) throws Exception {
return this.Contract.rawTransact(opts, null);
}
{{end}}
} }
{{end}} {{end}}
` `

View file

@ -49,17 +49,13 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
topic[common.HashLength-1] = 1 topic[common.HashLength-1] = 1
} }
case int8: case int8:
blob := big.NewInt(int64(rule)).Bytes() copy(topic[:], genIntType(int64(rule), 1))
copy(topic[common.HashLength-len(blob):], blob)
case int16: case int16:
blob := big.NewInt(int64(rule)).Bytes() copy(topic[:], genIntType(int64(rule), 2))
copy(topic[common.HashLength-len(blob):], blob)
case int32: case int32:
blob := big.NewInt(int64(rule)).Bytes() copy(topic[:], genIntType(int64(rule), 4))
copy(topic[common.HashLength-len(blob):], blob)
case int64: case int64:
blob := big.NewInt(rule).Bytes() copy(topic[:], genIntType(rule, 8))
copy(topic[common.HashLength-len(blob):], blob)
case uint8: case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes() blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob) copy(topic[common.HashLength-len(blob):], blob)
@ -103,151 +99,75 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
return topics, nil return topics, nil
} }
// Big batch of reflect types for topic reconstruction. func genIntType(rule int64, size uint) []byte {
var ( var topic [common.HashLength]byte
reflectHash = reflect.TypeOf(common.Hash{}) if rule < 0 {
reflectAddress = reflect.TypeOf(common.Address{}) // if a rule is negative, we need to put it into two's complement.
reflectBigInt = reflect.TypeOf(new(big.Int)) // extended to common.Hashlength bytes.
) topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
}
for i := uint(0); i < size; i++ {
topic[common.HashLength-i-1] = byte(rule >> (i * 8))
}
return topic[:]
}
// parseTopics converts the indexed topic fields into actual log field values. // parseTopics converts the indexed topic fields into actual log field values.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error { func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
// Sanity check that the fields and topics match up return parseTopicWithSetter(fields, topics,
if len(fields) != len(topics) { func(arg abi.Argument, reconstr interface{}) {
return errors.New("topic/field count mismatch") field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
} field.Set(reflect.ValueOf(reconstr))
// Iterate over all the fields and reconstruct them from topics })
for _, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
// Try to parse the topic back into the fields based on primitive types
switch field.Kind() {
case reflect.Bool:
if topics[0][common.HashLength-1] == 1 {
field.Set(reflect.ValueOf(true))
}
case reflect.Int8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int8(num.Int64())))
case reflect.Int16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int16(num.Int64())))
case reflect.Int32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int32(num.Int64())))
case reflect.Int64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Int64()))
case reflect.Uint8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint8(num.Uint64())))
case reflect.Uint16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint16(num.Uint64())))
case reflect.Uint32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint32(num.Uint64())))
case reflect.Uint64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Uint64()))
default:
// Ran out of plain primitive types, try custom types
switch field.Type() {
case reflectHash: // Also covers all dynamic types
field.Set(reflect.ValueOf(topics[0]))
case reflectAddress:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
field.Set(reflect.ValueOf(addr))
case reflectBigInt:
num := new(big.Int).SetBytes(topics[0][:])
if arg.Type.T == abi.IntTy {
if num.Cmp(abi.MaxInt256) > 0 {
num.Add(abi.MaxUint256, big.NewInt(0).Neg(num))
num.Add(num, big.NewInt(1))
num.Neg(num)
}
}
field.Set(reflect.ValueOf(num))
default:
// Ran out of custom types, try the crazies
switch {
// static byte array
case arg.Type.T == abi.FixedBytesTy:
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
default:
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
}
}
}
topics = topics[1:]
}
return nil
} }
// parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs // parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs
func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error { func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error {
return parseTopicWithSetter(fields, topics,
func(arg abi.Argument, reconstr interface{}) {
out[arg.Name] = reconstr
})
}
// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the
// provided set function.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter func(abi.Argument, interface{})) error {
// Sanity check that the fields and topics match up // Sanity check that the fields and topics match up
if len(fields) != len(topics) { if len(fields) != len(topics) {
return errors.New("topic/field count mismatch") return errors.New("topic/field count mismatch")
} }
// Iterate over all the fields and reconstruct them from topics // Iterate over all the fields and reconstruct them from topics
for _, arg := range fields { for i, arg := range fields {
if !arg.Indexed { if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction") return errors.New("non-indexed field in topic reconstruction")
} }
var reconstr interface{}
switch arg.Type.T { switch arg.Type.T {
case abi.BoolTy: case abi.TupleTy:
out[arg.Name] = topics[0][common.HashLength-1] == 1 return errors.New("tuple type in topic reconstruction")
case abi.IntTy, abi.UintTy:
out[arg.Name] = abi.ReadInteger(arg.Type.T, arg.Type.Kind, topics[0].Bytes())
case abi.AddressTy:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
out[arg.Name] = addr
case abi.HashTy:
out[arg.Name] = topics[0]
case abi.FixedBytesTy:
array, err := abi.ReadFixedBytes(arg.Type, topics[0].Bytes())
if err != nil {
return err
}
out[arg.Name] = array
case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy: case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy:
// Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash
// whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash
out[arg.Name] = topics[0] reconstr = topics[i]
case abi.FunctionTy: case abi.FunctionTy:
if garbage := binary.BigEndian.Uint64(topics[0][0:8]); garbage != 0 { if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 {
return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[0].Bytes()) return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes())
} }
var tmp [24]byte var tmp [24]byte
copy(tmp[:], topics[0][8:32]) copy(tmp[:], topics[i][8:32])
out[arg.Name] = tmp reconstr = tmp
default: // Not handling tuples default:
return fmt.Errorf("unsupported indexed type: %v", arg.Type) var err error
reconstr, err = abi.ToGoType(0, arg.Type, topics[i].Bytes())
if err != nil {
return err
}
} }
// Use the setter function to store the value
topics = topics[1:] setter(arg, reconstr)
} }
return nil return nil

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
func TestMakeTopics(t *testing.T) { func TestMakeTopics(t *testing.T) {
@ -41,6 +42,80 @@ func TestMakeTopics(t *testing.T) {
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
false, false,
}, },
{
"support common hash types in topics",
args{[][]interface{}{{common.Hash{1, 2, 3, 4, 5}}}},
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
false,
},
{
"support address types in topics",
args{[][]interface{}{{common.Address{1, 2, 3, 4, 5}}}},
[][]common.Hash{{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5}}},
false,
},
{
"support *big.Int types in topics",
args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}},
[][]common.Hash{{common.Hash{128}}},
false,
},
{
"support boolean types in topics",
args{[][]interface{}{
{true},
{false},
}},
[][]common.Hash{
{common.Hash{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, 1}},
{common.Hash{0}},
},
false,
},
{
"support int/uint(8/16/32/64) types in topics",
args{[][]interface{}{
{int8(-2)},
{int16(-3)},
{int32(-4)},
{int64(-5)},
{int8(1)},
{int16(256)},
{int32(65536)},
{int64(4294967296)},
{uint8(1)},
{uint16(256)},
{uint32(65536)},
{uint64(4294967296)},
}},
[][]common.Hash{
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254}},
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253}},
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252}},
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251}},
{common.Hash{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, 1}},
{common.Hash{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, 1, 0}},
{common.Hash{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, 1, 0, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}},
{common.Hash{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, 1}},
{common.Hash{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, 1, 0}},
{common.Hash{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, 1, 0, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}},
},
false,
},
{
"support string types in topics",
args{[][]interface{}{{"hello world"}}},
[][]common.Hash{{crypto.Keccak256Hash([]byte("hello world"))}},
false,
},
{
"support byte slice types in topics",
args{[][]interface{}{{[]byte{1, 2, 3}}}},
[][]common.Hash{{crypto.Keccak256Hash([]byte{1, 2, 3})}},
false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -84,6 +159,7 @@ func setupTopicsTests() []topicTest {
bytesType, _ := abi.NewType("bytes5", "", nil) bytesType, _ := abi.NewType("bytes5", "", nil)
int8Type, _ := abi.NewType("int8", "", nil) int8Type, _ := abi.NewType("int8", "", nil)
int256Type, _ := abi.NewType("int256", "", nil) int256Type, _ := abi.NewType("int256", "", nil)
tupleType, _ := abi.NewType("tuple(int256,int8)", "", nil)
tests := []topicTest{ tests := []topicTest{
{ {
@ -145,6 +221,21 @@ func setupTopicsTests() []topicTest {
}, },
wantErr: false, wantErr: false,
}, },
{
name: "tuple(int256, int8)",
args: args{
createObj: func() interface{} { return nil },
resultObj: func() interface{} { return nil },
resultMap: func() map[string]interface{} { return make(map[string]interface{}) },
fields: abi.Arguments{abi.Argument{
Name: "tupletype",
Type: tupleType,
Indexed: true,
}},
topics: []common.Hash{},
},
wantErr: true,
},
} }
return tests return tests

View file

@ -39,23 +39,21 @@ func formatSliceString(kind reflect.Kind, sliceSize int) string {
// type in t. // type in t.
func sliceTypeCheck(t Type, val reflect.Value) error { func sliceTypeCheck(t Type, val reflect.Value) error {
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
return typeErr(formatSliceString(t.Kind, t.Size), val.Type()) return typeErr(formatSliceString(t.getType().Kind(), t.Size), val.Type())
} }
if t.T == ArrayTy && val.Len() != t.Size { if t.T == ArrayTy && val.Len() != t.Size {
return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) return typeErr(formatSliceString(t.Elem.getType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
} }
if t.Elem.T == SliceTy { if t.Elem.T == SliceTy || t.Elem.T == ArrayTy {
if val.Len() > 0 { if val.Len() > 0 {
return sliceTypeCheck(*t.Elem, val.Index(0)) return sliceTypeCheck(*t.Elem, val.Index(0))
} }
} else if t.Elem.T == ArrayTy {
return sliceTypeCheck(*t.Elem, val.Index(0))
} }
if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.Kind { if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.getType().Kind() {
return typeErr(formatSliceString(t.Elem.Kind, t.Size), val.Type()) return typeErr(formatSliceString(t.Elem.getType().Kind(), t.Size), val.Type())
} }
return nil return nil
} }
@ -68,10 +66,10 @@ func typeCheck(t Type, value reflect.Value) error {
} }
// Check base type validity. Element types will be checked later on. // Check base type validity. Element types will be checked later on.
if t.Kind != value.Kind() { if t.getType().Kind() != value.Kind() {
return typeErr(t.Kind, value.Kind()) return typeErr(t.getType().Kind(), value.Kind())
} else if t.T == FixedBytesTy && t.Size != value.Len() { } else if t.T == FixedBytesTy && t.Size != value.Len() {
return typeErr(t.Type, value.Type()) return typeErr(t.getType(), value.Type())
} else { } else {
return nil return nil
} }

View file

@ -42,36 +42,59 @@ type Event struct {
RawName string RawName string
Anonymous bool Anonymous bool
Inputs Arguments Inputs Arguments
str string
// Sig contains the string signature according to the ABI spec.
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
// Please note that "int" is substitute for its canonical representation "int256"
Sig string
// ID returns the canonical representation of the event's signature used by the
// abi definition to identify event names and types.
ID common.Hash
}
// NewEvent creates a new Event.
// It sanitizes the input arguments to remove unnamed arguments.
// It also precomputes the id, signature and string representation
// of the event.
func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event {
// sanitize inputs to remove inputs without names
// and precompute string and sig representation.
names := make([]string, len(inputs))
types := make([]string, len(inputs))
for i, input := range inputs {
if input.Name == "" {
inputs[i] = Argument{
Name: fmt.Sprintf("arg%d", i),
Indexed: input.Indexed,
Type: input.Type,
}
} else {
inputs[i] = input
}
// string representation
names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name)
if input.Indexed {
names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name)
}
// sig representation
types[i] = input.Type.String()
}
str := fmt.Sprintf("event %v(%v)", rawName, strings.Join(names, ", "))
sig := fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
id := common.BytesToHash(crypto.Keccak256([]byte(sig)))
return Event{
Name: name,
RawName: rawName,
Anonymous: anonymous,
Inputs: inputs,
str: str,
Sig: sig,
ID: id,
}
} }
func (e Event) String() string { func (e Event) String() string {
inputs := make([]string, len(e.Inputs)) return e.str
for i, input := range e.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
if input.Indexed {
inputs[i] = fmt.Sprintf("%v indexed %v", input.Type, input.Name)
}
}
return fmt.Sprintf("event %v(%v)", e.RawName, strings.Join(inputs, ", "))
}
// Sig returns the event string signature according to the ABI spec.
//
// Example
//
// event foo(uint32 a, int b) = "foo(uint32,int256)"
//
// Please note that "int" is substitute for its canonical representation "int256"
func (e Event) Sig() string {
types := make([]string, len(e.Inputs))
for i, input := range e.Inputs {
types[i] = input.Type.String()
}
return fmt.Sprintf("%v(%v)", e.RawName, strings.Join(types, ","))
}
// ID returns the canonical representation of the event's signature used by the
// abi definition to identify event names and types.
func (e Event) ID() common.Hash {
return common.BytesToHash(crypto.Keccak256([]byte(e.Sig())))
} }

View file

@ -104,8 +104,8 @@ func TestEventId(t *testing.T) {
} }
for name, event := range abi.Events { for name, event := range abi.Events {
if event.ID() != test.expectations[name] { if event.ID != test.expectations[name] {
t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID()) t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID)
} }
} }
} }

View file

@ -23,6 +23,24 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
// FunctionType represents different types of functions a contract might have.
type FunctionType int
const (
// Constructor represents the constructor of the contract.
// The constructor function is called while deploying a contract.
Constructor FunctionType = iota
// Fallback represents the fallback function.
// This function is executed if no other function matches the given function
// signature and no receive function is specified.
Fallback
// Receive represents the receive function.
// This function is executed on plain Ether transfers.
Receive
// Function represents a normal function.
Function
)
// Method represents a callable given a `Name` and whether the method is a constant. // Method represents a callable given a `Name` and whether the method is a constant.
// If the method is `Const` no transaction needs to be created for this // If the method is `Const` no transaction needs to be created for this
// particular Method call. It can easily be simulated using a local VM. // particular Method call. It can easily be simulated using a local VM.
@ -41,50 +59,109 @@ type Method struct {
// * foo(uint,uint) // * foo(uint,uint)
// The method name of the first one will be resolved as foo while the second one // The method name of the first one will be resolved as foo while the second one
// will be resolved as foo0. // will be resolved as foo0.
Name string Name string
// RawName is the raw method name parsed from ABI. RawName string // RawName is the raw method name parsed from ABI
RawName string
Const bool // Type indicates whether the method is a
// special fallback introduced in solidity v0.6.0
Type FunctionType
// StateMutability indicates the mutability state of method,
// the default value is nonpayable. It can be empty if the abi
// is generated by legacy compiler.
StateMutability string
// Legacy indicators generated by compiler before v0.6.0
Constant bool
Payable bool
Inputs Arguments Inputs Arguments
Outputs Arguments Outputs Arguments
str string
// Sig returns the methods string signature according to the ABI spec.
// e.g. function foo(uint32 a, int b) = "foo(uint32,int256)"
// Please note that "int" is substitute for its canonical representation "int256"
Sig string
// ID returns the canonical representation of the method's signature used by the
// abi definition to identify method names and types.
ID []byte
} }
// Sig returns the methods string signature according to the ABI spec. // NewMethod creates a new Method.
// // A method should always be created using NewMethod.
// Example // It also precomputes the sig representation and the string representation
// // of the method.
// function foo(uint32 a, int b) = "foo(uint32,int256)" func NewMethod(name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments) Method {
// var (
// Please note that "int" is substitute for its canonical representation "int256" types = make([]string, len(inputs))
func (method Method) Sig() string { inputNames = make([]string, len(inputs))
types := make([]string, len(method.Inputs)) outputNames = make([]string, len(outputs))
for i, input := range method.Inputs { )
for i, input := range inputs {
inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
types[i] = input.Type.String() types[i] = input.Type.String()
} }
return fmt.Sprintf("%v(%v)", method.RawName, strings.Join(types, ",")) for i, output := range outputs {
outputNames[i] = output.Type.String()
if len(output.Name) > 0 {
outputNames[i] += fmt.Sprintf(" %v", output.Name)
}
}
// calculate the signature and method id. Note only function
// has meaningful signature and id.
var (
sig string
id []byte
)
if funType == Function {
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
id = crypto.Keccak256([]byte(sig))[:4]
}
// Extract meaningful state mutability of solidity method.
// If it's default value, never print it.
state := mutability
if state == "nonpayable" {
state = ""
}
if state != "" {
state = state + " "
}
identity := fmt.Sprintf("function %v", rawName)
if funType == Fallback {
identity = "fallback"
} else if funType == Receive {
identity = "receive"
} else if funType == Constructor {
identity = "constructor"
}
str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", "))
return Method{
Name: name,
RawName: rawName,
Type: funType,
StateMutability: mutability,
Constant: isConst,
Payable: isPayable,
Inputs: inputs,
Outputs: outputs,
str: str,
Sig: sig,
ID: id,
}
} }
func (method Method) String() string { func (method Method) String() string {
inputs := make([]string, len(method.Inputs)) return method.str
for i, input := range method.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
}
outputs := make([]string, len(method.Outputs))
for i, output := range method.Outputs {
outputs[i] = output.Type.String()
if len(output.Name) > 0 {
outputs[i] += fmt.Sprintf(" %v", output.Name)
}
}
constant := ""
if method.Const {
constant = "constant "
}
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
} }
// ID returns the canonical representation of the method's signature used by the // IsConstant returns the indicator whether the method is read-only.
// abi definition to identify method names and types. func (method Method) IsConstant() bool {
func (method Method) ID() []byte { return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant
return crypto.Keccak256([]byte(method.Sig()))[:4] }
// IsPayable returns the indicator whether the method can process
// plain ether transfers.
func (method Method) IsPayable() bool {
return method.StateMutability == "payable" || method.Payable
} }

View file

@ -23,13 +23,15 @@ import (
const methoddata = ` const methoddata = `
[ [
{"type": "function", "name": "balance", "constant": true }, {"type": "function", "name": "balance", "stateMutability": "view"},
{"type": "function", "name": "send", "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }]}, {"type": "function", "name": "send", "inputs": [{ "name": "amount", "type": "uint256" }]},
{"type": "function", "name": "transfer", "constant": false, "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]}, {"type": "function", "name": "transfer", "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]},
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"} {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"stateMutability":"nonpayable","type":"fallback"},
{"stateMutability":"payable","type":"receive"}
]` ]`
func TestMethodString(t *testing.T) { func TestMethodString(t *testing.T) {
@ -39,7 +41,7 @@ func TestMethodString(t *testing.T) {
}{ }{
{ {
method: "balance", method: "balance",
expectation: "function balance() constant returns()", expectation: "function balance() view returns()",
}, },
{ {
method: "send", method: "send",
@ -65,6 +67,14 @@ func TestMethodString(t *testing.T) {
method: "complexTuple", method: "complexTuple",
expectation: "function complexTuple((uint256,uint256)[5][] a) returns()", expectation: "function complexTuple((uint256,uint256)[5][] a) returns()",
}, },
{
method: "fallback",
expectation: "fallback() returns()",
},
{
method: "receive",
expectation: "receive() payable returns()",
},
} }
abi, err := JSON(strings.NewReader(methoddata)) abi, err := JSON(strings.NewReader(methoddata))
@ -73,7 +83,14 @@ func TestMethodString(t *testing.T) {
} }
for _, test := range table { for _, test := range table {
got := abi.Methods[test.method].String() var got string
if test.method == "fallback" {
got = abi.Fallback.String()
} else if test.method == "receive" {
got = abi.Receive.String()
} else {
got = abi.Methods[test.method].String()
}
if got != test.expectation { if got != test.expectation {
t.Errorf("expected string to be %s, got %s", test.expectation, got) t.Errorf("expected string to be %s, got %s", test.expectation, got)
} }
@ -120,7 +137,7 @@ func TestMethodSig(t *testing.T) {
} }
for _, test := range cases { for _, test := range cases {
got := abi.Methods[test.method].Sig() got := abi.Methods[test.method].Sig
if got != test.expect { if got != test.expect {
t.Errorf("expected string to be %s, got %s", test.expect, got) t.Errorf("expected string to be %s, got %s", test.expect, got)
} }

View file

@ -1,44 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package abi
import (
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
)
var (
bigT = reflect.TypeOf(&big.Int{})
derefbigT = reflect.TypeOf(big.Int{})
uint8T = reflect.TypeOf(uint8(0))
uint16T = reflect.TypeOf(uint16(0))
uint32T = reflect.TypeOf(uint32(0))
uint64T = reflect.TypeOf(uint64(0))
int8T = reflect.TypeOf(int8(0))
int16T = reflect.TypeOf(int16(0))
int32T = reflect.TypeOf(int32(0))
int64T = reflect.TypeOf(int64(0))
addressT = reflect.TypeOf(common.Address{})
)
// U256 converts a big Int into a 256bit EVM number.
func U256(n *big.Int) []byte {
return math.PaddedBigBytes(math.U256(n), 32)
}

View file

@ -1,33 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package abi
import (
"bytes"
"math/big"
"testing"
)
func TestNumberTypes(t *testing.T) {
ubytes := make([]byte, 32)
ubytes[31] = 1
unsigned := U256(big.NewInt(1))
if !bytes.Equal(unsigned, ubytes) {
t.Errorf("expected %x got %x", ubytes, unsigned)
}
}

View file

@ -69,11 +69,11 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
func packNum(value reflect.Value) []byte { func packNum(value reflect.Value) []byte {
switch kind := value.Kind(); kind { switch kind := value.Kind(); kind {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return U256(new(big.Int).SetUint64(value.Uint())) return math.U256Bytes(new(big.Int).SetUint64(value.Uint()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return U256(big.NewInt(value.Int())) return math.U256Bytes(big.NewInt(value.Int()))
case reflect.Ptr: case reflect.Ptr:
return U256(new(big.Int).Set(value.Interface().(*big.Int))) return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int)))
default: default:
panic("abi: fatal error") panic("abi: fatal error")
} }

View file

@ -18,623 +18,62 @@ package abi
import ( import (
"bytes" "bytes"
"encoding/hex"
"fmt"
"math" "math"
"math/big" "math/big"
"reflect" "reflect"
"strconv"
"strings" "strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// TestPack tests the general pack/unpack tests in packing_test.go
func TestPack(t *testing.T) { func TestPack(t *testing.T) {
for i, test := range []struct { for i, test := range packUnpackTests {
typ string t.Run(strconv.Itoa(i), func(t *testing.T) {
components []ArgumentMarshaling encb, err := hex.DecodeString(test.packed)
input interface{} if err != nil {
output []byte t.Fatalf("invalid hex %s: %v", test.packed, err)
}{ }
{ inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, test.def)
"uint8", inAbi, err := JSON(strings.NewReader(inDef))
nil, if err != nil {
uint8(2), t.Fatalf("invalid ABI definition %s, %v", inDef, err)
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), }
}, var packed []byte
{ if reflect.TypeOf(test.unpacked).Kind() != reflect.Struct {
"uint8[]", packed, err = inAbi.Pack("method", test.unpacked)
nil, } else {
[]uint8{1, 2}, // if want is a struct we need to use the components.
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), elem := reflect.ValueOf(test.unpacked)
}, var values []interface{}
{ for i := 0; i < elem.NumField(); i++ {
"uint16", field := elem.Field(i)
nil, values = append(values, field.Interface())
uint16(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint16[]",
nil,
[]uint16{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint32",
nil,
uint32(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint32[]",
nil,
[]uint32{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint64",
nil,
uint64(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint64[]",
nil,
[]uint64{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint256",
nil,
big.NewInt(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint256[]",
nil,
[]*big.Int{big.NewInt(1), big.NewInt(2)},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int8",
nil,
int8(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int8[]",
nil,
[]int8{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int16",
nil,
int16(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int16[]",
nil,
[]int16{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int32",
nil,
int32(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int32[]",
nil,
[]int32{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int64",
nil,
int64(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int64[]",
nil,
[]int64{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int256",
nil,
big.NewInt(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int256[]",
nil,
[]*big.Int{big.NewInt(1), big.NewInt(2)},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"bytes1",
nil,
[1]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes2",
nil,
[2]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes3",
nil,
[3]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes4",
nil,
[4]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes5",
nil,
[5]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes6",
nil,
[6]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes7",
nil,
[7]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes8",
nil,
[8]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes9",
nil,
[9]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes10",
nil,
[10]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes11",
nil,
[11]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes12",
nil,
[12]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes13",
nil,
[13]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes14",
nil,
[14]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes15",
nil,
[15]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes16",
nil,
[16]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes17",
nil,
[17]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes18",
nil,
[18]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes19",
nil,
[19]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes20",
nil,
[20]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes21",
nil,
[21]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes22",
nil,
[22]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes23",
nil,
[23]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes24",
nil,
[24]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes25",
nil,
[25]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes26",
nil,
[26]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes27",
nil,
[27]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes28",
nil,
[28]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes29",
nil,
[29]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes30",
nil,
[30]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes31",
nil,
[31]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes32",
nil,
[32]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"uint32[2][3][4]",
nil,
[4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"),
},
{
"address[]",
nil,
[]common.Address{{1}, {2}},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
},
{
"bytes32[]",
nil,
[]common.Hash{{1}, {2}},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
},
{
"function",
nil,
[24]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"string",
nil,
"foobar",
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
},
{
"string[]",
nil,
[]string{"hello", "foobar"},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
},
{
"string[2]",
nil,
[]string{"hello", "foobar"},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
},
{
"bytes32[][]",
nil,
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
{
"bytes32[][2]",
nil,
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
{
"bytes32[3][2]",
nil,
[][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
{
// static tuple
"tuple",
[]ArgumentMarshaling{
{Name: "a", Type: "int64"},
{Name: "b", Type: "int256"},
{Name: "c", Type: "int256"},
{Name: "d", Type: "bool"},
{Name: "e", Type: "bytes32[3][2]"},
},
struct {
A int64
B *big.Int
C *big.Int
D bool
E [][]common.Hash
}{1, big.NewInt(1), big.NewInt(-1), true, [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001" + // struct[a]
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c]
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[d]
"0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1]
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2]
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // struct[e] array[1][2]
},
{
// dynamic tuple
"tuple",
[]ArgumentMarshaling{
{Name: "a", Type: "string"},
{Name: "b", Type: "int64"},
{Name: "c", Type: "bytes"},
{Name: "d", Type: "string[]"},
{Name: "e", Type: "int256[]"},
{Name: "f", Type: "address[]"},
},
struct {
FieldA string `abi:"a"` // Test whether abi tag works
FieldB int64 `abi:"b"`
C []byte
D []string
E []*big.Int
F []common.Address
}{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}},
common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
"0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset
"0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset
"0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset
"0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset
"0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length
"666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar"
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length
"0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1}
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length
"0000000000000000000000000000000000000000000000000000000000000040" + // foo offset
"0000000000000000000000000000000000000000000000000000000000000080" + // bar offset
"0000000000000000000000000000000000000000000000000000000000000003" + // foo length
"666f6f0000000000000000000000000000000000000000000000000000000000" + // foo
"0000000000000000000000000000000000000000000000000000000000000003" + // bar offset
"6261720000000000000000000000000000000000000000000000000000000000" + // bar
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length
"0000000000000000000000000000000000000000000000000000000000000001" + // 1
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length
"0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1}
"0000000000000000000000000200000000000000000000000000000000000000"), // common.Address{2}
},
{
// nested tuple
"tuple",
[]ArgumentMarshaling{
{Name: "a", Type: "tuple", Components: []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256[]"}}},
{Name: "b", Type: "int256[]"},
},
struct {
A struct {
FieldA *big.Int `abi:"a"`
B []*big.Int
} }
B []*big.Int packed, err = inAbi.Pack("method", values...)
}{ }
A: struct {
FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple
B []*big.Int
}{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
B: []*big.Int{big.NewInt(1), big.NewInt(0)}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // a offset
"00000000000000000000000000000000000000000000000000000000000000e0" + // b offset
"0000000000000000000000000000000000000000000000000000000000000001" + // a.a value
"0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b length
"0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value
"0000000000000000000000000000000000000000000000000000000000000000" + // a.b[1] value
"0000000000000000000000000000000000000000000000000000000000000002" + // b length
"0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value
"0000000000000000000000000000000000000000000000000000000000000000"), // b[1] value
},
{
// tuple slice
"tuple[]",
[]ArgumentMarshaling{
{Name: "a", Type: "int256"},
{Name: "b", Type: "int256[]"},
},
[]struct {
A *big.Int
B []*big.Int
}{
{big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
{big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}},
},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // tuple length
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
"00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value
"0000000000000000000000000000000000000000000000000000000000000000" + // tuple[0].B[1] value
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].B[1] value
},
{
// static tuple array
"tuple[2]",
[]ArgumentMarshaling{
{Name: "a", Type: "int256"},
{Name: "b", Type: "int256"},
},
[2]struct {
A *big.Int
B *big.Int
}{
{big.NewInt(-1), big.NewInt(1)},
{big.NewInt(1), big.NewInt(-1)},
},
common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].b
},
{
// dynamic tuple array
"tuple[2]",
[]ArgumentMarshaling{
{Name: "a", Type: "int256[]"},
},
[2]struct {
A []*big.Int
}{
{[]*big.Int{big.NewInt(-1), big.NewInt(1)}},
{[]*big.Int{big.NewInt(1), big.NewInt(-1)}},
},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
"00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0]
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1]
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0]
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
},
} {
typ, err := NewType(test.typ, "", test.components)
if err != nil {
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
}
output, err := typ.pack(reflect.ValueOf(test.input))
if err != nil {
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
}
if !bytes.Equal(output, test.output) { if err != nil {
t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output) t.Fatalf("test %d (%v) failed: %v", i, test.def, err)
} }
if !reflect.DeepEqual(packed[4:], encb) {
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, encb, packed[4:])
}
})
} }
} }
func TestMethodPack(t *testing.T) { func TestMethodPack(t *testing.T) {
abi, err := JSON(strings.NewReader(jsondata2)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
sig := abi.Methods["slice"].ID() sig := abi.Methods["slice"].ID
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
@ -648,7 +87,7 @@ func TestMethodPack(t *testing.T) {
} }
var addrA, addrB = common.Address{1}, common.Address{2} var addrA, addrB = common.Address{1}, common.Address{2}
sig = abi.Methods["sliceAddress"].ID() sig = abi.Methods["sliceAddress"].ID
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
@ -663,7 +102,7 @@ func TestMethodPack(t *testing.T) {
} }
var addrC, addrD = common.Address{3}, common.Address{4} var addrC, addrD = common.Address{3}, common.Address{4}
sig = abi.Methods["sliceMultiAddress"].ID() sig = abi.Methods["sliceMultiAddress"].ID
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
@ -681,7 +120,7 @@ func TestMethodPack(t *testing.T) {
t.Errorf("expected %x got %x", sig, packed) t.Errorf("expected %x got %x", sig, packed)
} }
sig = abi.Methods["slice256"].ID() sig = abi.Methods["slice256"].ID
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
@ -695,7 +134,7 @@ func TestMethodPack(t *testing.T) {
} }
a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}} a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}}
sig = abi.Methods["nestedArray"].ID() sig = abi.Methods["nestedArray"].ID
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
@ -712,7 +151,7 @@ func TestMethodPack(t *testing.T) {
t.Errorf("expected %x got %x", sig, packed) t.Errorf("expected %x got %x", sig, packed)
} }
sig = abi.Methods["nestedArray2"].ID() sig = abi.Methods["nestedArray2"].ID
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...)
@ -728,7 +167,7 @@ func TestMethodPack(t *testing.T) {
t.Errorf("expected %x got %x", sig, packed) t.Errorf("expected %x got %x", sig, packed)
} }
sig = abi.Methods["nestedSlice"].ID() sig = abi.Methods["nestedSlice"].ID
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)

View file

@ -0,0 +1,988 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package abi
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
type packUnpackTest struct {
def string
unpacked interface{}
packed string
}
var packUnpackTests = []packUnpackTest{
// Booleans
{
def: `[{ "type": "bool" }]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: true,
},
{
def: `[{ "type": "bool" }]`,
packed: "0000000000000000000000000000000000000000000000000000000000000000",
unpacked: false,
},
// Integers
{
def: `[{ "type": "uint8" }]`,
unpacked: uint8(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{ "type": "uint8[]" }]`,
unpacked: []uint8{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{ "type": "uint16" }]`,
unpacked: uint16(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{ "type": "uint16[]" }]`,
unpacked: []uint16{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "uint17"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: big.NewInt(1),
},
{
def: `[{"type": "uint32"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: uint32(1),
},
{
def: `[{"type": "uint32[]"}]`,
unpacked: []uint32{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "uint64"}]`,
unpacked: uint64(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "uint64[]"}]`,
unpacked: []uint64{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "uint256"}]`,
unpacked: big.NewInt(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "uint256[]"}]`,
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int8"}]`,
unpacked: int8(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int8[]"}]`,
unpacked: []int8{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int16"}]`,
unpacked: int16(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int16[]"}]`,
unpacked: []int16{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int17"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: big.NewInt(1),
},
{
def: `[{"type": "int32"}]`,
unpacked: int32(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int32"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: int32(1),
},
{
def: `[{"type": "int32[]"}]`,
unpacked: []int32{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int64"}]`,
unpacked: int64(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int64[]"}]`,
unpacked: []int64{1, 2},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int256"}]`,
unpacked: big.NewInt(2),
packed: "0000000000000000000000000000000000000000000000000000000000000002",
},
{
def: `[{"type": "int256"}]`,
packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
unpacked: big.NewInt(-1),
},
{
def: `[{"type": "int256[]"}]`,
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
},
// Address
{
def: `[{"type": "address"}]`,
packed: "0000000000000000000000000100000000000000000000000000000000000000",
unpacked: common.Address{1},
},
{
def: `[{"type": "address[]"}]`,
unpacked: []common.Address{{1}, {2}},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000100000000000000000000000000000000000000" +
"0000000000000000000000000200000000000000000000000000000000000000",
},
// Bytes
{
def: `[{"type": "bytes1"}]`,
unpacked: [1]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes2"}]`,
unpacked: [2]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes3"}]`,
unpacked: [3]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes4"}]`,
unpacked: [4]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes5"}]`,
unpacked: [5]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes6"}]`,
unpacked: [6]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes7"}]`,
unpacked: [7]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes8"}]`,
unpacked: [8]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes9"}]`,
unpacked: [9]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes10"}]`,
unpacked: [10]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes11"}]`,
unpacked: [11]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes12"}]`,
unpacked: [12]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes13"}]`,
unpacked: [13]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes14"}]`,
unpacked: [14]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes15"}]`,
unpacked: [15]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes16"}]`,
unpacked: [16]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes17"}]`,
unpacked: [17]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes18"}]`,
unpacked: [18]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes19"}]`,
unpacked: [19]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes20"}]`,
unpacked: [20]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes21"}]`,
unpacked: [21]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes22"}]`,
unpacked: [22]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes23"}]`,
unpacked: [23]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes24"}]`,
unpacked: [24]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes25"}]`,
unpacked: [25]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes26"}]`,
unpacked: [26]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes27"}]`,
unpacked: [27]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes28"}]`,
unpacked: [28]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes29"}]`,
unpacked: [29]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes30"}]`,
unpacked: [30]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes31"}]`,
unpacked: [31]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes32"}]`,
unpacked: [32]byte{1},
packed: "0100000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "bytes32"}]`,
packed: "0100000000000000000000000000000000000000000000000000000000000000",
unpacked: [32]byte{1, 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},
},
{
def: `[{"type": "bytes"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000020" +
"0100000000000000000000000000000000000000000000000000000000000000",
unpacked: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
def: `[{"type": "bytes32"}]`,
packed: "0100000000000000000000000000000000000000000000000000000000000000",
unpacked: [32]byte{1, 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},
},
// Functions
{
def: `[{"type": "function"}]`,
packed: "0100000000000000000000000000000000000000000000000000000000000000",
unpacked: [24]byte{1},
},
// Slice and Array
{
def: `[{"type": "uint8[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []uint8{1, 2},
},
{
def: `[{"type": "uint8[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000000",
unpacked: []uint8{},
},
{
def: `[{"type": "uint256[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000000",
unpacked: []*big.Int{},
},
{
def: `[{"type": "uint8[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]uint8{1, 2},
},
{
def: `[{"type": "int8[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]int8{1, 2},
},
{
def: `[{"type": "int16[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []int16{1, 2},
},
{
def: `[{"type": "int16[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]int16{1, 2},
},
{
def: `[{"type": "int32[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []int32{1, 2},
},
{
def: `[{"type": "int32[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]int32{1, 2},
},
{
def: `[{"type": "int64[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []int64{1, 2},
},
{
def: `[{"type": "int64[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]int64{1, 2},
},
{
def: `[{"type": "int256[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"type": "int256[3]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000003",
unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
},
// multi dimensional, if these pass, all types that don't require length prefix should pass
{
def: `[{"type": "uint8[][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000000",
unpacked: [][]uint8{},
},
{
def: `[{"type": "uint8[][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"00000000000000000000000000000000000000000000000000000000000000a0" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [][]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint8[][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"00000000000000000000000000000000000000000000000000000000000000a0" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000003" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000003",
unpacked: [][]uint8{{1, 2}, {1, 2, 3}},
},
{
def: `[{"type": "uint8[2][2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2][2]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint8[][2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"0000000000000000000000000000000000000000000000000000000000000060" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000",
unpacked: [2][]uint8{{}, {}},
},
{
def: `[{"type": "uint8[][2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"0000000000000000000000000000000000000000000000000000000000000080" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000001",
unpacked: [2][]uint8{{1}, {1}},
},
{
def: `[{"type": "uint8[2][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000000",
unpacked: [][2]uint8{},
},
{
def: `[{"type": "uint8[2][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [][2]uint8{{1, 2}},
},
{
def: `[{"type": "uint8[2][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [][2]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint16[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []uint16{1, 2},
},
{
def: `[{"type": "uint16[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]uint16{1, 2},
},
{
def: `[{"type": "uint32[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []uint32{1, 2},
},
{
def: `[{"type": "uint32[2][3][4]"}]`,
unpacked: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000003" +
"0000000000000000000000000000000000000000000000000000000000000004" +
"0000000000000000000000000000000000000000000000000000000000000005" +
"0000000000000000000000000000000000000000000000000000000000000006" +
"0000000000000000000000000000000000000000000000000000000000000007" +
"0000000000000000000000000000000000000000000000000000000000000008" +
"0000000000000000000000000000000000000000000000000000000000000009" +
"000000000000000000000000000000000000000000000000000000000000000a" +
"000000000000000000000000000000000000000000000000000000000000000b" +
"000000000000000000000000000000000000000000000000000000000000000c" +
"000000000000000000000000000000000000000000000000000000000000000d" +
"000000000000000000000000000000000000000000000000000000000000000e" +
"000000000000000000000000000000000000000000000000000000000000000f" +
"0000000000000000000000000000000000000000000000000000000000000010" +
"0000000000000000000000000000000000000000000000000000000000000011" +
"0000000000000000000000000000000000000000000000000000000000000012" +
"0000000000000000000000000000000000000000000000000000000000000013" +
"0000000000000000000000000000000000000000000000000000000000000014" +
"0000000000000000000000000000000000000000000000000000000000000015" +
"0000000000000000000000000000000000000000000000000000000000000016" +
"0000000000000000000000000000000000000000000000000000000000000017" +
"0000000000000000000000000000000000000000000000000000000000000018",
},
{
def: `[{"type": "bytes32[]"}]`,
unpacked: []common.Hash{{1}, {2}},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0100000000000000000000000000000000000000000000000000000000000000" +
"0200000000000000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "uint32[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]uint32{1, 2},
},
{
def: `[{"type": "uint64[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []uint64{1, 2},
},
{
def: `[{"type": "uint64[2]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: [2]uint64{1, 2},
},
{
def: `[{"type": "uint256[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"type": "uint256[3]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000003",
unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
},
{
def: `[{"type": "string[4]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000080" +
"00000000000000000000000000000000000000000000000000000000000000c0" +
"0000000000000000000000000000000000000000000000000000000000000100" +
"0000000000000000000000000000000000000000000000000000000000000140" +
"0000000000000000000000000000000000000000000000000000000000000005" +
"48656c6c6f000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000005" +
"576f726c64000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000000b" +
"476f2d657468657265756d000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000008" +
"457468657265756d000000000000000000000000000000000000000000000000",
unpacked: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"},
},
{
def: `[{"type": "string[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"0000000000000000000000000000000000000000000000000000000000000080" +
"0000000000000000000000000000000000000000000000000000000000000008" +
"457468657265756d000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000000b" +
"676f2d657468657265756d000000000000000000000000000000000000000000",
unpacked: []string{"Ethereum", "go-ethereum"},
},
{
def: `[{"type": "bytes[]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"0000000000000000000000000000000000000000000000000000000000000080" +
"0000000000000000000000000000000000000000000000000000000000000003" +
"f0f0f00000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000003" +
"f0f0f00000000000000000000000000000000000000000000000000000000000",
unpacked: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}},
},
{
def: `[{"type": "uint256[2][][]"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000040" +
"00000000000000000000000000000000000000000000000000000000000000e0" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"00000000000000000000000000000000000000000000000000000000000000c8" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"00000000000000000000000000000000000000000000000000000000000003e8" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"00000000000000000000000000000000000000000000000000000000000000c8" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"00000000000000000000000000000000000000000000000000000000000003e8",
unpacked: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}},
},
// struct outputs
{
def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: struct {
Int1 *big.Int
Int2 *big.Int
}{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"name":"int_one","type":"int256"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int__one","type":"int256"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one_","type":"int256"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001",
unpacked: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`,
packed: "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
unpacked: struct {
IntOne *big.Int
Intone *big.Int
}{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"type": "string"}]`,
unpacked: "foobar",
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000006" +
"666f6f6261720000000000000000000000000000000000000000000000000000",
},
{
def: `[{"type": "string[]"}]`,
unpacked: []string{"hello", "foobar"},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
"666f6f6261720000000000000000000000000000000000000000000000000000", // str[1]
},
{
def: `[{"type": "string[2]"}]`,
unpacked: [2]string{"hello", "foobar"},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
"666f6f6261720000000000000000000000000000000000000000000000000000", // str[1]
},
{
def: `[{"type": "bytes32[][]"}]`,
unpacked: [][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000", // array[1][2]
},
{
def: `[{"type": "bytes32[][2]"}]`,
unpacked: [2][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000", // array[1][2]
},
{
def: `[{"type": "bytes32[3][2]"}]`,
unpacked: [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
packed: "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000", // array[1][2]
},
{
// static tuple
def: `[{"name":"a","type":"int64"},
{"name":"b","type":"int256"},
{"name":"c","type":"int256"},
{"name":"d","type":"bool"},
{"name":"e","type":"bytes32[3][2]"}]`,
unpacked: struct {
A int64
B *big.Int
C *big.Int
D bool
E [2][3][32]byte
}{1, big.NewInt(1), big.NewInt(-1), true, [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}}},
packed: "0000000000000000000000000000000000000000000000000000000000000001" + // struct[a]
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c]
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[d]
"0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1]
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2]
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000", // struct[e] array[1][2]
},
{
def: `[{"name":"a","type":"string"},
{"name":"b","type":"int64"},
{"name":"c","type":"bytes"},
{"name":"d","type":"string[]"},
{"name":"e","type":"int256[]"},
{"name":"f","type":"address[]"}]`,
unpacked: struct {
FieldA string `abi:"a"` // Test whether abi tag works
FieldB int64 `abi:"b"`
C []byte
D []string
E []*big.Int
F []common.Address
}{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}},
packed: "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
"0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset
"0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset
"0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset
"0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset
"0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length
"666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar"
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length
"0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1}
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length
"0000000000000000000000000000000000000000000000000000000000000040" + // foo offset
"0000000000000000000000000000000000000000000000000000000000000080" + // bar offset
"0000000000000000000000000000000000000000000000000000000000000003" + // foo length
"666f6f0000000000000000000000000000000000000000000000000000000000" + // foo
"0000000000000000000000000000000000000000000000000000000000000003" + // bar offset
"6261720000000000000000000000000000000000000000000000000000000000" + // bar
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length
"0000000000000000000000000000000000000000000000000000000000000001" + // 1
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length
"0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1}
"0000000000000000000000000200000000000000000000000000000000000000", // common.Address{2}
},
{
def: `[{"components": [{"name": "a","type": "uint256"},
{"name": "b","type": "uint256[]"}],
"name": "a","type": "tuple"},
{"name": "b","type": "uint256[]"}]`,
unpacked: struct {
A struct {
FieldA *big.Int `abi:"a"`
B []*big.Int
}
B []*big.Int
}{
A: struct {
FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple
B []*big.Int
}{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(2)}},
B: []*big.Int{big.NewInt(1), big.NewInt(2)}},
packed: "0000000000000000000000000000000000000000000000000000000000000040" + // a offset
"00000000000000000000000000000000000000000000000000000000000000e0" + // b offset
"0000000000000000000000000000000000000000000000000000000000000001" + // a.a value
"0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b length
"0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b[1] value
"0000000000000000000000000000000000000000000000000000000000000002" + // b length
"0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value
"0000000000000000000000000000000000000000000000000000000000000002", // b[1] value
},
{
def: `[{"components": [{"name": "a","type": "int256"},
{"name": "b","type": "int256[]"}],
"name": "a","type": "tuple[]"}]`,
unpacked: []struct {
A *big.Int
B []*big.Int
}{
{big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(3)}},
{big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}},
},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple length
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
"00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value
"0000000000000000000000000000000000000000000000000000000000000003" + // tuple[0].B[1] value
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].B[1] value
},
{
def: `[{"components": [{"name": "a","type": "int256"},
{"name": "b","type": "int256"}],
"name": "a","type": "tuple[2]"}]`,
unpacked: [2]struct {
A *big.Int
B *big.Int
}{
{big.NewInt(-1), big.NewInt(1)},
{big.NewInt(1), big.NewInt(-1)},
},
packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].b
},
{
def: `[{"components": [{"name": "a","type": "int256[]"}],
"name": "a","type": "tuple[2]"}]`,
unpacked: [2]struct {
A []*big.Int
}{
{[]*big.Int{big.NewInt(-1), big.NewInt(1)}},
{[]*big.Int{big.NewInt(1), big.NewInt(-1)}},
},
packed: "0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
"00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0]
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1]
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0]
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].A[1]
},
}

View file

@ -18,6 +18,7 @@ package abi
import ( import (
"fmt" "fmt"
"math/big"
"reflect" "reflect"
"strings" "strings"
) )
@ -25,7 +26,7 @@ import (
// indirect recursively dereferences the value until it either gets the value // indirect recursively dereferences the value until it either gets the value
// or finds a big.Int // or finds a big.Int
func indirect(v reflect.Value) reflect.Value { func indirect(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT { if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) {
return indirect(v.Elem()) return indirect(v.Elem())
} }
return v return v
@ -39,32 +40,32 @@ func indirectInterfaceOrPtr(v reflect.Value) reflect.Value {
return v return v
} }
// reflectIntKind returns the reflect using the given size and // reflectIntType returns the reflect using the given size and
// unsignedness. // unsignedness.
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) { func reflectIntType(unsigned bool, size int) reflect.Type {
if unsigned {
switch size {
case 8:
return reflect.TypeOf(uint8(0))
case 16:
return reflect.TypeOf(uint16(0))
case 32:
return reflect.TypeOf(uint32(0))
case 64:
return reflect.TypeOf(uint64(0))
}
}
switch size { switch size {
case 8: case 8:
if unsigned { return reflect.TypeOf(int8(0))
return reflect.Uint8, uint8T
}
return reflect.Int8, int8T
case 16: case 16:
if unsigned { return reflect.TypeOf(int16(0))
return reflect.Uint16, uint16T
}
return reflect.Int16, int16T
case 32: case 32:
if unsigned { return reflect.TypeOf(int32(0))
return reflect.Uint32, uint32T
}
return reflect.Int32, int32T
case 64: case 64:
if unsigned { return reflect.TypeOf(int64(0))
return reflect.Uint64, uint64T
}
return reflect.Int64, int64T
} }
return reflect.Ptr, bigT return reflect.TypeOf(&big.Int{})
} }
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value // mustArrayToBytesSlice creates a new byte slice with the exact same size as value
@ -84,12 +85,12 @@ func set(dst, src reflect.Value) error {
switch { switch {
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid(): case dstType.Kind() == reflect.Interface && dst.Elem().IsValid():
return set(dst.Elem(), src) return set(dst.Elem(), src)
case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT: case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}):
return set(dst.Elem(), src) return set(dst.Elem(), src)
case srcType.AssignableTo(dstType) && dst.CanSet(): case srcType.AssignableTo(dstType) && dst.CanSet():
dst.Set(src) dst.Set(src)
case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice: case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice && dst.CanSet():
return setSlice(dst, src) setSlice(dst, src)
default: default:
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
} }
@ -98,15 +99,13 @@ func set(dst, src reflect.Value) error {
// setSlice attempts to assign src to dst when slices are not assignable by default // setSlice attempts to assign src to dst when slices are not assignable by default
// e.g. src: [][]byte -> dst: [][15]byte // e.g. src: [][]byte -> dst: [][15]byte
func setSlice(dst, src reflect.Value) error { // setSlice ignores if we cannot copy all of src' elements.
func setSlice(dst, src reflect.Value) {
slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len())
for i := 0; i < src.Len(); i++ { for i := 0; i < src.Len(); i++ {
v := src.Index(i) reflect.Copy(slice.Index(i), src.Index(i))
reflect.Copy(slice.Index(i), v)
} }
dst.Set(slice) dst.Set(slice)
return nil
} }
// requireAssignable assures that `dest` is a pointer and it's not an interface. // requireAssignable assures that `dest` is a pointer and it's not an interface.
@ -118,18 +117,16 @@ func requireAssignable(dst, src reflect.Value) error {
} }
// requireUnpackKind verifies preconditions for unpacking `args` into `kind` // requireUnpackKind verifies preconditions for unpacking `args` into `kind`
func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind, func requireUnpackKind(v reflect.Value, minLength int, args Arguments) error {
args Arguments) error { switch v.Kind() {
switch k {
case reflect.Struct: case reflect.Struct:
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
if minLen := args.LengthNonIndexed(); v.Len() < minLen { if v.Len() < minLength {
return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d", return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d",
minLen, v.Len()) minLength, v.Len())
} }
default: default:
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t) return fmt.Errorf("abi: cannot unmarshal tuple into %v", v.Type())
} }
return nil return nil
} }
@ -156,9 +153,8 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
continue continue
} }
// skip fields that have no abi:"" tag. // skip fields that have no abi:"" tag.
var ok bool tagName, ok := typ.Field(i).Tag.Lookup("abi")
var tagName string if !ok {
if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
continue continue
} }
// check if tag is empty. // check if tag is empty.

View file

@ -23,6 +23,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/ethereum/go-ethereum/common"
) )
// Type enumerator // Type enumerator
@ -45,17 +47,16 @@ const (
// Type is the reflection of the supported argument type // Type is the reflection of the supported argument type
type Type struct { type Type struct {
Elem *Type Elem *Type
Kind reflect.Kind
Type reflect.Type
Size int Size int
T byte // Our own type checking T byte // Our own type checking
stringKind string // holds the unparsed string for deriving signatures stringKind string // holds the unparsed string for deriving signatures
// Tuple relative fields // Tuple relative fields
TupleRawName string // Raw struct name defined in source code, may be empty. TupleRawName string // Raw struct name defined in source code, may be empty.
TupleElems []*Type // Type information of all tuple fields TupleElems []*Type // Type information of all tuple fields
TupleRawNames []string // Raw field name of all tuple fields TupleRawNames []string // Raw field name of all tuple fields
TupleType reflect.Type // Underlying struct of the tuple
} }
var ( var (
@ -94,20 +95,16 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
if len(intz) == 0 { if len(intz) == 0 {
// is a slice // is a slice
typ.T = SliceTy typ.T = SliceTy
typ.Kind = reflect.Slice
typ.Elem = &embeddedType typ.Elem = &embeddedType
typ.Type = reflect.SliceOf(embeddedType.Type)
typ.stringKind = embeddedType.stringKind + sliced typ.stringKind = embeddedType.stringKind + sliced
} else if len(intz) == 1 { } else if len(intz) == 1 {
// is a array // is a array
typ.T = ArrayTy typ.T = ArrayTy
typ.Kind = reflect.Array
typ.Elem = &embeddedType typ.Elem = &embeddedType
typ.Size, err = strconv.Atoi(intz[0]) typ.Size, err = strconv.Atoi(intz[0])
if err != nil { if err != nil {
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
} }
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
typ.stringKind = embeddedType.stringKind + sliced typ.stringKind = embeddedType.stringKind + sliced
} else { } else {
return Type{}, fmt.Errorf("invalid formatting of array type") return Type{}, fmt.Errorf("invalid formatting of array type")
@ -139,36 +136,24 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
// varType is the parsed abi type // varType is the parsed abi type
switch varType := parsedType[1]; varType { switch varType := parsedType[1]; varType {
case "int": case "int":
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
typ.Size = varSize typ.Size = varSize
typ.T = IntTy typ.T = IntTy
case "uint": case "uint":
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
typ.Size = varSize typ.Size = varSize
typ.T = UintTy typ.T = UintTy
case "bool": case "bool":
typ.Kind = reflect.Bool
typ.T = BoolTy typ.T = BoolTy
typ.Type = reflect.TypeOf(bool(false))
case "address": case "address":
typ.Kind = reflect.Array
typ.Type = addressT
typ.Size = 20 typ.Size = 20
typ.T = AddressTy typ.T = AddressTy
case "string": case "string":
typ.Kind = reflect.String
typ.Type = reflect.TypeOf("")
typ.T = StringTy typ.T = StringTy
case "bytes": case "bytes":
if varSize == 0 { if varSize == 0 {
typ.T = BytesTy typ.T = BytesTy
typ.Kind = reflect.Slice
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
} else { } else {
typ.T = FixedBytesTy typ.T = FixedBytesTy
typ.Kind = reflect.Array
typ.Size = varSize typ.Size = varSize
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
} }
case "tuple": case "tuple":
var ( var (
@ -188,7 +173,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
} }
fields = append(fields, reflect.StructField{ fields = append(fields, reflect.StructField{
Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field. Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field.
Type: cType.Type, Type: cType.getType(),
Tag: reflect.StructTag("json:\"" + c.Name + "\""), Tag: reflect.StructTag("json:\"" + c.Name + "\""),
}) })
elems = append(elems, &cType) elems = append(elems, &cType)
@ -199,8 +184,8 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
} }
} }
expression += ")" expression += ")"
typ.Kind = reflect.Struct
typ.Type = reflect.StructOf(fields) typ.TupleType = reflect.StructOf(fields)
typ.TupleElems = elems typ.TupleElems = elems
typ.TupleRawNames = names typ.TupleRawNames = names
typ.T = TupleTy typ.T = TupleTy
@ -217,10 +202,8 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
} }
case "function": case "function":
typ.Kind = reflect.Array
typ.T = FunctionTy typ.T = FunctionTy
typ.Size = 24 typ.Size = 24
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
default: default:
return Type{}, fmt.Errorf("unsupported arg type: %s", t) return Type{}, fmt.Errorf("unsupported arg type: %s", t)
} }
@ -228,6 +211,41 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
return return
} }
func (t Type) getType() reflect.Type {
switch t.T {
case IntTy:
return reflectIntType(false, t.Size)
case UintTy:
return reflectIntType(true, t.Size)
case BoolTy:
return reflect.TypeOf(false)
case StringTy:
return reflect.TypeOf("")
case SliceTy:
return reflect.SliceOf(t.Elem.getType())
case ArrayTy:
return reflect.ArrayOf(t.Size, t.Elem.getType())
case TupleTy:
return t.TupleType
case AddressTy:
return reflect.TypeOf(common.Address{})
case FixedBytesTy:
return reflect.ArrayOf(t.Size, reflect.TypeOf(byte(0)))
case BytesTy:
return reflect.SliceOf(reflect.TypeOf(byte(0)))
case HashTy:
// hashtype currently not used
return reflect.ArrayOf(32, reflect.TypeOf(byte(0)))
case FixedPointTy:
// fixedpoint type currently not used
return reflect.ArrayOf(32, reflect.TypeOf(byte(0)))
case FunctionTy:
return reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
default:
panic("Invalid type")
}
}
// String implements Stringer // String implements Stringer
func (t Type) String() (out string) { func (t Type) String() (out string) {
return t.stringKind return t.stringKind

View file

@ -36,58 +36,58 @@ func TestTypeRegexp(t *testing.T) {
components []ArgumentMarshaling components []ArgumentMarshaling
kind Type kind Type
}{ }{
{"bool", nil, Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}}, {"bool", nil, Type{T: BoolTy, stringKind: "bool"}},
{"bool[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}}, {"bool[]", nil, Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}},
{"bool[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}}, {"bool[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}},
{"bool[2][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}}, {"bool[2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
{"bool[][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}}, {"bool[][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
{"bool[][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}}, {"bool[][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
{"bool[2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}}, {"bool[2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
{"bool[2][][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}}, {"bool[2][][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
{"bool[2][2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}}, {"bool[2][2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
{"bool[][][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}}, {"bool[][][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
{"bool[][2][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}}, {"bool[][2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
{"int8", nil, Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}}, {"int8", nil, Type{Size: 8, T: IntTy, stringKind: "int8"}},
{"int16", nil, Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}}, {"int16", nil, Type{Size: 16, T: IntTy, stringKind: "int16"}},
{"int32", nil, Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}}, {"int32", nil, Type{Size: 32, T: IntTy, stringKind: "int32"}},
{"int64", nil, Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}}, {"int64", nil, Type{Size: 64, T: IntTy, stringKind: "int64"}},
{"int256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}}, {"int256", nil, Type{Size: 256, T: IntTy, stringKind: "int256"}},
{"int8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, {"int8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
{"int8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, {"int8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
{"int16[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, {"int16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
{"int16[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, {"int16[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
{"int32[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, {"int32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
{"int32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, {"int32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
{"int64[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, {"int64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
{"int64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, {"int64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
{"int256[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, {"int256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
{"int256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, {"int256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
{"uint8", nil, Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}}, {"uint8", nil, Type{Size: 8, T: UintTy, stringKind: "uint8"}},
{"uint16", nil, Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}}, {"uint16", nil, Type{Size: 16, T: UintTy, stringKind: "uint16"}},
{"uint32", nil, Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}}, {"uint32", nil, Type{Size: 32, T: UintTy, stringKind: "uint32"}},
{"uint64", nil, Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}}, {"uint64", nil, Type{Size: 64, T: UintTy, stringKind: "uint64"}},
{"uint256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}}, {"uint256", nil, Type{Size: 256, T: UintTy, stringKind: "uint256"}},
{"uint8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, {"uint8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
{"uint8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, {"uint8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
{"uint16[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, {"uint16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
{"uint16[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, {"uint16[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
{"uint32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, {"uint32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
{"uint32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, {"uint32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
{"uint64[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, {"uint64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
{"uint64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, {"uint64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
{"uint256[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, {"uint256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
{"uint256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, {"uint256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
{"bytes32", nil, Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}}, {"bytes32", nil, Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}},
{"bytes[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, {"bytes[]", nil, Type{T: SliceTy, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
{"bytes[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}}, {"bytes[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}},
{"bytes32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, {"bytes32[]", nil, Type{T: SliceTy, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
{"bytes32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, {"bytes32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
{"string", nil, Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}}, {"string", nil, Type{T: StringTy, stringKind: "string"}},
{"string[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}}, {"string[]", nil, Type{T: SliceTy, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
{"string[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}}, {"string[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[2]"}},
{"address", nil, Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}}, {"address", nil, Type{Size: 20, T: AddressTy, stringKind: "address"}},
{"address[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, {"address[]", nil, Type{T: SliceTy, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
{"address[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, {"address[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
// TODO when fixed types are implemented properly // TODO when fixed types are implemented properly
// {"fixed", nil, Type{}}, // {"fixed", nil, Type{}},
// {"fixed128x128", nil, Type{}}, // {"fixed128x128", nil, Type{}},
@ -95,14 +95,14 @@ func TestTypeRegexp(t *testing.T) {
// {"fixed[2]", nil, Type{}}, // {"fixed[2]", nil, Type{}},
// {"fixed128x128[]", nil, Type{}}, // {"fixed128x128[]", nil, Type{}},
// {"fixed128x128[2]", nil, Type{}}, // {"fixed128x128[2]", nil, Type{}},
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct { {"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct {
A int64 `json:"a"` A int64 `json:"a"`
}{}), stringKind: "(int64)", }{}), stringKind: "(int64)",
TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}}, TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}},
{"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct { {"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct {
ATypicalParamName int64 `json:"aTypicalParamName"` ATypicalParamName int64 `json:"aTypicalParamName"`
}{}), stringKind: "(int64)", }{}), stringKind: "(int64)",
TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}}, TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}},
} }
for _, tt := range tests { for _, tt := range tests {
@ -306,3 +306,27 @@ func TestTypeCheck(t *testing.T) {
} }
} }
} }
func TestInternalType(t *testing.T) {
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
internalType := "struct a.b[]"
kind := Type{
T: TupleTy,
TupleType: reflect.TypeOf(struct {
A int64 `json:"a"`
}{}),
stringKind: "(int64)",
TupleRawName: "ab[]",
TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}},
TupleRawNames: []string{"a"},
}
blob := "tuple"
typ, err := NewType(blob, internalType, components)
if err != nil {
t.Errorf("type %q: failed to parse type string: %v", blob, err)
}
if !reflect.DeepEqual(typ, kind) {
t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(kind)))
}
}

View file

@ -33,33 +33,37 @@ var (
) )
// ReadInteger reads the integer based on its kind and returns the appropriate value // ReadInteger reads the integer based on its kind and returns the appropriate value
func ReadInteger(typ byte, kind reflect.Kind, b []byte) interface{} { func ReadInteger(typ Type, b []byte) interface{} {
switch kind { if typ.T == UintTy {
case reflect.Uint8: switch typ.Size {
return b[len(b)-1] case 8:
case reflect.Uint16: return b[len(b)-1]
return binary.BigEndian.Uint16(b[len(b)-2:]) case 16:
case reflect.Uint32: return binary.BigEndian.Uint16(b[len(b)-2:])
return binary.BigEndian.Uint32(b[len(b)-4:]) case 32:
case reflect.Uint64: return binary.BigEndian.Uint32(b[len(b)-4:])
return binary.BigEndian.Uint64(b[len(b)-8:]) case 64:
case reflect.Int8: return binary.BigEndian.Uint64(b[len(b)-8:])
default:
// the only case left for unsigned integer is uint256.
return new(big.Int).SetBytes(b)
}
}
switch typ.Size {
case 8:
return int8(b[len(b)-1]) return int8(b[len(b)-1])
case reflect.Int16: case 16:
return int16(binary.BigEndian.Uint16(b[len(b)-2:])) return int16(binary.BigEndian.Uint16(b[len(b)-2:]))
case reflect.Int32: case 32:
return int32(binary.BigEndian.Uint32(b[len(b)-4:])) return int32(binary.BigEndian.Uint32(b[len(b)-4:]))
case reflect.Int64: case 64:
return int64(binary.BigEndian.Uint64(b[len(b)-8:])) return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
default: default:
// the only case left for integer is int256/uint256. // the only case left for integer is int256
ret := new(big.Int).SetBytes(b)
if typ == UintTy {
return ret
}
// big.SetBytes can't tell if a number is negative or positive in itself. // big.SetBytes can't tell if a number is negative or positive in itself.
// On EVM, if the returned number > max int256, it is negative. // On EVM, if the returned number > max int256, it is negative.
// A number is > max int256 if the bit at position 255 is set. // A number is > max int256 if the bit at position 255 is set.
ret := new(big.Int).SetBytes(b)
if ret.Bit(255) == 1 { if ret.Bit(255) == 1 {
ret.Add(MaxUint256, new(big.Int).Neg(ret)) ret.Add(MaxUint256, new(big.Int).Neg(ret))
ret.Add(ret, common.Big1) ret.Add(ret, common.Big1)
@ -106,7 +110,7 @@ func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
} }
// convert // convert
array := reflect.New(t.Type).Elem() array := reflect.New(t.getType()).Elem()
reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) reflect.Copy(array, reflect.ValueOf(word[0:t.Size]))
return array.Interface(), nil return array.Interface(), nil
@ -127,10 +131,10 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
if t.T == SliceTy { if t.T == SliceTy {
// declare our slice // declare our slice
refSlice = reflect.MakeSlice(t.Type, size, size) refSlice = reflect.MakeSlice(t.getType(), size, size)
} else if t.T == ArrayTy { } else if t.T == ArrayTy {
// declare our array // declare our array
refSlice = reflect.New(t.Type).Elem() refSlice = reflect.New(t.getType()).Elem()
} else { } else {
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
} }
@ -140,7 +144,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
elemSize := getTypeSize(*t.Elem) elemSize := getTypeSize(*t.Elem)
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
inter, err := toGoType(i, *t.Elem, output) inter, err := ToGoType(i, *t.Elem, output)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,10 +158,10 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
} }
func forTupleUnpack(t Type, output []byte) (interface{}, error) { func forTupleUnpack(t Type, output []byte) (interface{}, error) {
retval := reflect.New(t.Type).Elem() retval := reflect.New(t.getType()).Elem()
virtualArgs := 0 virtualArgs := 0
for index, elem := range t.TupleElems { for index, elem := range t.TupleElems {
marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) marshalledValue, err := ToGoType((index+virtualArgs)*32, *elem, output)
if elem.T == ArrayTy && !isDynamicType(*elem) { if elem.T == ArrayTy && !isDynamicType(*elem) {
// If we have a static array, like [3]uint256, these are coded as // If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256. // just like uint256,uint256,uint256.
@ -183,9 +187,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) {
return retval.Interface(), nil return retval.Interface(), nil
} }
// toGoType parses the output bytes and recursively assigns the value of these bytes // ToGoType parses the output bytes and recursively assigns the value of these bytes
// into a go type with accordance with the ABI spec. // into a go type with accordance with the ABI spec.
func toGoType(index int, t Type, output []byte) (interface{}, error) { func ToGoType(index int, t Type, output []byte) (interface{}, error) {
if index+32 > len(output) { if index+32 > len(output) {
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32)
} }
@ -228,7 +232,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
case StringTy: // variable arrays are written at the end of the return bytes case StringTy: // variable arrays are written at the end of the return bytes
return string(output[begin : begin+length]), nil return string(output[begin : begin+length]), nil
case IntTy, UintTy: case IntTy, UintTy:
return ReadInteger(t.T, t.Kind, returnOutput), nil return ReadInteger(t, returnOutput), nil
case BoolTy: case BoolTy:
return readBool(returnOutput) return readBool(returnOutput)
case AddressTy: case AddressTy:

View file

@ -30,6 +30,34 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestUnpack tests the general pack/unpack tests in packing_test.go
func TestUnpack(t *testing.T) {
for i, test := range packUnpackTests {
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
//Unpack
def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
abi, err := JSON(strings.NewReader(def))
if err != nil {
t.Fatalf("invalid ABI definition %s: %v", def, err)
}
encb, err := hex.DecodeString(test.packed)
if err != nil {
t.Fatalf("invalid hex %s: %v", test.packed, err)
}
outptr := reflect.New(reflect.TypeOf(test.unpacked))
err = abi.Unpack(outptr.Interface(), "method", encb)
if err != nil {
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
return
}
out := outptr.Elem().Interface()
if !reflect.DeepEqual(test.unpacked, out) {
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out)
}
})
}
}
type unpackTest struct { type unpackTest struct {
def string // ABI definition JSON def string // ABI definition JSON
enc string // evm return data enc string // evm return data
@ -52,16 +80,6 @@ func (test unpackTest) checkError(err error) error {
var unpackTests = []unpackTest{ var unpackTests = []unpackTest{
// Bools // Bools
{
def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: true,
},
{
def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000000000000000000",
want: false,
},
{ {
def: `[{ "type": "bool" }]`, def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000001000000000001", enc: "0000000000000000000000000000000000000000000000000001000000000001",
@ -75,11 +93,6 @@ var unpackTests = []unpackTest{
err: "abi: improperly encoded boolean value", err: "abi: improperly encoded boolean value",
}, },
// Integers // Integers
{
def: `[{"type": "uint32"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: uint32(1),
},
{ {
def: `[{"type": "uint32"}]`, def: `[{"type": "uint32"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001", enc: "0000000000000000000000000000000000000000000000000000000000000001",
@ -92,16 +105,6 @@ var unpackTests = []unpackTest{
want: uint16(0), want: uint16(0),
err: "abi: cannot unmarshal *big.Int in to uint16", err: "abi: cannot unmarshal *big.Int in to uint16",
}, },
{
def: `[{"type": "uint17"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: big.NewInt(1),
},
{
def: `[{"type": "int32"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: int32(1),
},
{ {
def: `[{"type": "int32"}]`, def: `[{"type": "int32"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001", enc: "0000000000000000000000000000000000000000000000000000000000000001",
@ -114,33 +117,6 @@ var unpackTests = []unpackTest{
want: int16(0), want: int16(0),
err: "abi: cannot unmarshal *big.Int in to int16", err: "abi: cannot unmarshal *big.Int in to int16",
}, },
{
def: `[{"type": "int17"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: big.NewInt(1),
},
{
def: `[{"type": "int256"}]`,
enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
want: big.NewInt(-1),
},
// Address
{
def: `[{"type": "address"}]`,
enc: "0000000000000000000000000100000000000000000000000000000000000000",
want: common.Address{1},
},
// Bytes
{
def: `[{"type": "bytes32"}]`,
enc: "0100000000000000000000000000000000000000000000000000000000000000",
want: [32]byte{1, 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},
},
{
def: `[{"type": "bytes"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000",
want: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{ {
def: `[{"type": "bytes"}]`, def: `[{"type": "bytes"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000",
@ -153,237 +129,6 @@ var unpackTests = []unpackTest{
want: []byte(nil), want: []byte(nil),
err: "abi: cannot unmarshal [32]uint8 in to []uint8", err: "abi: cannot unmarshal [32]uint8 in to []uint8",
}, },
{
def: `[{"type": "bytes32"}]`,
enc: "0100000000000000000000000000000000000000000000000000000000000000",
want: [32]byte{1, 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},
},
// Functions
{
def: `[{"type": "function"}]`,
enc: "0100000000000000000000000000000000000000000000000000000000000000",
want: [24]byte{1},
},
// Slice and Array
{
def: `[{"type": "uint8[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []uint8{1, 2},
},
{
def: `[{"type": "uint8[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: []uint8{},
},
{
def: `[{"type": "uint256[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: []*big.Int{},
},
{
def: `[{"type": "uint8[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]uint8{1, 2},
},
// multi dimensional, if these pass, all types that don't require length prefix should pass
{
def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: [][]uint8{},
},
{
def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [][]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
want: [][]uint8{{1, 2}, {1, 2, 3}},
},
{
def: `[{"type": "uint8[2][2]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2][2]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint8[][2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
want: [2][]uint8{{}, {}},
},
{
def: `[{"type": "uint8[][2]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
want: [2][]uint8{{1}, {1}},
},
{
def: `[{"type": "uint8[2][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: [][2]uint8{},
},
{
def: `[{"type": "uint8[2][]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [][2]uint8{{1, 2}},
},
{
def: `[{"type": "uint8[2][]"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [][2]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint16[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []uint16{1, 2},
},
{
def: `[{"type": "uint16[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]uint16{1, 2},
},
{
def: `[{"type": "uint32[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []uint32{1, 2},
},
{
def: `[{"type": "uint32[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]uint32{1, 2},
},
{
def: `[{"type": "uint32[2][3][4]"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018",
want: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
},
{
def: `[{"type": "uint64[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []uint64{1, 2},
},
{
def: `[{"type": "uint64[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]uint64{1, 2},
},
{
def: `[{"type": "uint256[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []*big.Int{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"type": "uint256[3]"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
},
{
def: `[{"type": "string[4]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b476f2d657468657265756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000",
want: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"},
},
{
def: `[{"type": "string[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b676f2d657468657265756d000000000000000000000000000000000000000000",
want: []string{"Ethereum", "go-ethereum"},
},
{
def: `[{"type": "bytes[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003f0f0f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0f0f00000000000000000000000000000000000000000000000000000000000",
want: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}},
},
{
def: `[{"type": "uint256[2][][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8",
want: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}},
},
{
def: `[{"type": "int8[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []int8{1, 2},
},
{
def: `[{"type": "int8[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]int8{1, 2},
},
{
def: `[{"type": "int16[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []int16{1, 2},
},
{
def: `[{"type": "int16[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]int16{1, 2},
},
{
def: `[{"type": "int32[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []int32{1, 2},
},
{
def: `[{"type": "int32[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]int32{1, 2},
},
{
def: `[{"type": "int64[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []int64{1, 2},
},
{
def: `[{"type": "int64[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]int64{1, 2},
},
{
def: `[{"type": "int256[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []*big.Int{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"type": "int256[3]"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
},
// struct outputs
{
def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
Int1 *big.Int
Int2 *big.Int
}{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"name":"int_one","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int__one","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one_","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
Intone *big.Int
}{big.NewInt(1), big.NewInt(2)},
},
{ {
def: `[{"name":"___","type":"int256"}]`, def: `[{"name":"___","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -438,12 +183,37 @@ var unpackTests = []unpackTest{
}{}, }{},
err: "abi: purely underscored output cannot unpack to struct", err: "abi: purely underscored output cannot unpack to struct",
}, },
// Make sure only the first argument is consumed
{
def: `[{"name":"int_one","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int__one","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one_","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
} }
func TestUnpack(t *testing.T) { // TestLocalUnpackTests runs test specially designed only for unpacking.
// All test cases that can be used to test packing and unpacking should move to packing_test.go
func TestLocalUnpackTests(t *testing.T) {
for i, test := range unpackTests { for i, test := range unpackTests {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) //Unpack
def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
abi, err := JSON(strings.NewReader(def)) abi, err := JSON(strings.NewReader(def))
if err != nil { if err != nil {
t.Fatalf("invalid ABI definition %s: %v", def, err) t.Fatalf("invalid ABI definition %s: %v", def, err)
@ -522,7 +292,7 @@ type methodMultiOutput struct {
func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) { func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
const definition = `[ const definition = `[
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` { "name" : "multi", "type": "function", "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
var expected = methodMultiOutput{big.NewInt(1), "hello"} var expected = methodMultiOutput{big.NewInt(1), "hello"}
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
@ -611,7 +381,7 @@ func TestMethodMultiReturn(t *testing.T) {
} }
func TestMultiReturnWithArray(t *testing.T) { func TestMultiReturnWithArray(t *testing.T) {
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -634,7 +404,7 @@ func TestMultiReturnWithArray(t *testing.T) {
} }
func TestMultiReturnWithStringArray(t *testing.T) { func TestMultiReturnWithStringArray(t *testing.T) {
const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -664,7 +434,7 @@ func TestMultiReturnWithStringArray(t *testing.T) {
} }
func TestMultiReturnWithStringSlice(t *testing.T) { func TestMultiReturnWithStringSlice(t *testing.T) {
const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -700,7 +470,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
// values of nested static arrays count towards the size as well, and any element following // values of nested static arrays count towards the size as well, and any element following
// after such nested array argument should be read with the correct offset, // after such nested array argument should be read with the correct offset,
// so that it does not read content from the previous array argument. // so that it does not read content from the previous array argument.
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -737,15 +507,15 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
func TestUnmarshal(t *testing.T) { func TestUnmarshal(t *testing.T) {
const definition = `[ const definition = `[
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, { "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
{ "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] }, { "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
{ "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] }, { "name" : "bytes", "type": "function", "outputs": [ { "type": "bytes" } ] },
{ "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, { "name" : "fixed", "type": "function", "outputs": [ { "type": "bytes32" } ] },
{ "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, { "name" : "multi", "type": "function", "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
{ "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] }, { "name" : "intArraySingle", "type": "function", "outputs": [ { "type": "uint256[3]" } ] },
{ "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] }, { "name" : "addressSliceSingle", "type": "function", "outputs": [ { "type": "address[]" } ] },
{ "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, { "name" : "addressSliceDouble", "type": "function", "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
{ "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` { "name" : "mixedBytes", "type": "function", "stateMutability" : "view", "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
@ -985,7 +755,7 @@ func TestUnmarshal(t *testing.T) {
} }
func TestUnpackTuple(t *testing.T) { func TestUnpackTuple(t *testing.T) {
const simpleTuple = `[{"name":"tuple","constant":false,"outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
abi, err := JSON(strings.NewReader(simpleTuple)) abi, err := JSON(strings.NewReader(simpleTuple))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1014,7 +784,7 @@ func TestUnpackTuple(t *testing.T) {
} }
// Test nested tuple // Test nested tuple
const nestedTuple = `[{"name":"tuple","constant":false,"outputs":[ const nestedTuple = `[{"name":"tuple","type":"function","outputs":[
{"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]}, {"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]},
{"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}, {"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]},
{"type":"uint256","name":"a"} {"type":"uint256","name":"a"}
@ -1136,7 +906,7 @@ func TestOOMMaliciousInput(t *testing.T) {
}, },
} }
for i, test := range oomTests { for i, test := range oomTests {
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
abi, err := JSON(strings.NewReader(def)) abi, err := JSON(strings.NewReader(def))
if err != nil { if err != nil {
t.Fatalf("invalid ABI definition %s: %v", def, err) t.Fatalf("invalid ABI definition %s: %v", def, err)

View file

@ -129,6 +129,8 @@ type Wallet interface {
// about which fields or actions are needed. The user may retry by providing // about which fields or actions are needed. The user may retry by providing
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
// the account in a keystore). // the account in a keystore).
//
// This method should return the signature in 'canonical' format, with v 0 or 1
SignText(account Account, text []byte) ([]byte, error) SignText(account Account, text []byte) ([]byte, error)
// SignTextWithPassphrase is identical to Signtext, but also takes a password // SignTextWithPassphrase is identical to Signtext, but also takes a password

View file

@ -131,6 +131,12 @@ func (api *ExternalSigner) Accounts() []accounts.Account {
func (api *ExternalSigner) Contains(account accounts.Account) bool { func (api *ExternalSigner) Contains(account accounts.Account) bool {
api.cacheMu.RLock() api.cacheMu.RLock()
defer api.cacheMu.RUnlock() defer api.cacheMu.RUnlock()
if api.cache == nil {
// If we haven't already fetched the accounts, it's time to do so now
api.cacheMu.RUnlock()
api.Accounts()
api.cacheMu.RLock()
}
for _, a := range api.cache { for _, a := range api.cache {
if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) {
return true return true
@ -169,15 +175,20 @@ func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, d
} }
func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
var res hexutil.Bytes var signature hexutil.Bytes
var signAddress = common.NewMixedcaseAddress(account.Address) var signAddress = common.NewMixedcaseAddress(account.Address)
if err := api.client.Call(&res, "account_signData", if err := api.client.Call(&signature, "account_signData",
accounts.MimetypeTextPlain, accounts.MimetypeTextPlain,
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
hexutil.Encode(text)); err != nil { hexutil.Encode(text)); err != nil {
return nil, err return nil, err
} }
return res, nil if signature[64] == 27 || signature[64] == 28 {
// If clef is used as a backend, it may already have transformed
// the signature to ethereum-type signature.
signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1
}
return signature, nil
} }
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {

View file

@ -24,7 +24,6 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
crand "crypto/rand" crand "crypto/rand"
"errors" "errors"
"fmt"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
@ -67,7 +66,8 @@ type KeyStore struct {
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
updating bool // Whether the event notification loop is running updating bool // Whether the event notification loop is running
mu sync.RWMutex mu sync.RWMutex
importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing
} }
type unlocked struct { type unlocked struct {
@ -443,14 +443,21 @@ func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (ac
if err != nil { if err != nil {
return accounts.Account{}, err return accounts.Account{}, err
} }
ks.importMu.Lock()
defer ks.importMu.Unlock()
if ks.cache.hasAddress(key.Address) {
return accounts.Account{}, errors.New("account already exists")
}
return ks.importKey(key, newPassphrase) return ks.importKey(key, newPassphrase)
} }
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. // ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
key := newKeyFromECDSA(priv) key := newKeyFromECDSA(priv)
ks.importMu.Lock()
defer ks.importMu.Unlock()
if ks.cache.hasAddress(key.Address) { if ks.cache.hasAddress(key.Address) {
return accounts.Account{}, fmt.Errorf("account already exists") return accounts.Account{}, errors.New("account already exists")
} }
return ks.importKey(key, passphrase) return ks.importKey(key, passphrase)
} }

View file

@ -23,11 +23,14 @@ import (
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
"sync"
"sync/atomic"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
) )
@ -338,6 +341,88 @@ func TestWalletNotifications(t *testing.T) {
checkEvents(t, wantEvents, events) checkEvents(t, wantEvents, events)
} }
// TestImportExport tests the import functionality of a keystore.
func TestImportECDSA(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate key: %v", key)
}
if _, err = ks.ImportECDSA(key, "old"); err != nil {
t.Errorf("importing failed: %v", err)
}
if _, err = ks.ImportECDSA(key, "old"); err == nil {
t.Errorf("importing same key twice succeeded")
}
if _, err = ks.ImportECDSA(key, "new"); err == nil {
t.Errorf("importing same key twice succeeded")
}
}
// TestImportECDSA tests the import and export functionality of a keystore.
func TestImportExport(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
acc, err := ks.NewAccount("old")
if err != nil {
t.Fatalf("failed to create account: %v", acc)
}
json, err := ks.Export(acc, "old", "new")
if err != nil {
t.Fatalf("failed to export account: %v", acc)
}
dir2, ks2 := tmpKeyStore(t, true)
defer os.RemoveAll(dir2)
if _, err = ks2.Import(json, "old", "old"); err == nil {
t.Errorf("importing with invalid password succeeded")
}
acc2, err := ks2.Import(json, "new", "new")
if err != nil {
t.Errorf("importing failed: %v", err)
}
if acc.Address != acc2.Address {
t.Error("imported account does not match exported account")
}
if _, err = ks2.Import(json, "new", "new"); err == nil {
t.Errorf("importing a key twice succeeded")
}
}
// TestImportRace tests the keystore on races.
// This test should fail under -race if importing races.
func TestImportRace(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)
acc, err := ks.NewAccount("old")
if err != nil {
t.Fatalf("failed to create account: %v", acc)
}
json, err := ks.Export(acc, "old", "new")
if err != nil {
t.Fatalf("failed to export account: %v", acc)
}
dir2, ks2 := tmpKeyStore(t, true)
defer os.RemoveAll(dir2)
var atom uint32
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func() {
defer wg.Done()
if _, err := ks2.Import(json, "new", "new"); err != nil {
atomic.AddUint32(&atom, 1)
}
}()
}
wg.Wait()
if atom != 1 {
t.Errorf("Import is racy")
}
}
// checkAccounts checks that all known live accounts are present in the wallet list. // checkAccounts checks that all known live accounts are present in the wallet list.
func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) { func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
if len(live) != len(wallets) { if len(live) != len(wallets) {

View file

@ -312,15 +312,15 @@ func (w *Wallet) Status() (string, error) {
} }
switch { switch {
case !w.session.verified && status.PinRetryCount == 0 && status.PukRetryCount == 0: case !w.session.verified && status.PinRetryCount == 0 && status.PukRetryCount == 0:
return fmt.Sprintf("Bricked, waiting for full wipe"), nil return "Bricked, waiting for full wipe", nil
case !w.session.verified && status.PinRetryCount == 0: case !w.session.verified && status.PinRetryCount == 0:
return fmt.Sprintf("Blocked, waiting for PUK (%d attempts left) and new PIN", status.PukRetryCount), nil return fmt.Sprintf("Blocked, waiting for PUK (%d attempts left) and new PIN", status.PukRetryCount), nil
case !w.session.verified: case !w.session.verified:
return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil
case !status.Initialized: case !status.Initialized:
return fmt.Sprintf("Empty, waiting for initialization"), nil return "Empty, waiting for initialization", nil
default: default:
return fmt.Sprintf("Online"), nil return "Online", nil
} }
} }

View file

@ -6,6 +6,7 @@ clone_depth: 5
version: "{branch}.{build}" version: "{branch}.{build}"
environment: environment:
global: global:
GO111MODULE: on
GOPATH: C:\gopath GOPATH: C:\gopath
CC: gcc.exe CC: gcc.exe
matrix: matrix:
@ -23,8 +24,8 @@ environment:
install: install:
- git submodule update --init - git submodule update --init
- rmdir C:\go /s /q - rmdir C:\go /s /q
- appveyor DownloadFile https://dl.google.com/go/go1.13.8.windows-%GETH_ARCH%.zip - appveyor DownloadFile https://dl.google.com/go/go1.14.2.windows-%GETH_ARCH%.zip
- 7z x go1.13.8.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - 7z x go1.14.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version - go version
- gcc --version - gcc --version

View file

@ -1,19 +1,21 @@
# This file contains sha256 checksums of optional build dependencies. # This file contains sha256 checksums of optional build dependencies.
b13bf04633d4d8cf53226ebeaace8d4d2fd07ae6fa676d0844a688339debec34 go1.13.8.src.tar.gz 98de84e69726a66da7b4e58eac41b99cbe274d7e8906eeb8a5b7eb0aadee7f7c go1.14.2.src.tar.gz
478994633b0f5121a7a8d4f368078093e21014fdc7fb2c0ceeae63668c13c5b6 golangci-lint-1.22.2-freebsd-amd64.tar.gz aeaa5498682246b87d0b77ece283897348ea03d98e816760a074058bfca60b2a golangci-lint-1.24.0-windows-amd64.zip
fcf80824c21567eb0871055711bf9bdca91cf9a081122e2a45f1d11fed754600 golangci-lint-1.22.2-darwin-amd64.tar.gz 7e854a70d449fe77b7a91583ec88c8603eb3bf96c45d52797dc4ba3f2f278dbe golangci-lint-1.24.0-darwin-386.tar.gz
cda85c72fc128b2ea0ae05baea7b91172c63aea34064829f65285f1dd536f1e0 golangci-lint-1.22.2-windows-386.zip 835101fae192c3a2e7a51cb19d5ac3e1a40b0e311955e89bc21d61de78635979 golangci-lint-1.24.0-linux-armv6.tar.gz
94f04899f620aadc9c1524e5482e415efdbd993fa2b2918c4fec2798f030ac1c golangci-lint-1.22.2-linux-armv7.tar.gz a041a6e6a61c9ff3dbe58673af13ea00c76bcd462abede0ade645808e97cdd6d golangci-lint-1.24.0-windows-386.zip
0e72a87d71edde00b6e37e84a99841833ad55fee83e20d21130a7a622b2860bb golangci-lint-1.22.2-freebsd-386.tar.gz 7cc73eb9ca02b7a766c72b913f8080401862b10e7bb90c09b085415a81f21609 golangci-lint-1.24.0-freebsd-armv6.tar.gz
86def2f31fe8fd7c05674104ed2a4bef3e44b7132b93c6ad2f52f198b3d01801 golangci-lint-1.22.2-linux-s390x.tar.gz 537bb2186987b5e68ad4e8829230557f26087c3028eb736dea1662a851bad73d golangci-lint-1.24.0-linux-armv7.tar.gz
b0df4546d36be94e8107733ba290b98dd9b7e41a42d3fb202e87fc7e4ee800c3 golangci-lint-1.22.2-freebsd-armv6.tar.gz 8cb1bc1e63d8f0d9b71fcb10b38887e1646a6b8a120ded2e0cd7c3284528f633 golangci-lint-1.24.0-linux-mips64.tar.gz
3d45958dcf6a8d195086d2fced1a21db42a90815dfd156d180efa62dbdda6724 golangci-lint-1.22.2-darwin-386.tar.gz 095d3f8bf7fc431739861574d0b58d411a617df2ed5698ce5ae5ecc66d23d44d golangci-lint-1.24.0-freebsd-armv7.tar.gz
7ee29f35c74fab017a454237990c74d984ce3855960f2c10509238992bb781f9 golangci-lint-1.22.2-linux-arm64.tar.gz e245df27cec3827aef9e7afbac59e92816978ee3b64f84f7b88562ff4b2ac225 golangci-lint-1.24.0-linux-arm64.tar.gz
52086ac52a502b68578e58e35d3964f127c16d7a90b9ffcb399a004d055ded51 golangci-lint-1.22.2-linux-386.tar.gz 35d6d5927e19f0577cf527f0e4441dbb37701d87e8cf729c98a510fce397fbf7 golangci-lint-1.24.0-linux-ppc64le.tar.gz
c2e4df1fab2ae53762f9baac6041503eeeaa968ce38ea41779f7cb526751c667 golangci-lint-1.22.2-windows-amd64.zip a1ed66353b8ceb575d78db3051491bce3ac1560e469a9bc87e8554486fec7dfe golangci-lint-1.24.0-freebsd-386.tar.gz
109d38cdc89f271392f5a138d6782657157f9f496fd4801956efa2d0428e0cbe golangci-lint-1.22.2-linux-amd64.tar.gz 241ca454102e909de04957ff8a5754c757cefa255758b3e1fba8a4533d19d179 golangci-lint-1.24.0-linux-amd64.tar.gz
f08aae4868d4828c8f07deb0dcd941a1da695b97e58d15e9f3d1d07dcc7a0c84 golangci-lint-1.22.2-linux-armv6.tar.gz ff488423db01a0ec8ffbe4e1d65ef1be6a8d5e6d7930cf380ce8aaf714125470 golangci-lint-1.24.0-linux-386.tar.gz
37af03d9c144d527cb15c46a07e6a22d3f62b5491e34ad6f3bfe6bb0b0b597d4 golangci-lint-1.22.2-linux-ppc64le.tar.gz f05af56f15ebbcf77663a8955d1e39009b584ce8ea4c5583669369d80353a113 golangci-lint-1.24.0-darwin-amd64.tar.gz
251a1081d53944f1d5f86216d752837b23079f90605c9d1cc628da1ffcd2e749 golangci-lint-1.22.2-freebsd-armv7.tar.gz b0096796c0ffcd6c350a2ec006100e7ef5f0597b43a204349d4f997273fb32a7 golangci-lint-1.24.0-freebsd-amd64.tar.gz
c9c2867380e85628813f1f7d1c3cfc6c6f7931e89bea86f567ff451b8cdb6654 golangci-lint-1.24.0-linux-mips64le.tar.gz
2feb97fa61c934aa3eba9bc104ab5dd8fb946791d58e64060e8857e800eeae0b golangci-lint-1.24.0-linux-s390x.tar.gz

View file

@ -325,7 +325,7 @@ func doTest(cmdline []string) {
// Test a single package at a time. CI builders are slow // Test a single package at a time. CI builders are slow
// and some tests run into timeouts under load. // and some tests run into timeouts under load.
gotest := goTool("test", buildFlags(env)...) gotest := goTool("test", buildFlags(env)...)
gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "5m") gotest.Args = append(gotest.Args, "-p", "1")
if *coverage { if *coverage {
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
} }
@ -356,7 +356,7 @@ func doLint(cmdline []string) {
// downloadLinter downloads and unpacks golangci-lint. // downloadLinter downloads and unpacks golangci-lint.
func downloadLinter(cachedir string) string { func downloadLinter(cachedir string) string {
const version = "1.22.2" const version = "1.24.0"
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := build.MustLoadChecksums("build/checksums.txt")
base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH) base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH)

View file

@ -222,11 +222,11 @@ func init() {
utils.LightKDFFlag, utils.LightKDFFlag,
utils.NoUSBFlag, utils.NoUSBFlag,
utils.SmartCardDaemonPathFlag, utils.SmartCardDaemonPathFlag,
utils.RPCListenAddrFlag, utils.HTTPListenAddrFlag,
utils.RPCVirtualHostsFlag, utils.HTTPVirtualHostsFlag,
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.RPCEnabledFlag, utils.HTTPEnabledFlag,
rpcPortFlag, rpcPortFlag,
signerSecretFlag, signerSecretFlag,
customDBFlag, customDBFlag,
@ -579,21 +579,29 @@ func signer(c *cli.Context) error {
Service: api, Service: api,
Version: "1.0"}, Version: "1.0"},
} }
if c.GlobalBool(utils.RPCEnabledFlag.Name) { if c.GlobalBool(utils.HTTPEnabledFlag.Name) {
vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) vhosts := splitAndTrim(c.GlobalString(utils.HTTPVirtualHostsFlag.Name))
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) cors := splitAndTrim(c.GlobalString(utils.HTTPCORSDomainFlag.Name))
srv := rpc.NewServer()
err := node.RegisterApisFromWhitelist(rpcAPI, []string{"account"}, srv, false)
if err != nil {
utils.Fatalf("Could not register API: %w", err)
}
handler := node.NewHTTPHandlerStack(srv, cors, vhosts)
// start http server // start http server
httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts) httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler)
if err != nil { if err != nil {
utils.Fatalf("Could not start RPC api: %v", err) utils.Fatalf("Could not start RPC api: %v", err)
} }
extapiURL = fmt.Sprintf("http://%v/", listener.Addr()) extapiURL = fmt.Sprintf("http://%v/", addr)
log.Info("HTTP endpoint opened", "url", extapiURL) log.Info("HTTP endpoint opened", "url", extapiURL)
defer func() { defer func() {
listener.Close() // Don't bother imposing a timeout here.
httpServer.Shutdown(context.Background())
log.Info("HTTP endpoint closed", "url", extapiURL) log.Info("HTTP endpoint closed", "url", extapiURL)
}() }()
} }

View file

@ -20,14 +20,13 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
) )
type crawler struct { type crawler struct {
input nodeSet input nodeSet
output nodeSet output nodeSet
disc *discover.UDPv4 disc resolver
iters []enode.Iterator iters []enode.Iterator
inputIter enode.Iterator inputIter enode.Iterator
ch chan *enode.Node ch chan *enode.Node
@ -37,7 +36,11 @@ type crawler struct {
revalidateInterval time.Duration revalidateInterval time.Duration
} }
func newCrawler(input nodeSet, disc *discover.UDPv4, iters ...enode.Iterator) *crawler { type resolver interface {
RequestENR(*enode.Node) (*enode.Node, error)
}
func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler {
c := &crawler{ c := &crawler{
input: input, input: input,
output: make(nodeSet, len(input)), output: make(nodeSet, len(input)),

View file

@ -81,6 +81,18 @@ var (
Name: "bootnodes", Name: "bootnodes",
Usage: "Comma separated nodes used for bootstrapping", Usage: "Comma separated nodes used for bootstrapping",
} }
nodekeyFlag = cli.StringFlag{
Name: "nodekey",
Usage: "Hex-encoded node key",
}
nodedbFlag = cli.StringFlag{
Name: "nodedb",
Usage: "Nodes database location",
}
listenAddrFlag = cli.StringFlag{
Name: "addr",
Usage: "Listening address",
}
crawlTimeoutFlag = cli.DurationFlag{ crawlTimeoutFlag = cli.DurationFlag{
Name: "timeout", Name: "timeout",
Usage: "Time limit for the crawl.", Usage: "Time limit for the crawl.",
@ -172,6 +184,62 @@ func discv4Crawl(ctx *cli.Context) error {
return nil return nil
} }
// startV4 starts an ephemeral discovery V4 node.
func startV4(ctx *cli.Context) *discover.UDPv4 {
ln, config := makeDiscoveryConfig(ctx)
socket := listen(ln, ctx.String(listenAddrFlag.Name))
disc, err := discover.ListenV4(socket, ln, config)
if err != nil {
exit(err)
}
return disc
}
func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) {
var cfg discover.Config
if ctx.IsSet(nodekeyFlag.Name) {
key, err := crypto.HexToECDSA(ctx.String(nodekeyFlag.Name))
if err != nil {
exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err))
}
cfg.PrivateKey = key
} else {
cfg.PrivateKey, _ = crypto.GenerateKey()
}
if commandHasFlag(ctx, bootnodesFlag) {
bn, err := parseBootnodes(ctx)
if err != nil {
exit(err)
}
cfg.Bootnodes = bn
}
dbpath := ctx.String(nodedbFlag.Name)
db, err := enode.OpenDB(dbpath)
if err != nil {
exit(err)
}
ln := enode.NewLocalNode(db, cfg.PrivateKey)
return ln, cfg
}
func listen(ln *enode.LocalNode, addr string) *net.UDPConn {
if addr == "" {
addr = "0.0.0.0:0"
}
socket, err := net.ListenPacket("udp4", addr)
if err != nil {
exit(err)
}
usocket := socket.(*net.UDPConn)
uaddr := socket.LocalAddr().(*net.UDPAddr)
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
ln.SetFallbackUDP(uaddr.Port)
return usocket
}
func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
s := params.RinkebyBootnodes s := params.RinkebyBootnodes
if ctx.IsSet(bootnodesFlag.Name) { if ctx.IsSet(bootnodesFlag.Name) {
@ -187,40 +255,3 @@ func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
} }
return nodes, nil return nodes, nil
} }
// startV4 starts an ephemeral discovery V4 node.
func startV4(ctx *cli.Context) *discover.UDPv4 {
socket, ln, cfg, err := listen()
if err != nil {
exit(err)
}
if commandHasFlag(ctx, bootnodesFlag) {
bn, err := parseBootnodes(ctx)
if err != nil {
exit(err)
}
cfg.Bootnodes = bn
}
disc, err := discover.ListenV4(socket, ln, cfg)
if err != nil {
exit(err)
}
return disc
}
func listen() (*net.UDPConn, *enode.LocalNode, discover.Config, error) {
var cfg discover.Config
cfg.PrivateKey, _ = crypto.GenerateKey()
db, _ := enode.OpenDB("")
ln := enode.NewLocalNode(db, cfg.PrivateKey)
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}})
if err != nil {
db.Close()
return nil, nil, cfg, err
}
addr := socket.LocalAddr().(*net.UDPAddr)
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
ln.SetFallbackUDP(addr.Port)
return socket, ln, cfg, nil
}

123
cmd/devp2p/discv5cmd.go Normal file
View file

@ -0,0 +1,123 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/discover"
"gopkg.in/urfave/cli.v1"
)
var (
discv5Command = cli.Command{
Name: "discv5",
Usage: "Node Discovery v5 tools",
Subcommands: []cli.Command{
discv5PingCommand,
discv5ResolveCommand,
discv5CrawlCommand,
discv5ListenCommand,
},
}
discv5PingCommand = cli.Command{
Name: "ping",
Usage: "Sends ping to a node",
Action: discv5Ping,
}
discv5ResolveCommand = cli.Command{
Name: "resolve",
Usage: "Finds a node in the DHT",
Action: discv5Resolve,
Flags: []cli.Flag{bootnodesFlag},
}
discv5CrawlCommand = cli.Command{
Name: "crawl",
Usage: "Updates a nodes.json file with random nodes found in the DHT",
Action: discv5Crawl,
Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag},
}
discv5ListenCommand = cli.Command{
Name: "listen",
Usage: "Runs a node",
Action: discv5Listen,
Flags: []cli.Flag{
bootnodesFlag,
nodekeyFlag,
nodedbFlag,
listenAddrFlag,
},
}
)
func discv5Ping(ctx *cli.Context) error {
n := getNodeArg(ctx)
disc := startV5(ctx)
defer disc.Close()
fmt.Println(disc.Ping(n))
return nil
}
func discv5Resolve(ctx *cli.Context) error {
n := getNodeArg(ctx)
disc := startV5(ctx)
defer disc.Close()
fmt.Println(disc.Resolve(n))
return nil
}
func discv5Crawl(ctx *cli.Context) error {
if ctx.NArg() < 1 {
return fmt.Errorf("need nodes file as argument")
}
nodesFile := ctx.Args().First()
var inputSet nodeSet
if common.FileExist(nodesFile) {
inputSet = loadNodesJSON(nodesFile)
}
disc := startV5(ctx)
defer disc.Close()
c := newCrawler(inputSet, disc, disc.RandomNodes())
c.revalidateInterval = 10 * time.Minute
output := c.run(ctx.Duration(crawlTimeoutFlag.Name))
writeNodesJSON(nodesFile, output)
return nil
}
func discv5Listen(ctx *cli.Context) error {
disc := startV5(ctx)
defer disc.Close()
fmt.Println(disc.Self())
select {}
}
// startV5 starts an ephemeral discovery v5 node.
func startV5(ctx *cli.Context) *discover.UDPv5 {
ln, config := makeDiscoveryConfig(ctx)
socket := listen(ln, ctx.String(listenAddrFlag.Name))
disc, err := discover.ListenV5(socket, ln, config)
if err != nil {
exit(err)
}
return disc
}

View file

@ -59,6 +59,7 @@ func init() {
app.Commands = []cli.Command{ app.Commands = []cli.Command{
enrdumpCommand, enrdumpCommand,
discv4Command, discv4Command,
discv5Command,
dnsCommand, dnsCommand,
nodesetCommand, nodesetCommand,
} }

View file

@ -164,7 +164,7 @@ func ethFilter(args []string) (nodeFilter, error) {
case "goerli": case "goerli":
filter = forkid.NewStaticFilter(params.GoerliChainConfig, params.GoerliGenesisHash) filter = forkid.NewStaticFilter(params.GoerliChainConfig, params.GoerliGenesisHash)
case "ropsten": case "ropsten":
filter = forkid.NewStaticFilter(params.TestnetChainConfig, params.TestnetGenesisHash) filter = forkid.NewStaticFilter(params.RopstenChainConfig, params.RopstenGenesisHash)
default: default:
return nil, fmt.Errorf("unknown network %q", args[0]) return nil, fmt.Errorf("unknown network %q", args[0])
} }

View file

@ -96,7 +96,7 @@ func stateTestCmd(ctx *cli.Context) error {
for _, st := range test.Subtests() { for _, st := range test.Subtests() {
// Run the test and aggregate the result // Run the test and aggregate the result
result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
state, err := test.Run(st, cfg, false) _, state, err := test.Run(st, cfg, false)
// print state root for evmlab tracing // print state root for evmlab tracing
if ctx.GlobalBool(MachineFlag.Name) && state != nil { if ctx.GlobalBool(MachineFlag.Name) && state != nil {
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false)) fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))

View file

@ -88,6 +88,42 @@ Path of the secret key file: .*UTC--.+--[0-9a-f]{40}
`) `)
} }
func TestAccountImport(t *testing.T) {
tests := []struct{ name, key, output string }{
{
name: "correct account",
key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
output: "Address: {fcad0b19bb29d4674531d6f115237e16afce377c}\n",
},
{
name: "invalid character",
key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1",
output: "Fatal: Failed to load the private key: invalid character '1' at end of key file\n",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
importAccountWithExpect(t, test.key, test.output)
})
}
}
func importAccountWithExpect(t *testing.T, key string, expected string) {
dir := tmpdir(t)
keyfile := filepath.Join(dir, "key.prv")
if err := ioutil.WriteFile(keyfile, []byte(key), 0600); err != nil {
t.Error(err)
}
passwordFile := filepath.Join(dir, "password.txt")
if err := ioutil.WriteFile(passwordFile, []byte("foobar"), 0600); err != nil {
t.Error(err)
}
geth := runGeth(t, "account", "import", keyfile, "-password", passwordFile)
defer geth.ExpectExit()
geth.Expect(expected)
}
func TestAccountNewBadRepeat(t *testing.T) { func TestAccountNewBadRepeat(t *testing.T) {
geth := runGeth(t, "account", "new", "--lightkdf") geth := runGeth(t, "account", "new", "--lightkdf")
defer geth.ExpectExit() defer geth.ExpectExit()

View file

@ -157,9 +157,11 @@ The export-preimages command export hash preimages to an RLP encoded stream`,
utils.CacheFlag, utils.CacheFlag,
utils.SyncModeFlag, utils.SyncModeFlag,
utils.FakePoWFlag, utils.FakePoWFlag,
utils.TestnetFlag, utils.RopstenFlag,
utils.RinkebyFlag, utils.RinkebyFlag,
utils.TxLookupLimitFlag, utils.TxLookupLimitFlag,
utils.GoerliFlag,
utils.LegacyTestnetFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
@ -205,9 +207,10 @@ Use "ethereum dump 0" to dump the genesis block.`,
utils.DataDirFlag, utils.DataDirFlag,
utils.AncientFlag, utils.AncientFlag,
utils.CacheFlag, utils.CacheFlag,
utils.TestnetFlag, utils.RopstenFlag,
utils.RinkebyFlag, utils.RinkebyFlag,
utils.GoerliFlag, utils.GoerliFlag,
utils.LegacyTestnetFlag,
utils.SyncModeFlag, utils.SyncModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",

View file

@ -20,7 +20,6 @@ import (
"bufio" "bufio"
"errors" "errors"
"fmt" "fmt"
"math/big"
"os" "os"
"reflect" "reflect"
"unicode" "unicode"
@ -147,12 +146,6 @@ func enableWhisper(ctx *cli.Context) bool {
func makeFullNode(ctx *cli.Context) *node.Node { func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx) stack, cfg := makeConfigNode(ctx)
if ctx.GlobalIsSet(utils.OverrideIstanbulFlag.Name) {
cfg.Eth.OverrideIstanbul = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideIstanbulFlag.Name))
}
if ctx.GlobalIsSet(utils.OverrideMuirGlacierFlag.Name) {
cfg.Eth.OverrideMuirGlacier = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideMuirGlacierFlag.Name))
}
utils.RegisterEthService(stack, &cfg.Eth) utils.RegisterEthService(stack, &cfg.Eth)
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode

View file

@ -123,10 +123,19 @@ func remoteConsole(ctx *cli.Context) error {
path = ctx.GlobalString(utils.DataDirFlag.Name) path = ctx.GlobalString(utils.DataDirFlag.Name)
} }
if path != "" { if path != "" {
if ctx.GlobalBool(utils.TestnetFlag.Name) { if ctx.GlobalBool(utils.LegacyTestnetFlag.Name) || ctx.GlobalBool(utils.RopstenFlag.Name) {
path = filepath.Join(path, "testnet") // Maintain compatibility with older Geth configurations storing the
// Ropsten database in `testnet` instead of `ropsten`.
legacyPath := filepath.Join(path, "testnet")
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
path = legacyPath
} else {
path = filepath.Join(path, "ropsten")
}
} else if ctx.GlobalBool(utils.RinkebyFlag.Name) { } else if ctx.GlobalBool(utils.RinkebyFlag.Name) {
path = filepath.Join(path, "rinkeby") path = filepath.Join(path, "rinkeby")
} else if ctx.GlobalBool(utils.GoerliFlag.Name) {
path = filepath.Join(path, "goerli")
} }
} }
endpoint = fmt.Sprintf("%s/geth.ipc", path) endpoint = fmt.Sprintf("%s/geth.ipc", path)

View file

@ -69,8 +69,6 @@ var (
utils.ExternalSignerFlag, utils.ExternalSignerFlag,
utils.NoUSBFlag, utils.NoUSBFlag,
utils.SmartCardDaemonPathFlag, utils.SmartCardDaemonPathFlag,
utils.OverrideIstanbulFlag,
utils.OverrideMuirGlacierFlag,
utils.EthashCacheDirFlag, utils.EthashCacheDirFlag,
utils.EthashCachesInMemoryFlag, utils.EthashCachesInMemoryFlag,
utils.EthashCachesOnDiskFlag, utils.EthashCachesOnDiskFlag,
@ -96,11 +94,11 @@ var (
utils.SnapshotFlag, utils.SnapshotFlag,
utils.TxLookupLimitFlag, utils.TxLookupLimitFlag,
utils.LightServeFlag, utils.LightServeFlag,
utils.LightLegacyServFlag, utils.LegacyLightServFlag,
utils.LightIngressFlag, utils.LightIngressFlag,
utils.LightEgressFlag, utils.LightEgressFlag,
utils.LightMaxPeersFlag, utils.LightMaxPeersFlag,
utils.LightLegacyPeersFlag, utils.LegacyLightPeersFlag,
utils.LightKDFFlag, utils.LightKDFFlag,
utils.UltraLightServersFlag, utils.UltraLightServersFlag,
utils.UltraLightFractionFlag, utils.UltraLightFractionFlag,
@ -117,17 +115,17 @@ var (
utils.MaxPendingPeersFlag, utils.MaxPendingPeersFlag,
utils.MiningEnabledFlag, utils.MiningEnabledFlag,
utils.MinerThreadsFlag, utils.MinerThreadsFlag,
utils.MinerLegacyThreadsFlag, utils.LegacyMinerThreadsFlag,
utils.MinerNotifyFlag, utils.MinerNotifyFlag,
utils.MinerGasTargetFlag, utils.MinerGasTargetFlag,
utils.MinerLegacyGasTargetFlag, utils.LegacyMinerGasTargetFlag,
utils.MinerGasLimitFlag, utils.MinerGasLimitFlag,
utils.MinerGasPriceFlag, utils.MinerGasPriceFlag,
utils.MinerLegacyGasPriceFlag, utils.LegacyMinerGasPriceFlag,
utils.MinerEtherbaseFlag, utils.MinerEtherbaseFlag,
utils.MinerLegacyEtherbaseFlag, utils.LegacyMinerEtherbaseFlag,
utils.MinerExtraDataFlag, utils.MinerExtraDataFlag,
utils.MinerLegacyExtraDataFlag, utils.LegacyMinerExtraDataFlag,
utils.MinerRecommitIntervalFlag, utils.MinerRecommitIntervalFlag,
utils.MinerNoVerfiyFlag, utils.MinerNoVerfiyFlag,
utils.NATFlag, utils.NATFlag,
@ -139,7 +137,8 @@ var (
utils.DNSDiscoveryFlag, utils.DNSDiscoveryFlag,
utils.DeveloperFlag, utils.DeveloperFlag,
utils.DeveloperPeriodFlag, utils.DeveloperPeriodFlag,
utils.TestnetFlag, utils.LegacyTestnetFlag,
utils.RopstenFlag,
utils.RinkebyFlag, utils.RinkebyFlag,
utils.GoerliFlag, utils.GoerliFlag,
utils.VMEnableDebugFlag, utils.VMEnableDebugFlag,
@ -148,29 +147,41 @@ var (
utils.FakePoWFlag, utils.FakePoWFlag,
utils.NoCompactionFlag, utils.NoCompactionFlag,
utils.GpoBlocksFlag, utils.GpoBlocksFlag,
utils.LegacyGpoBlocksFlag,
utils.GpoPercentileFlag, utils.GpoPercentileFlag,
utils.LegacyGpoPercentileFlag,
utils.EWASMInterpreterFlag, utils.EWASMInterpreterFlag,
utils.EVMInterpreterFlag, utils.EVMInterpreterFlag,
configFileFlag, configFileFlag,
} }
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{
utils.RPCEnabledFlag, utils.HTTPEnabledFlag,
utils.RPCListenAddrFlag, utils.HTTPListenAddrFlag,
utils.RPCPortFlag, utils.HTTPPortFlag,
utils.RPCCORSDomainFlag, utils.HTTPCORSDomainFlag,
utils.RPCVirtualHostsFlag, utils.HTTPVirtualHostsFlag,
utils.LegacyRPCEnabledFlag,
utils.LegacyRPCListenAddrFlag,
utils.LegacyRPCPortFlag,
utils.LegacyRPCCORSDomainFlag,
utils.LegacyRPCVirtualHostsFlag,
utils.GraphQLEnabledFlag, utils.GraphQLEnabledFlag,
utils.GraphQLListenAddrFlag, utils.GraphQLListenAddrFlag,
utils.GraphQLPortFlag, utils.GraphQLPortFlag,
utils.GraphQLCORSDomainFlag, utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag, utils.GraphQLVirtualHostsFlag,
utils.RPCApiFlag, utils.HTTPApiFlag,
utils.LegacyRPCApiFlag,
utils.WSEnabledFlag, utils.WSEnabledFlag,
utils.WSListenAddrFlag, utils.WSListenAddrFlag,
utils.LegacyWSListenAddrFlag,
utils.WSPortFlag, utils.WSPortFlag,
utils.LegacyWSPortFlag,
utils.WSApiFlag, utils.WSApiFlag,
utils.LegacyWSApiFlag,
utils.WSAllowedOriginsFlag, utils.WSAllowedOriginsFlag,
utils.LegacyWSAllowedOriginsFlag,
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.InsecureUnlockAllowedFlag, utils.InsecureUnlockAllowedFlag,
@ -229,6 +240,8 @@ func init() {
dumpConfigCommand, dumpConfigCommand,
// See retesteth.go // See retesteth.go
retestethCommand, retestethCommand,
// See cmd/utils/flags_legacy.go
utils.ShowDeprecated,
} }
sort.Sort(cli.CommandsByName(app.Commands)) sort.Sort(cli.CommandsByName(app.Commands))
@ -236,6 +249,7 @@ func init() {
app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, rpcFlags...)
app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, consoleFlags...)
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, debug.DeprecatedFlags...)
app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, whisperFlags...)
app.Flags = append(app.Flags, metricsFlags...) app.Flags = append(app.Flags, metricsFlags...)
@ -259,10 +273,32 @@ func main() {
// prepare manipulates memory cache allowance and setups metric system. // prepare manipulates memory cache allowance and setups metric system.
// This function should be called before launching devp2p stack. // This function should be called before launching devp2p stack.
func prepare(ctx *cli.Context) { func prepare(ctx *cli.Context) {
// If we're running a known preset, log it for convenience.
switch {
case ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name):
log.Info("Starting Geth on Ropsten testnet...")
log.Warn("The --testnet flag is ambiguous! Please specify one of --goerli, --rinkeby, or --ropsten.")
log.Warn("The generic --testnet flag is deprecated and will be removed in the future!")
case ctx.GlobalIsSet(utils.RopstenFlag.Name):
log.Info("Starting Geth on Ropsten testnet...")
case ctx.GlobalIsSet(utils.RinkebyFlag.Name):
log.Info("Starting Geth on Rinkeby testnet...")
case ctx.GlobalIsSet(utils.GoerliFlag.Name):
log.Info("Starting Geth on Görli testnet...")
case ctx.GlobalIsSet(utils.DeveloperFlag.Name):
log.Info("Starting Geth in ephemeral dev mode...")
case !ctx.GlobalIsSet(utils.NetworkIdFlag.Name):
log.Info("Starting Geth on Ethereum mainnet...")
}
// If we're a full node on mainnet without --cache specified, bump default cache allowance // If we're a full node on mainnet without --cache specified, bump default cache allowance
if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) { if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {
// Make sure we're not on any supported preconfigured testnet either // Make sure we're not on any supported preconfigured testnet either
if !ctx.GlobalIsSet(utils.TestnetFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { if !ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name) && !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) {
// Nope, we're really on mainnet. Bump that cache up! // Nope, we're really on mainnet. Bump that cache up!
log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096) log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096)
ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096)) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096))
@ -279,6 +315,10 @@ func prepare(ctx *cli.Context) {
// Check https://github.com/elastic/gosigar#supported-platforms // Check https://github.com/elastic/gosigar#supported-platforms
if runtime.GOOS != "openbsd" { if runtime.GOOS != "openbsd" {
if err := mem.Get(); err == nil { if err := mem.Get(); err == nil {
if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024)
mem.Total = 2 * 1024 * 1024 * 1024
}
allowance := int(mem.Total / 1024 / 1024 / 3) allowance := int(mem.Total / 1024 / 1024 / 3)
if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance { if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance {
log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
@ -340,7 +380,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
// Set contract backend for ethereum service if local node // Set contract backend for ethereum service if local node
// is serving LES requests. // is serving LES requests.
if ctx.GlobalInt(utils.LightLegacyServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 {
var ethService *eth.Ethereum var ethService *eth.Ethereum
if err := stack.Service(&ethService); err != nil { if err := stack.Service(&ethService); err != nil {
utils.Fatalf("Failed to retrieve ethereum service: %v", err) utils.Fatalf("Failed to retrieve ethereum service: %v", err)
@ -425,16 +465,18 @@ func startNode(ctx *cli.Context, stack *node.Node) {
utils.Fatalf("Ethereum service not running: %v", err) utils.Fatalf("Ethereum service not running: %v", err)
} }
// Set the gas price to the limits from the CLI and start mining // Set the gas price to the limits from the CLI and start mining
gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name) gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
if ctx.IsSet(utils.MinerGasPriceFlag.Name) { if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) {
gasprice = utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name)
} }
ethereum.TxPool().SetGasPrice(gasprice) ethereum.TxPool().SetGasPrice(gasprice)
threads := ctx.GlobalInt(utils.MinerLegacyThreadsFlag.Name) threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)
if ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) {
threads = ctx.GlobalInt(utils.MinerThreadsFlag.Name) threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name)
log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads")
} }
if err := ethereum.StartMining(threads); err != nil { if err := ethereum.StartMining(threads); err != nil {
utils.Fatalf("Failed to start mining: %v", err) utils.Fatalf("Failed to start mining: %v", err)
} }

View file

@ -678,7 +678,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context,
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, statedb, api.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 AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state
@ -788,7 +788,7 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, statedb, api.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 StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state
@ -887,8 +887,16 @@ func retesteth(ctx *cli.Context) error {
Version: "1.0", Version: "1.0",
}, },
} }
vhosts := splitAndTrim(ctx.GlobalString(utils.RPCVirtualHostsFlag.Name)) vhosts := splitAndTrim(ctx.GlobalString(utils.HTTPVirtualHostsFlag.Name))
cors := splitAndTrim(ctx.GlobalString(utils.RPCCORSDomainFlag.Name)) cors := splitAndTrim(ctx.GlobalString(utils.HTTPCORSDomainFlag.Name))
// register apis and create handler stack
srv := rpc.NewServer()
err := node.RegisterApisFromWhitelist(rpcAPI, []string{"test", "eth", "debug", "web3"}, srv, false)
if err != nil {
utils.Fatalf("Could not register RPC apis: %w", err)
}
handler := node.NewHTTPHandlerStack(srv, cors, vhosts)
// start http server // start http server
var RetestethHTTPTimeouts = rpc.HTTPTimeouts{ var RetestethHTTPTimeouts = rpc.HTTPTimeouts{
@ -896,8 +904,8 @@ func retesteth(ctx *cli.Context) error {
WriteTimeout: 120 * time.Second, WriteTimeout: 120 * time.Second,
IdleTimeout: 120 * time.Second, IdleTimeout: 120 * time.Second,
} }
httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.RPCListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name)) httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.HTTPListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name))
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"test", "eth", "debug", "web3"}, cors, vhosts, RetestethHTTPTimeouts) httpServer, _, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler)
if err != nil { if err != nil {
utils.Fatalf("Could not start RPC api: %v", err) utils.Fatalf("Could not start RPC api: %v", err)
} }
@ -905,7 +913,8 @@ func retesteth(ctx *cli.Context) error {
log.Info("HTTP endpoint opened", "url", extapiURL) log.Info("HTTP endpoint opened", "url", extapiURL)
defer func() { defer func() {
listener.Close() // Don't bother imposing a timeout here.
httpServer.Shutdown(context.Background())
log.Info("HTTP endpoint closed", "url", httpEndpoint) log.Info("HTTP endpoint closed", "url", httpEndpoint)
}() }()

View file

@ -72,9 +72,9 @@ var AppHelpFlagGroups = []flagGroup{
utils.NoUSBFlag, utils.NoUSBFlag,
utils.SmartCardDaemonPathFlag, utils.SmartCardDaemonPathFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.TestnetFlag,
utils.RinkebyFlag,
utils.GoerliFlag, utils.GoerliFlag,
utils.RinkebyFlag,
utils.RopstenFlag,
utils.SyncModeFlag, utils.SyncModeFlag,
utils.ExitWhenSyncedFlag, utils.ExitWhenSyncedFlag,
utils.GCModeFlag, utils.GCModeFlag,
@ -158,13 +158,12 @@ var AppHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.RPCEnabledFlag, utils.HTTPEnabledFlag,
utils.RPCListenAddrFlag, utils.HTTPListenAddrFlag,
utils.RPCPortFlag, utils.HTTPPortFlag,
utils.RPCApiFlag, utils.HTTPApiFlag,
utils.RPCGlobalGasCap, utils.HTTPCORSDomainFlag,
utils.RPCCORSDomainFlag, utils.HTTPVirtualHostsFlag,
utils.RPCVirtualHostsFlag,
utils.WSEnabledFlag, utils.WSEnabledFlag,
utils.WSListenAddrFlag, utils.WSListenAddrFlag,
utils.WSPortFlag, utils.WSPortFlag,
@ -175,6 +174,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.GraphQLPortFlag, utils.GraphQLPortFlag,
utils.GraphQLCORSDomainFlag, utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag, utils.GraphQLVirtualHostsFlag,
utils.RPCGlobalGasCap,
utils.JSpathFlag, utils.JSpathFlag,
utils.ExecFlag, utils.ExecFlag,
utils.PreloadJSFlag, utils.PreloadJSFlag,
@ -244,19 +244,28 @@ var AppHelpFlagGroups = []flagGroup{
Flags: whisperFlags, Flags: whisperFlags,
}, },
{ {
Name: "DEPRECATED", Name: "ALIASED (deprecated)",
Flags: []cli.Flag{ Flags: append([]cli.Flag{
utils.LightLegacyServFlag, utils.LegacyRPCEnabledFlag,
utils.LightLegacyPeersFlag, utils.LegacyRPCListenAddrFlag,
utils.MinerLegacyThreadsFlag, utils.LegacyRPCPortFlag,
utils.MinerLegacyGasTargetFlag, utils.LegacyRPCCORSDomainFlag,
utils.MinerLegacyGasPriceFlag, utils.LegacyRPCVirtualHostsFlag,
utils.MinerLegacyEtherbaseFlag, utils.LegacyRPCApiFlag,
utils.MinerLegacyExtraDataFlag, utils.LegacyWSListenAddrFlag,
}, utils.LegacyWSPortFlag,
utils.LegacyWSAllowedOriginsFlag,
utils.LegacyWSApiFlag,
utils.LegacyGpoBlocksFlag,
utils.LegacyGpoPercentileFlag,
}, debug.DeprecatedFlags...),
}, },
{ {
Name: "MISC", Name: "MISC",
Flags: []cli.Flag{
utils.SnapshotFlag,
cli.HelpFlag,
},
}, },
} }
@ -314,10 +323,17 @@ func init() {
categorized[flag.String()] = struct{}{} categorized[flag.String()] = struct{}{}
} }
} }
deprecated := make(map[string]struct{})
for _, flag := range utils.DeprecatedFlags {
deprecated[flag.String()] = struct{}{}
}
// Only add uncategorized flags if they are not deprecated
var uncategorized []cli.Flag var uncategorized []cli.Flag
for _, flag := range data.(*cli.App).Flags { for _, flag := range data.(*cli.App).Flags {
if _, ok := categorized[flag.String()]; !ok { if _, ok := categorized[flag.String()]; !ok {
uncategorized = append(uncategorized, flag) if _, ok := deprecated[flag.String()]; !ok {
uncategorized = append(uncategorized, flag)
}
} }
} }
if len(uncategorized) > 0 { if len(uncategorized) > 0 {

View file

@ -182,11 +182,11 @@ func checkWallet(client *sshClient, network string) (*walletInfos, error) {
// Run a sanity check to see if the devp2p and RPC ports are reachable // Run a sanity check to see if the devp2p and RPC ports are reachable
nodePort := infos.portmap[infos.envvars["NODE_PORT"]] nodePort := infos.portmap[infos.envvars["NODE_PORT"]]
if err = checkPort(client.server, nodePort); err != nil { if err = checkPort(client.server, nodePort); err != nil {
log.Warn(fmt.Sprintf("Wallet devp2p port seems unreachable"), "server", client.server, "port", nodePort, "err", err) log.Warn("Wallet devp2p port seems unreachable", "server", client.server, "port", nodePort, "err", err)
} }
rpcPort := infos.portmap["8545/tcp"] rpcPort := infos.portmap["8545/tcp"]
if err = checkPort(client.server, rpcPort); err != nil { if err = checkPort(client.server, rpcPort); err != nil {
log.Warn(fmt.Sprintf("Wallet RPC port seems unreachable"), "server", client.server, "port", rpcPort, "err", err) log.Warn("Wallet RPC port seems unreachable", "server", client.server, "port", rpcPort, "err", err)
} }
// Assemble and return the useful infos // Assemble and return the useful infos
stats := &walletInfos{ stats := &walletInfos{

View file

@ -301,7 +301,7 @@ func ExportPreimages(db ethdb.Database, fn string) error {
defer writer.(*gzip.Writer).Close() defer writer.(*gzip.Writer).Close()
} }
// Iterate over the preimages and export them // Iterate over the preimages and export them
it := db.NewIteratorWithPrefix([]byte("secure-key-")) it := db.NewIterator([]byte("secure-key-"), nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {

View file

@ -162,20 +162,20 @@ var (
} }
NetworkIdFlag = cli.Uint64Flag{ NetworkIdFlag = cli.Uint64Flag{
Name: "networkid", Name: "networkid",
Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby)", Usage: "Network identifier (integer, 1=Frontier, 3=Ropsten, 4=Rinkeby, 5=Görli)",
Value: eth.DefaultConfig.NetworkId, Value: eth.DefaultConfig.NetworkId,
} }
TestnetFlag = cli.BoolFlag{ GoerliFlag = cli.BoolFlag{
Name: "testnet", Name: "goerli",
Usage: "Ropsten network: pre-configured proof-of-work test network", Usage: "Görli network: pre-configured proof-of-authority test network",
} }
RinkebyFlag = cli.BoolFlag{ RinkebyFlag = cli.BoolFlag{
Name: "rinkeby", Name: "rinkeby",
Usage: "Rinkeby network: pre-configured proof-of-authority test network", Usage: "Rinkeby network: pre-configured proof-of-authority test network",
} }
GoerliFlag = cli.BoolFlag{ RopstenFlag = cli.BoolFlag{
Name: "goerli", Name: "ropsten",
Usage: "Görli network: pre-configured proof-of-authority test network", Usage: "Ropsten network: pre-configured proof-of-work test network",
} }
DeveloperFlag = cli.BoolFlag{ DeveloperFlag = cli.BoolFlag{
Name: "dev", Name: "dev",
@ -242,20 +242,7 @@ var (
Name: "whitelist", Name: "whitelist",
Usage: "Comma separated block number-to-hash mappings to enforce (<number>=<hash>)", Usage: "Comma separated block number-to-hash mappings to enforce (<number>=<hash>)",
} }
OverrideIstanbulFlag = cli.Uint64Flag{
Name: "override.istanbul",
Usage: "Manually specify Istanbul fork-block, overriding the bundled setting",
}
OverrideMuirGlacierFlag = cli.Uint64Flag{
Name: "override.muirglacier",
Usage: "Manually specify Muir Glacier fork-block, overriding the bundled setting",
}
// Light server and client settings // Light server and client settings
LightLegacyServFlag = cli.IntFlag{ // Deprecated in favor of light.serve, remove in 2021
Name: "lightserv",
Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)",
Value: eth.DefaultConfig.LightServ,
}
LightServeFlag = cli.IntFlag{ LightServeFlag = cli.IntFlag{
Name: "light.serve", Name: "light.serve",
Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)", Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)",
@ -271,11 +258,6 @@ var (
Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)",
Value: eth.DefaultConfig.LightEgress, Value: eth.DefaultConfig.LightEgress,
} }
LightLegacyPeersFlag = cli.IntFlag{ // Deprecated in favor of light.maxpeers, remove in 2021
Name: "lightpeers",
Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated, use --light.maxpeers)",
Value: eth.DefaultConfig.LightPeers,
}
LightMaxPeersFlag = cli.IntFlag{ LightMaxPeersFlag = cli.IntFlag{
Name: "light.maxpeers", Name: "light.maxpeers",
Usage: "Maximum number of light clients to serve, or light servers to attach to", Usage: "Maximum number of light clients to serve, or light servers to attach to",
@ -427,11 +409,6 @@ var (
Usage: "Number of CPU threads to use for mining", Usage: "Number of CPU threads to use for mining",
Value: 0, Value: 0,
} }
MinerLegacyThreadsFlag = cli.IntFlag{
Name: "minerthreads",
Usage: "Number of CPU threads to use for mining (deprecated, use --miner.threads)",
Value: 0,
}
MinerNotifyFlag = cli.StringFlag{ MinerNotifyFlag = cli.StringFlag{
Name: "miner.notify", Name: "miner.notify",
Usage: "Comma separated HTTP URL list to notify of new work packages", Usage: "Comma separated HTTP URL list to notify of new work packages",
@ -441,11 +418,6 @@ var (
Usage: "Target gas floor for mined blocks", Usage: "Target gas floor for mined blocks",
Value: eth.DefaultConfig.Miner.GasFloor, Value: eth.DefaultConfig.Miner.GasFloor,
} }
MinerLegacyGasTargetFlag = cli.Uint64Flag{
Name: "targetgaslimit",
Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)",
Value: eth.DefaultConfig.Miner.GasFloor,
}
MinerGasLimitFlag = cli.Uint64Flag{ MinerGasLimitFlag = cli.Uint64Flag{
Name: "miner.gaslimit", Name: "miner.gaslimit",
Usage: "Target gas ceiling for mined blocks", Usage: "Target gas ceiling for mined blocks",
@ -456,29 +428,15 @@ var (
Usage: "Minimum gas price for mining a transaction", Usage: "Minimum gas price for mining a transaction",
Value: eth.DefaultConfig.Miner.GasPrice, Value: eth.DefaultConfig.Miner.GasPrice,
} }
MinerLegacyGasPriceFlag = BigFlag{
Name: "gasprice",
Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)",
Value: eth.DefaultConfig.Miner.GasPrice,
}
MinerEtherbaseFlag = cli.StringFlag{ MinerEtherbaseFlag = cli.StringFlag{
Name: "miner.etherbase", Name: "miner.etherbase",
Usage: "Public address for block mining rewards (default = first account)", Usage: "Public address for block mining rewards (default = first account)",
Value: "0", Value: "0",
} }
MinerLegacyEtherbaseFlag = cli.StringFlag{
Name: "etherbase",
Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)",
Value: "0",
}
MinerExtraDataFlag = cli.StringFlag{ MinerExtraDataFlag = cli.StringFlag{
Name: "miner.extradata", Name: "miner.extradata",
Usage: "Block extra data set by the miner (default = client version)", Usage: "Block extra data set by the miner (default = client version)",
} }
MinerLegacyExtraDataFlag = cli.StringFlag{
Name: "extradata",
Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)",
}
MinerRecommitIntervalFlag = cli.DurationFlag{ MinerRecommitIntervalFlag = cli.DurationFlag{
Name: "miner.recommit", Name: "miner.recommit",
Usage: "Time interval to recreate the block being mined", Usage: "Time interval to recreate the block being mined",
@ -538,32 +496,32 @@ var (
Name: "ipcpath", Name: "ipcpath",
Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)", Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)",
} }
RPCEnabledFlag = cli.BoolFlag{ HTTPEnabledFlag = cli.BoolFlag{
Name: "rpc", Name: "http",
Usage: "Enable the HTTP-RPC server", Usage: "Enable the HTTP-RPC server",
} }
RPCListenAddrFlag = cli.StringFlag{ HTTPListenAddrFlag = cli.StringFlag{
Name: "rpcaddr", Name: "http.addr",
Usage: "HTTP-RPC server listening interface", Usage: "HTTP-RPC server listening interface",
Value: node.DefaultHTTPHost, Value: node.DefaultHTTPHost,
} }
RPCPortFlag = cli.IntFlag{ HTTPPortFlag = cli.IntFlag{
Name: "rpcport", Name: "http.port",
Usage: "HTTP-RPC server listening port", Usage: "HTTP-RPC server listening port",
Value: node.DefaultHTTPPort, Value: node.DefaultHTTPPort,
} }
RPCCORSDomainFlag = cli.StringFlag{ HTTPCORSDomainFlag = cli.StringFlag{
Name: "rpccorsdomain", Name: "http.corsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
Value: "", Value: "",
} }
RPCVirtualHostsFlag = cli.StringFlag{ HTTPVirtualHostsFlag = cli.StringFlag{
Name: "rpcvhosts", Name: "http.vhosts",
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","), Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","),
} }
RPCApiFlag = cli.StringFlag{ HTTPApiFlag = cli.StringFlag{
Name: "rpcapi", Name: "http.api",
Usage: "API's offered over the HTTP-RPC interface", Usage: "API's offered over the HTTP-RPC interface",
Value: "", Value: "",
} }
@ -572,22 +530,22 @@ var (
Usage: "Enable the WS-RPC server", Usage: "Enable the WS-RPC server",
} }
WSListenAddrFlag = cli.StringFlag{ WSListenAddrFlag = cli.StringFlag{
Name: "wsaddr", Name: "ws.addr",
Usage: "WS-RPC server listening interface", Usage: "WS-RPC server listening interface",
Value: node.DefaultWSHost, Value: node.DefaultWSHost,
} }
WSPortFlag = cli.IntFlag{ WSPortFlag = cli.IntFlag{
Name: "wsport", Name: "ws.port",
Usage: "WS-RPC server listening port", Usage: "WS-RPC server listening port",
Value: node.DefaultWSPort, Value: node.DefaultWSPort,
} }
WSApiFlag = cli.StringFlag{ WSApiFlag = cli.StringFlag{
Name: "wsapi", Name: "ws.api",
Usage: "API's offered over the WS-RPC interface", Usage: "API's offered over the WS-RPC interface",
Value: "", Value: "",
} }
WSAllowedOriginsFlag = cli.StringFlag{ WSAllowedOriginsFlag = cli.StringFlag{
Name: "wsorigins", Name: "ws.origins",
Usage: "Origins from which to accept websockets requests", Usage: "Origins from which to accept websockets requests",
Value: "", Value: "",
} }
@ -694,12 +652,12 @@ var (
// Gas price oracle settings // Gas price oracle settings
GpoBlocksFlag = cli.IntFlag{ GpoBlocksFlag = cli.IntFlag{
Name: "gpoblocks", Name: "gpo.blocks",
Usage: "Number of recent blocks to check for gas prices", Usage: "Number of recent blocks to check for gas prices",
Value: eth.DefaultConfig.GPO.Blocks, Value: eth.DefaultConfig.GPO.Blocks,
} }
GpoPercentileFlag = cli.IntFlag{ GpoPercentileFlag = cli.IntFlag{
Name: "gpopercentile", Name: "gpo.percentile",
Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices", Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices",
Value: eth.DefaultConfig.GPO.Percentile, Value: eth.DefaultConfig.GPO.Percentile,
} }
@ -764,7 +722,6 @@ var (
Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements",
Value: "host=localhost", Value: "host=localhost",
} }
EWASMInterpreterFlag = cli.StringFlag{ EWASMInterpreterFlag = cli.StringFlag{
Name: "vm.ewasm", Name: "vm.ewasm",
Usage: "External ewasm configuration (default = built-in interpreter)", Usage: "External ewasm configuration (default = built-in interpreter)",
@ -779,11 +736,17 @@ var (
// MakeDataDir retrieves the currently requested data directory, terminating // MakeDataDir retrieves the currently requested data directory, terminating
// if none (or the empty string) is specified. If the node is starting a testnet, // if none (or the empty string) is specified. If the node is starting a testnet,
// the a subdirectory of the specified datadir will be used. // then a subdirectory of the specified datadir will be used.
func MakeDataDir(ctx *cli.Context) string { func MakeDataDir(ctx *cli.Context) string {
if path := ctx.GlobalString(DataDirFlag.Name); path != "" { if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
if ctx.GlobalBool(TestnetFlag.Name) { if ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name) {
return filepath.Join(path, "testnet") // Maintain compatibility with older Geth configurations storing the
// Ropsten database in `testnet` instead of `ropsten`.
legacyPath := filepath.Join(path, "testnet")
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
return legacyPath
}
return filepath.Join(path, "ropsten")
} }
if ctx.GlobalBool(RinkebyFlag.Name) { if ctx.GlobalBool(RinkebyFlag.Name) {
return filepath.Join(path, "rinkeby") return filepath.Join(path, "rinkeby")
@ -841,8 +804,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
} else { } else {
urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
} }
case ctx.GlobalBool(TestnetFlag.Name): case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
urls = params.TestnetBootnodes urls = params.RopstenBootnodes
case ctx.GlobalBool(RinkebyFlag.Name): case ctx.GlobalBool(RinkebyFlag.Name):
urls = params.RinkebyBootnodes urls = params.RinkebyBootnodes
case ctx.GlobalBool(GoerliFlag.Name): case ctx.GlobalBool(GoerliFlag.Name):
@ -928,23 +891,51 @@ func splitAndTrim(input string) []string {
// setHTTP creates the HTTP RPC listener interface string from the set // setHTTP creates the HTTP RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled. // command line flags, returning empty if the HTTP endpoint is disabled.
func setHTTP(ctx *cli.Context, cfg *node.Config) { func setHTTP(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(RPCEnabledFlag.Name) && cfg.HTTPHost == "" { if ctx.GlobalBool(LegacyRPCEnabledFlag.Name) && cfg.HTTPHost == "" {
log.Warn("The flag --rpc is deprecated and will be removed in the future, please use --http")
cfg.HTTPHost = "127.0.0.1" cfg.HTTPHost = "127.0.0.1"
if ctx.GlobalIsSet(RPCListenAddrFlag.Name) { if ctx.GlobalIsSet(LegacyRPCListenAddrFlag.Name) {
cfg.HTTPHost = ctx.GlobalString(RPCListenAddrFlag.Name) cfg.HTTPHost = ctx.GlobalString(LegacyRPCListenAddrFlag.Name)
log.Warn("The flag --rpcaddr is deprecated and will be removed in the future, please use --http.addr")
} }
} }
if ctx.GlobalIsSet(RPCPortFlag.Name) { if ctx.GlobalBool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" {
cfg.HTTPPort = ctx.GlobalInt(RPCPortFlag.Name) cfg.HTTPHost = "127.0.0.1"
if ctx.GlobalIsSet(HTTPListenAddrFlag.Name) {
cfg.HTTPHost = ctx.GlobalString(HTTPListenAddrFlag.Name)
}
} }
if ctx.GlobalIsSet(RPCCORSDomainFlag.Name) {
cfg.HTTPCors = splitAndTrim(ctx.GlobalString(RPCCORSDomainFlag.Name)) if ctx.GlobalIsSet(LegacyRPCPortFlag.Name) {
cfg.HTTPPort = ctx.GlobalInt(LegacyRPCPortFlag.Name)
log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port")
} }
if ctx.GlobalIsSet(RPCApiFlag.Name) { if ctx.GlobalIsSet(HTTPPortFlag.Name) {
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name)) cfg.HTTPPort = ctx.GlobalInt(HTTPPortFlag.Name)
} }
if ctx.GlobalIsSet(RPCVirtualHostsFlag.Name) {
cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name)) if ctx.GlobalIsSet(LegacyRPCCORSDomainFlag.Name) {
cfg.HTTPCors = splitAndTrim(ctx.GlobalString(LegacyRPCCORSDomainFlag.Name))
log.Warn("The flag --rpccorsdomain is deprecated and will be removed in the future, please use --http.corsdomain")
}
if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) {
cfg.HTTPCors = splitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name))
}
if ctx.GlobalIsSet(LegacyRPCApiFlag.Name) {
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(LegacyRPCApiFlag.Name))
log.Warn("The flag --rpcapi is deprecated and will be removed in the future, please use --http.api")
}
if ctx.GlobalIsSet(HTTPApiFlag.Name) {
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(HTTPApiFlag.Name))
}
if ctx.GlobalIsSet(LegacyRPCVirtualHostsFlag.Name) {
cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(LegacyRPCVirtualHostsFlag.Name))
log.Warn("The flag --rpcvhosts is deprecated and will be removed in the future, please use --http.vhosts")
}
if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) {
cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name))
} }
} }
@ -971,16 +962,34 @@ func setGraphQL(ctx *cli.Context, cfg *node.Config) {
func setWS(ctx *cli.Context, cfg *node.Config) { func setWS(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(WSEnabledFlag.Name) && cfg.WSHost == "" { if ctx.GlobalBool(WSEnabledFlag.Name) && cfg.WSHost == "" {
cfg.WSHost = "127.0.0.1" cfg.WSHost = "127.0.0.1"
if ctx.GlobalIsSet(LegacyWSListenAddrFlag.Name) {
cfg.WSHost = ctx.GlobalString(LegacyWSListenAddrFlag.Name)
log.Warn("The flag --wsaddr is deprecated and will be removed in the future, please use --ws.addr")
}
if ctx.GlobalIsSet(WSListenAddrFlag.Name) { if ctx.GlobalIsSet(WSListenAddrFlag.Name) {
cfg.WSHost = ctx.GlobalString(WSListenAddrFlag.Name) cfg.WSHost = ctx.GlobalString(WSListenAddrFlag.Name)
} }
} }
if ctx.GlobalIsSet(LegacyWSPortFlag.Name) {
cfg.WSPort = ctx.GlobalInt(LegacyWSPortFlag.Name)
log.Warn("The flag --wsport is deprecated and will be removed in the future, please use --ws.port")
}
if ctx.GlobalIsSet(WSPortFlag.Name) { if ctx.GlobalIsSet(WSPortFlag.Name) {
cfg.WSPort = ctx.GlobalInt(WSPortFlag.Name) cfg.WSPort = ctx.GlobalInt(WSPortFlag.Name)
} }
if ctx.GlobalIsSet(LegacyWSAllowedOriginsFlag.Name) {
cfg.WSOrigins = splitAndTrim(ctx.GlobalString(LegacyWSAllowedOriginsFlag.Name))
log.Warn("The flag --wsorigins is deprecated and will be removed in the future, please use --ws.origins")
}
if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) { if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) {
cfg.WSOrigins = splitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name)) cfg.WSOrigins = splitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name))
} }
if ctx.GlobalIsSet(LegacyWSApiFlag.Name) {
cfg.WSModules = splitAndTrim(ctx.GlobalString(LegacyWSApiFlag.Name))
log.Warn("The flag --wsapi is deprecated and will be removed in the future, please use --ws.api")
}
if ctx.GlobalIsSet(WSApiFlag.Name) { if ctx.GlobalIsSet(WSApiFlag.Name) {
cfg.WSModules = splitAndTrim(ctx.GlobalString(WSApiFlag.Name)) cfg.WSModules = splitAndTrim(ctx.GlobalString(WSApiFlag.Name))
} }
@ -1000,8 +1009,9 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
// setLes configures the les server and ultra light client settings from the command line flags. // setLes configures the les server and ultra light client settings from the command line flags.
func setLes(ctx *cli.Context, cfg *eth.Config) { func setLes(ctx *cli.Context, cfg *eth.Config) {
if ctx.GlobalIsSet(LightLegacyServFlag.Name) { if ctx.GlobalIsSet(LegacyLightServFlag.Name) {
cfg.LightServ = ctx.GlobalInt(LightLegacyServFlag.Name) cfg.LightServ = ctx.GlobalInt(LegacyLightServFlag.Name)
log.Warn("The flag --lightserv is deprecated and will be removed in the future, please use --light.serve")
} }
if ctx.GlobalIsSet(LightServeFlag.Name) { if ctx.GlobalIsSet(LightServeFlag.Name) {
cfg.LightServ = ctx.GlobalInt(LightServeFlag.Name) cfg.LightServ = ctx.GlobalInt(LightServeFlag.Name)
@ -1012,8 +1022,9 @@ func setLes(ctx *cli.Context, cfg *eth.Config) {
if ctx.GlobalIsSet(LightEgressFlag.Name) { if ctx.GlobalIsSet(LightEgressFlag.Name) {
cfg.LightEgress = ctx.GlobalInt(LightEgressFlag.Name) cfg.LightEgress = ctx.GlobalInt(LightEgressFlag.Name)
} }
if ctx.GlobalIsSet(LightLegacyPeersFlag.Name) { if ctx.GlobalIsSet(LegacyLightPeersFlag.Name) {
cfg.LightPeers = ctx.GlobalInt(LightLegacyPeersFlag.Name) cfg.LightPeers = ctx.GlobalInt(LegacyLightPeersFlag.Name)
log.Warn("The flag --lightpeers is deprecated and will be removed in the future, please use --light.maxpeers")
} }
if ctx.GlobalIsSet(LightMaxPeersFlag.Name) { if ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
cfg.LightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name) cfg.LightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name)
@ -1077,8 +1088,10 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
// Extract the current etherbase, new flag overriding legacy one // Extract the current etherbase, new flag overriding legacy one
var etherbase string var etherbase string
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) { if ctx.GlobalIsSet(LegacyMinerEtherbaseFlag.Name) {
etherbase = ctx.GlobalString(MinerLegacyEtherbaseFlag.Name) etherbase = ctx.GlobalString(LegacyMinerEtherbaseFlag.Name)
log.Warn("The flag --etherbase is deprecated and will be removed in the future, please use --miner.etherbase")
} }
if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) { if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) {
etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name) etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name)
@ -1123,27 +1136,27 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setBootstrapNodesV5(ctx, cfg) setBootstrapNodesV5(ctx, cfg)
lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light" lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light"
lightServer := (ctx.GlobalInt(LightLegacyServFlag.Name) != 0 || ctx.GlobalInt(LightServeFlag.Name) != 0) lightServer := (ctx.GlobalInt(LegacyLightServFlag.Name) != 0 || ctx.GlobalInt(LightServeFlag.Name) != 0)
lightPeers := ctx.GlobalInt(LightLegacyPeersFlag.Name) lightPeers := ctx.GlobalInt(LegacyLightPeersFlag.Name)
if ctx.GlobalIsSet(LightMaxPeersFlag.Name) { if ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
lightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name) lightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name)
} }
if lightClient && !ctx.GlobalIsSet(LightLegacyPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { if lightClient && !ctx.GlobalIsSet(LegacyLightPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
// dynamic default - for clients we use 1/10th of the default for servers // dynamic default - for clients we use 1/10th of the default for servers
lightPeers /= 10 lightPeers /= 10
} }
if ctx.GlobalIsSet(MaxPeersFlag.Name) { if ctx.GlobalIsSet(MaxPeersFlag.Name) {
cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name) cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name)
if lightServer && !ctx.GlobalIsSet(LightLegacyPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { if lightServer && !ctx.GlobalIsSet(LegacyLightPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
cfg.MaxPeers += lightPeers cfg.MaxPeers += lightPeers
} }
} else { } else {
if lightServer { if lightServer {
cfg.MaxPeers += lightPeers cfg.MaxPeers += lightPeers
} }
if lightClient && (ctx.GlobalIsSet(LightLegacyPeersFlag.Name) || ctx.GlobalIsSet(LightMaxPeersFlag.Name)) && cfg.MaxPeers < lightPeers { if lightClient && (ctx.GlobalIsSet(LegacyLightPeersFlag.Name) || ctx.GlobalIsSet(LightMaxPeersFlag.Name)) && cfg.MaxPeers < lightPeers {
cfg.MaxPeers = lightPeers cfg.MaxPeers = lightPeers
} }
} }
@ -1245,8 +1258,16 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) {
cfg.DataDir = ctx.GlobalString(DataDirFlag.Name) cfg.DataDir = ctx.GlobalString(DataDirFlag.Name)
case ctx.GlobalBool(DeveloperFlag.Name): case ctx.GlobalBool(DeveloperFlag.Name):
cfg.DataDir = "" // unless explicitly requested, use memory databases cfg.DataDir = "" // unless explicitly requested, use memory databases
case ctx.GlobalBool(TestnetFlag.Name) && cfg.DataDir == node.DefaultDataDir(): case (ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name)) && cfg.DataDir == node.DefaultDataDir():
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet") // Maintain compatibility with older Geth configurations storing the
// Ropsten database in `testnet` instead of `ropsten`.
legacyPath := filepath.Join(node.DefaultDataDir(), "testnet")
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
log.Warn("Using the deprecated `testnet` datadir. Future versions will store the Ropsten chain in `ropsten`.")
cfg.DataDir = legacyPath
} else {
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten")
}
case ctx.GlobalBool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): case ctx.GlobalBool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir():
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby")
case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir():
@ -1255,9 +1276,18 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) {
} }
func setGPO(ctx *cli.Context, cfg *gasprice.Config) { func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
if ctx.GlobalIsSet(LegacyGpoBlocksFlag.Name) {
cfg.Blocks = ctx.GlobalInt(LegacyGpoBlocksFlag.Name)
log.Warn("The flag --gpoblocks is deprecated and will be removed in the future, please use --gpo.blocks")
}
if ctx.GlobalIsSet(GpoBlocksFlag.Name) { if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name) cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
} }
if ctx.GlobalIsSet(LegacyGpoPercentileFlag.Name) {
cfg.Percentile = ctx.GlobalInt(LegacyGpoPercentileFlag.Name)
log.Warn("The flag --gpopercentile is deprecated and will be removed in the future, please use --gpo.percentile")
}
if ctx.GlobalIsSet(GpoPercentileFlag.Name) { if ctx.GlobalIsSet(GpoPercentileFlag.Name) {
cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name) cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name)
} }
@ -1337,14 +1367,16 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.GlobalIsSet(MinerNotifyFlag.Name) { if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",") cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
} }
if ctx.GlobalIsSet(MinerLegacyExtraDataFlag.Name) { if ctx.GlobalIsSet(LegacyMinerExtraDataFlag.Name) {
cfg.ExtraData = []byte(ctx.GlobalString(MinerLegacyExtraDataFlag.Name)) cfg.ExtraData = []byte(ctx.GlobalString(LegacyMinerExtraDataFlag.Name))
log.Warn("The flag --extradata is deprecated and will be removed in the future, please use --miner.extradata")
} }
if ctx.GlobalIsSet(MinerExtraDataFlag.Name) { if ctx.GlobalIsSet(MinerExtraDataFlag.Name) {
cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name)) cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name))
} }
if ctx.GlobalIsSet(MinerLegacyGasTargetFlag.Name) { if ctx.GlobalIsSet(LegacyMinerGasTargetFlag.Name) {
cfg.GasFloor = ctx.GlobalUint64(MinerLegacyGasTargetFlag.Name) cfg.GasFloor = ctx.GlobalUint64(LegacyMinerGasTargetFlag.Name)
log.Warn("The flag --targetgaslimit is deprecated and will be removed in the future, please use --miner.gastarget")
} }
if ctx.GlobalIsSet(MinerGasTargetFlag.Name) { if ctx.GlobalIsSet(MinerGasTargetFlag.Name) {
cfg.GasFloor = ctx.GlobalUint64(MinerGasTargetFlag.Name) cfg.GasFloor = ctx.GlobalUint64(MinerGasTargetFlag.Name)
@ -1352,8 +1384,9 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.GlobalIsSet(MinerGasLimitFlag.Name) { if ctx.GlobalIsSet(MinerGasLimitFlag.Name) {
cfg.GasCeil = ctx.GlobalUint64(MinerGasLimitFlag.Name) cfg.GasCeil = ctx.GlobalUint64(MinerGasLimitFlag.Name)
} }
if ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { if ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) {
cfg.GasPrice = GlobalBig(ctx, MinerLegacyGasPriceFlag.Name) cfg.GasPrice = GlobalBig(ctx, LegacyMinerGasPriceFlag.Name)
log.Warn("The flag --gasprice is deprecated and will be removed in the future, please use --miner.gasprice")
} }
if ctx.GlobalIsSet(MinerGasPriceFlag.Name) { if ctx.GlobalIsSet(MinerGasPriceFlag.Name) {
cfg.GasPrice = GlobalBig(ctx, MinerGasPriceFlag.Name) cfg.GasPrice = GlobalBig(ctx, MinerGasPriceFlag.Name)
@ -1446,8 +1479,8 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
// SetEthConfig applies eth-related command line flags to the config. // SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
// Avoid conflicting network flags // Avoid conflicting network flags
CheckExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag, GoerliFlag) CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag)
CheckExclusive(ctx, LightLegacyServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light")
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag)
// todo(rjl493456442) make it available for les server // todo(rjl493456442) make it available for les server
@ -1533,12 +1566,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
// Override any default configs for hard coded networks. // Override any default configs for hard coded networks.
switch { switch {
case ctx.GlobalBool(TestnetFlag.Name): case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 3 cfg.NetworkId = 3
} }
cfg.Genesis = core.DefaultTestnetGenesisBlock() cfg.Genesis = core.DefaultRopstenGenesisBlock()
setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.TestnetGenesisHash]) setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.RopstenGenesisHash])
case ctx.GlobalBool(RinkebyFlag.Name): case ctx.GlobalBool(RinkebyFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 4 cfg.NetworkId = 4
@ -1574,7 +1607,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
log.Info("Using developer account", "address", developer.Address) log.Info("Using developer account", "address", developer.Address)
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address) cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) {
cfg.Miner.GasPrice = big.NewInt(1) cfg.Miner.GasPrice = big.NewInt(1)
} }
default: default:
@ -1720,8 +1753,8 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
func MakeGenesis(ctx *cli.Context) *core.Genesis { func MakeGenesis(ctx *cli.Context) *core.Genesis {
var genesis *core.Genesis var genesis *core.Genesis
switch { switch {
case ctx.GlobalBool(TestnetFlag.Name): case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
genesis = core.DefaultTestnetGenesisBlock() genesis = core.DefaultRopstenGenesisBlock()
case ctx.GlobalBool(RinkebyFlag.Name): case ctx.GlobalBool(RinkebyFlag.Name):
genesis = core.DefaultRinkebyGenesisBlock() genesis = core.DefaultRinkebyGenesisBlock()
case ctx.GlobalBool(GoerliFlag.Name): case ctx.GlobalBool(GoerliFlag.Name):

163
cmd/utils/flags_legacy.go Normal file
View file

@ -0,0 +1,163 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package utils
import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/node"
"gopkg.in/urfave/cli.v1"
)
var ShowDeprecated = cli.Command{
Action: showDeprecated,
Name: "show-deprecated-flags",
Usage: "Show flags that have been deprecated",
ArgsUsage: " ",
Category: "MISCELLANEOUS COMMANDS",
Description: "Show flags that have been deprecated and will soon be removed",
}
var DeprecatedFlags = []cli.Flag{
LegacyTestnetFlag,
LegacyLightServFlag,
LegacyLightPeersFlag,
LegacyMinerThreadsFlag,
LegacyMinerGasTargetFlag,
LegacyMinerGasPriceFlag,
LegacyMinerEtherbaseFlag,
LegacyMinerExtraDataFlag,
}
var (
// (Deprecated April 2018)
LegacyMinerThreadsFlag = cli.IntFlag{
Name: "minerthreads",
Usage: "Number of CPU threads to use for mining (deprecated, use --miner.threads)",
Value: 0,
}
LegacyMinerGasTargetFlag = cli.Uint64Flag{
Name: "targetgaslimit",
Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)",
Value: eth.DefaultConfig.Miner.GasFloor,
}
LegacyMinerGasPriceFlag = BigFlag{
Name: "gasprice",
Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)",
Value: eth.DefaultConfig.Miner.GasPrice,
}
LegacyMinerEtherbaseFlag = cli.StringFlag{
Name: "etherbase",
Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)",
Value: "0",
}
LegacyMinerExtraDataFlag = cli.StringFlag{
Name: "extradata",
Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)",
}
// (Deprecated June 2019)
LegacyLightServFlag = cli.IntFlag{
Name: "lightserv",
Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)",
Value: eth.DefaultConfig.LightServ,
}
LegacyLightPeersFlag = cli.IntFlag{
Name: "lightpeers",
Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated, use --light.maxpeers)",
Value: eth.DefaultConfig.LightPeers,
}
// (Deprecated April 2020)
LegacyTestnetFlag = cli.BoolFlag{ // TODO(q9f): Remove after Ropsten is discontinued.
Name: "testnet",
Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)",
}
LegacyRPCEnabledFlag = cli.BoolFlag{
Name: "rpc",
Usage: "Enable the HTTP-RPC server (deprecated, use --http)",
}
LegacyRPCListenAddrFlag = cli.StringFlag{
Name: "rpcaddr",
Usage: "HTTP-RPC server listening interface (deprecated, use --http.addr)",
Value: node.DefaultHTTPHost,
}
LegacyRPCPortFlag = cli.IntFlag{
Name: "rpcport",
Usage: "HTTP-RPC server listening port (deprecated, use --http.port)",
Value: node.DefaultHTTPPort,
}
LegacyRPCCORSDomainFlag = cli.StringFlag{
Name: "rpccorsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated, use --http.corsdomain)",
Value: "",
}
LegacyRPCVirtualHostsFlag = cli.StringFlag{
Name: "rpcvhosts",
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (deprecated, use --http.vhosts)",
Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","),
}
LegacyRPCApiFlag = cli.StringFlag{
Name: "rpcapi",
Usage: "API's offered over the HTTP-RPC interface (deprecated, use --http.api)",
Value: "",
}
LegacyWSListenAddrFlag = cli.StringFlag{
Name: "wsaddr",
Usage: "WS-RPC server listening interface (deprecated, use --ws.addr)",
Value: node.DefaultWSHost,
}
LegacyWSPortFlag = cli.IntFlag{
Name: "wsport",
Usage: "WS-RPC server listening port (deprecated, use --ws.port)",
Value: node.DefaultWSPort,
}
LegacyWSApiFlag = cli.StringFlag{
Name: "wsapi",
Usage: "API's offered over the WS-RPC interface (deprecated, use --ws.api)",
Value: "",
}
LegacyWSAllowedOriginsFlag = cli.StringFlag{
Name: "wsorigins",
Usage: "Origins from which to accept websockets requests (deprecated, use --ws.origins)",
Value: "",
}
LegacyGpoBlocksFlag = cli.IntFlag{
Name: "gpoblocks",
Usage: "Number of recent blocks to check for gas prices (deprecated, use --gpo.blocks)",
Value: eth.DefaultConfig.GPO.Blocks,
}
LegacyGpoPercentileFlag = cli.IntFlag{
Name: "gpopercentile",
Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices (deprecated, use --gpo.percentile)",
Value: eth.DefaultConfig.GPO.Percentile,
}
)
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
func showDeprecated(*cli.Context) {
fmt.Println("--------------------------------------------------------------------")
fmt.Println("The following flags are deprecated and will be removed in the future!")
fmt.Println("--------------------------------------------------------------------")
fmt.Println()
for _, flag := range DeprecatedFlags {
fmt.Println(flag.String())
}
}

View file

@ -145,3 +145,14 @@ func TrimLeftZeroes(s []byte) []byte {
} }
return s[idx:] return s[idx:]
} }
// TrimRightZeroes returns a subslice of s without trailing zeroes
func TrimRightZeroes(s []byte) []byte {
idx := len(s)
for ; idx > 0; idx-- {
if s[idx-1] != 0 {
break
}
}
return s[:idx]
}

View file

@ -105,3 +105,22 @@ func TestNoPrefixShortHexOddLength(t *testing.T) {
t.Errorf("Expected %x got %x", expected, result) t.Errorf("Expected %x got %x", expected, result)
} }
} }
func TestTrimRightZeroes(t *testing.T) {
tests := []struct {
arr []byte
exp []byte
}{
{FromHex("0x00ffff00ff0000"), FromHex("0x00ffff00ff")},
{FromHex("0x00000000000000"), []byte{}},
{FromHex("0xff"), FromHex("0xff")},
{[]byte{}, []byte{}},
{FromHex("0x00ffffffffffff"), FromHex("0x00ffffffffffff")},
}
for i, test := range tests {
got := TrimRightZeroes(test.arr)
if !bytes.Equal(got, test.exp) {
t.Errorf("test %d, got %x exp %x", i, got, test.exp)
}
}
}

View file

@ -184,6 +184,12 @@ func U256(x *big.Int) *big.Int {
return x.And(x, tt256m1) return x.And(x, tt256m1)
} }
// U256Bytes converts a big Int into a 256bit EVM number.
// This operation is destructive.
func U256Bytes(n *big.Int) []byte {
return PaddedBigBytes(U256(n), 32)
}
// S256 interprets x as a two's complement number. // S256 interprets x as a two's complement number.
// x must not exceed 256 bits (the result is undefined if it does) and is not modified. // x must not exceed 256 bits (the result is undefined if it does) and is not modified.
// //

View file

@ -212,6 +212,16 @@ func TestU256(t *testing.T) {
} }
} }
func TestU256Bytes(t *testing.T) {
ubytes := make([]byte, 32)
ubytes[31] = 1
unsigned := U256Bytes(big.NewInt(1))
if !bytes.Equal(unsigned, ubytes) {
t.Errorf("expected %x got %x", ubytes, unsigned)
}
}
func TestBigEndianByteAt(t *testing.T) { func TestBigEndianByteAt(t *testing.T) {
tests := []struct { tests := []struct {
x string x string

View file

@ -20,7 +20,6 @@ var (
_ = big.NewInt _ = big.NewInt
_ = strings.NewReader _ = strings.NewReader
_ = ethereum.NotFound _ = ethereum.NotFound
_ = abi.U256
_ = bind.Bind _ = bind.Bind
_ = common.Big1 _ = common.Big1
_ = types.BloomLookup _ = types.BloomLookup

View file

@ -22,7 +22,6 @@ import (
"math/big" "math/big"
"math/rand" "math/rand"
"os" "os"
"reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -981,189 +980,108 @@ func TestLogReorgs(t *testing.T) {
} }
} }
// This EVM code generates a log when the contract is created.
var logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
// This test checks that log events and RemovedLogsEvent are sent
// when the chain reorganizes.
func TestLogRebirth(t *testing.T) { func TestLogRebirth(t *testing.T) {
var ( var (
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = crypto.PubkeyToAddress(key1.PublicKey)
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
// this code generates a log genesis = gspec.MustCommit(db)
code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") signer = types.NewEIP155Signer(gspec.Config.ChainID)
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} engine = ethash.NewFaker()
genesis = gspec.MustCommit(db) blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil)
signer = types.NewEIP155Signer(gspec.Config.ChainID)
newLogCh = make(chan bool)
removeLogCh = make(chan bool)
) )
// validateLogEvent checks whether the received logs number is equal with expected.
validateLogEvent := func(sink interface{}, result chan bool, expect int) {
chanval := reflect.ValueOf(sink)
chantyp := chanval.Type()
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 {
t.Fatalf("invalid channel, given type %v", chantyp)
}
cnt := 0
var recv []reflect.Value
timeout := time.After(1 * time.Second)
cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}}
for {
chose, v, _ := reflect.Select(cases)
if chose == 1 {
// Not enough event received
result <- false
return
}
cnt += 1
recv = append(recv, v)
if cnt == expect {
break
}
}
done := time.After(50 * time.Millisecond)
cases = cases[:1]
cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv})
chose, _, _ := reflect.Select(cases)
// If chose equal 0, it means receiving redundant events.
if chose == 1 {
result <- true
} else {
result <- false
}
}
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
defer blockchain.Stop() defer blockchain.Stop()
logsCh := make(chan []*types.Log) // The event channels.
blockchain.SubscribeLogsEvent(logsCh) newLogCh := make(chan []*types.Log, 10)
rmLogsCh := make(chan RemovedLogsEvent, 10)
blockchain.SubscribeLogsEvent(newLogCh)
blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
rmLogsCh := make(chan RemovedLogsEvent) // This chain contains a single log.
chain, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2, func(i int, gen *BlockGen) {
if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), logCode), signer, key1)
if err != nil {
t.Fatalf("failed to create tx: %v", err)
}
gen.AddTx(tx)
}
})
if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}
checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
// Generate long reorg chain containing another log. Inserting the
// chain removes one log and adds one.
forkChain, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2, func(i int, gen *BlockGen) {
if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), logCode), signer, key1)
if err != nil {
t.Fatalf("failed to create tx: %v", err)
}
gen.AddTx(tx)
gen.OffsetTime(-9) // higher block difficulty
}
})
if _, err := blockchain.InsertChain(forkChain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err)
}
checkLogEvents(t, newLogCh, rmLogsCh, 1, 1)
// This chain segment is rooted in the original chain, but doesn't contain any logs.
// When inserting it, the canonical chain switches away from forkChain and re-emits
// the log event for the old chain, as well as a RemovedLogsEvent for forkChain.
newBlocks, _ := GenerateChain(params.TestChainConfig, chain[len(chain)-1], engine, db, 1, func(i int, gen *BlockGen) {})
if _, err := blockchain.InsertChain(newBlocks); err != nil {
t.Fatalf("failed to insert forked chain: %v", err)
}
checkLogEvents(t, newLogCh, rmLogsCh, 1, 1)
}
// This test is a variation of TestLogRebirth. It verifies that log events are emitted
// when a side chain containing log events overtakes the canonical chain.
func TestSideLogRebirth(t *testing.T) {
var (
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
genesis = gspec.MustCommit(db)
signer = types.NewEIP155Signer(gspec.Config.ChainID)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
)
defer blockchain.Stop()
newLogCh := make(chan []*types.Log, 10)
rmLogsCh := make(chan RemovedLogsEvent, 10)
blockchain.SubscribeLogsEvent(newLogCh)
blockchain.SubscribeRemovedLogsEvent(rmLogsCh) blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
if i == 1 { if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) gen.OffsetTime(-9) // higher block difficulty
if err != nil {
t.Fatalf("failed to create tx: %v", err)
}
gen.AddTx(tx)
}
})
// Spawn a goroutine to receive log events
go validateLogEvent(logsCh, newLogCh, 1)
if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}
if !<-newLogCh {
t.Fatal("failed to receive new log event")
}
// Generate long reorg chain
forkChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1)
if err != nil {
t.Fatalf("failed to create tx: %v", err)
}
gen.AddTx(tx)
// Higher block difficulty
gen.OffsetTime(-9)
}
})
// Spawn a goroutine to receive log events
go validateLogEvent(logsCh, newLogCh, 1)
go validateLogEvent(rmLogsCh, removeLogCh, 1)
if _, err := blockchain.InsertChain(forkChain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err)
}
if !<-newLogCh {
t.Fatal("failed to receive new log event")
}
if !<-removeLogCh {
t.Fatal("failed to receive removed log event")
}
newBlocks, _ := GenerateChain(params.TestChainConfig, chain[len(chain)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
go validateLogEvent(logsCh, newLogCh, 1)
go validateLogEvent(rmLogsCh, removeLogCh, 1)
if _, err := blockchain.InsertChain(newBlocks); err != nil {
t.Fatalf("failed to insert forked chain: %v", err)
}
// Rebirth logs should omit a newLogEvent
if !<-newLogCh {
t.Fatal("failed to receive new log event")
}
// Ensure removedLog events received
if !<-removeLogCh {
t.Fatal("failed to receive removed log event")
}
}
func TestSideLogRebirth(t *testing.T) {
var (
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
db = rawdb.NewMemoryDatabase()
// this code generates a log
code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
genesis = gspec.MustCommit(db)
signer = types.NewEIP155Signer(gspec.Config.ChainID)
newLogCh = make(chan bool)
)
// listenNewLog checks whether the received logs number is equal with expected.
listenNewLog := func(sink chan []*types.Log, expect int) {
cnt := 0
for {
select {
case logs := <-sink:
cnt += len(logs)
case <-time.NewTimer(5 * time.Second).C:
// new logs timeout
newLogCh <- false
return
}
if cnt == expect {
break
} else if cnt > expect {
// redundant logs received
newLogCh <- false
return
}
}
select {
case <-sink:
// redundant logs received
newLogCh <- false
case <-time.NewTimer(100 * time.Millisecond).C:
newLogCh <- true
}
}
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
defer blockchain.Stop()
logsCh := make(chan []*types.Log)
blockchain.SubscribeLogsEvent(logsCh)
chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
if i == 1 {
// Higher block difficulty
gen.OffsetTime(-9)
} }
}) })
if _, err := blockchain.InsertChain(chain); err != nil { if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
// Generate side chain with lower difficulty // Generate side chain with lower difficulty
sideChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { sideChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
if i == 1 { if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), logCode), signer, key1)
if err != nil { if err != nil {
t.Fatalf("failed to create tx: %v", err) t.Fatalf("failed to create tx: %v", err)
} }
@ -1173,16 +1091,31 @@ func TestSideLogRebirth(t *testing.T) {
if _, err := blockchain.InsertChain(sideChain); err != nil { if _, err := blockchain.InsertChain(sideChain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
// Generate a new block based on side chain // Generate a new block based on side chain.
newBlocks, _ := GenerateChain(params.TestChainConfig, sideChain[len(sideChain)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) newBlocks, _ := GenerateChain(params.TestChainConfig, sideChain[len(sideChain)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
go listenNewLog(logsCh, 1)
if _, err := blockchain.InsertChain(newBlocks); err != nil { if _, err := blockchain.InsertChain(newBlocks); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
// Rebirth logs should omit a newLogEvent checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
if !<-newLogCh { }
t.Fatalf("failed to receive new log event")
func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan RemovedLogsEvent, wantNew, wantRemoved int) {
t.Helper()
if len(logsCh) != wantNew {
t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew)
}
if len(rmLogsCh) != wantRemoved {
t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved)
}
// Drain events.
for i := 0; i < len(logsCh); i++ {
<-logsCh
}
for i := 0; i < len(rmLogsCh); i++ {
<-rmLogsCh
} }
} }

View file

@ -439,6 +439,9 @@ func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) {
// AddChildIndexer adds a child ChainIndexer that can use the output of this one // AddChildIndexer adds a child ChainIndexer that can use the output of this one
func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) { func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
if indexer == c {
panic("can't add indexer as a child of itself")
}
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()

View file

@ -22,17 +22,45 @@ var (
// ErrKnownBlock is returned when a block to import is already known locally. // ErrKnownBlock is returned when a block to import is already known locally.
ErrKnownBlock = errors.New("block already known") ErrKnownBlock = errors.New("block already known")
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
// by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")
// ErrBlacklistedHash is returned if a block to import is on the blacklist. // ErrBlacklistedHash is returned if a block to import is on the blacklist.
ErrBlacklistedHash = errors.New("blacklisted hash") ErrBlacklistedHash = errors.New("blacklisted hash")
// ErrNoGenesis is returned when there is no Genesis Block.
ErrNoGenesis = errors.New("genesis not found in chain")
)
// List of evm-call-message pre-checking errors. All state transtion messages will
// be pre-checked before execution. If any invalidation detected, the corresponding
// error should be returned which is defined here.
//
// - If the pre-checking happens in the miner, then the transaction won't be packed.
// - If the pre-checking happens in the block processing procedure, then a "BAD BLOCk"
// error should be emitted.
var (
// 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")
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the // ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
// next one expected based on the local chain. // next one expected based on the local chain.
ErrNonceTooHigh = errors.New("nonce too high") ErrNonceTooHigh = errors.New("nonce too high")
// ErrNoGenesis is returned when there is no Genesis Block. // ErrGasLimitReached is returned by the gas pool if the amount of gas required
ErrNoGenesis = errors.New("genesis not found in chain") // by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")
// ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't
// have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
// 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")
// ErrGasUintOverflow is returned when calculating gas usage.
ErrGasUintOverflow = errors.New("gas uint64 overflow")
// 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")
) )

View file

@ -65,8 +65,8 @@ func TestCreation(t *testing.T) {
}, },
// Ropsten test cases // Ropsten test cases
{ {
params.TestnetChainConfig, params.RopstenChainConfig,
params.TestnetGenesisHash, params.RopstenGenesisHash,
[]testcase{ []testcase{
{0, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block {0, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block
{9, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Last Tangerine block {9, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Last Tangerine block

View file

@ -152,10 +152,6 @@ func (e *GenesisMismatchError) Error() string {
// //
// The returned chain configuration is never nil. // The returned chain configuration is never nil.
func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
return SetupGenesisBlockWithOverride(db, genesis, nil, nil)
}
func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideIstanbul, overrideMuirGlacier *big.Int) (*params.ChainConfig, common.Hash, error) {
if genesis != nil && genesis.Config == nil { if genesis != nil && genesis.Config == nil {
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
} }
@ -204,12 +200,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
// Get the existing chain configuration. // Get the existing chain configuration.
newcfg := genesis.configOrDefault(stored) newcfg := genesis.configOrDefault(stored)
if overrideIstanbul != nil {
newcfg.IstanbulBlock = overrideIstanbul
}
if overrideMuirGlacier != nil {
newcfg.MuirGlacierBlock = overrideMuirGlacier
}
if err := newcfg.CheckConfigForkOrder(); err != nil { if err := newcfg.CheckConfigForkOrder(); err != nil {
return newcfg, common.Hash{}, err return newcfg, common.Hash{}, err
} }
@ -246,8 +236,12 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
return g.Config return g.Config
case ghash == params.MainnetGenesisHash: case ghash == params.MainnetGenesisHash:
return params.MainnetChainConfig return params.MainnetChainConfig
case ghash == params.TestnetGenesisHash: case ghash == params.RopstenGenesisHash:
return params.TestnetChainConfig return params.RopstenChainConfig
case ghash == params.RinkebyGenesisHash:
return params.RinkebyChainConfig
case ghash == params.GoerliGenesisHash:
return params.GoerliChainConfig
default: default:
return params.AllEthashProtocolChanges return params.AllEthashProtocolChanges
} }
@ -347,15 +341,15 @@ func DefaultGenesisBlock() *Genesis {
} }
} }
// DefaultTestnetGenesisBlock returns the Ropsten network genesis block. // DefaultRopstenGenesisBlock returns the Ropsten network genesis block.
func DefaultTestnetGenesisBlock() *Genesis { func DefaultRopstenGenesisBlock() *Genesis {
return &Genesis{ return &Genesis{
Config: params.TestnetChainConfig, Config: params.RopstenChainConfig,
Nonce: 66, Nonce: 66,
ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"), ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"),
GasLimit: 16777216, GasLimit: 16777216,
Difficulty: big.NewInt(1048576), Difficulty: big.NewInt(1048576),
Alloc: decodePrealloc(testnetAllocData), Alloc: decodePrealloc(ropstenAllocData),
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -35,9 +35,9 @@ func TestDefaultGenesisBlock(t *testing.T) {
if block.Hash() != params.MainnetGenesisHash { if block.Hash() != params.MainnetGenesisHash {
t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash) t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash)
} }
block = DefaultTestnetGenesisBlock().ToBlock(nil) block = DefaultRopstenGenesisBlock().ToBlock(nil)
if block.Hash() != params.TestnetGenesisHash { if block.Hash() != params.RopstenGenesisHash {
t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash(), params.TestnetGenesisHash) t.Errorf("wrong ropsten genesis hash, got %v, want %v", block.Hash(), params.RopstenGenesisHash)
} }
} }
@ -95,14 +95,14 @@ func TestSetupGenesis(t *testing.T) {
wantConfig: customg.Config, wantConfig: customg.Config,
}, },
{ {
name: "custom block in DB, genesis == testnet", name: "custom block in DB, genesis == ropsten",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
customg.MustCommit(db) customg.MustCommit(db)
return SetupGenesisBlock(db, DefaultTestnetGenesisBlock()) return SetupGenesisBlock(db, DefaultRopstenGenesisBlock())
}, },
wantErr: &GenesisMismatchError{Stored: customghash, New: params.TestnetGenesisHash}, wantErr: &GenesisMismatchError{Stored: customghash, New: params.RopstenGenesisHash},
wantHash: params.TestnetGenesisHash, wantHash: params.RopstenGenesisHash,
wantConfig: params.TestnetChainConfig, wantConfig: params.RopstenChainConfig,
}, },
{ {
name: "compatible config in DB", name: "compatible config in DB",

View file

@ -149,7 +149,8 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
if ptd == nil { if ptd == nil {
return NonStatTy, consensus.ErrUnknownAncestor return NonStatTy, consensus.ErrUnknownAncestor
} }
localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64()) head := hc.CurrentHeader().Number.Uint64()
localTd := hc.GetTd(hc.currentHeaderHash, head)
externTd := new(big.Int).Add(header.Difficulty, ptd) externTd := new(big.Int).Add(header.Difficulty, ptd)
// Irrelevant of the canonical status, write the td and header to the database // Irrelevant of the canonical status, write the td and header to the database
@ -165,7 +166,15 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
// If the total difficulty is higher than our known, add it to the canonical chain // If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining. // Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) { reorg := externTd.Cmp(localTd) > 0
if !reorg && externTd.Cmp(localTd) == 0 {
if header.Number.Uint64() < head {
reorg = true
} else if header.Number.Uint64() == head {
reorg = mrand.Float64() < 0.5
}
}
if reorg {
// If the header can be added into canonical chain, adjust the // If the header can be added into canonical chain, adjust the
// header chain markers(canonical indexes and head header flag). // header chain markers(canonical indexes and head header flag).
// //

View file

@ -69,7 +69,7 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash {
prefix := headerKeyPrefix(number) prefix := headerKeyPrefix(number)
hashes := make([]common.Hash, 0, 1) hashes := make([]common.Hash, 0, 1)
it := db.NewIteratorWithPrefix(prefix) it := db.NewIterator(prefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {

View file

@ -93,7 +93,7 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com
// IterateStorageSnapshots returns an iterator for walking the entire storage // IterateStorageSnapshots returns an iterator for walking the entire storage
// space of a specific account. // space of a specific account.
func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator {
return db.NewIteratorWithPrefix(storageSnapshotsKey(accountHash)) return db.NewIterator(storageSnapshotsKey(accountHash), nil)
} }
// ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at // ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at

View file

@ -221,7 +221,7 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer
// InspectDatabase traverses the entire database and checks the size // InspectDatabase traverses the entire database and checks the size
// of all different categories of data. // of all different categories of data.
func InspectDatabase(db ethdb.Database) error { func InspectDatabase(db ethdb.Database) error {
it := db.NewIterator() it := db.NewIterator(nil, nil)
defer it.Release() defer it.Release()
var ( var (

View file

@ -541,20 +541,22 @@ func (t *freezerTable) getBounds(item uint64) (uint32, uint32, uint32, error) {
// Retrieve looks up the data offset of an item with the given number and retrieves // Retrieve looks up the data offset of an item with the given number and retrieves
// the raw binary blob from the data file. // the raw binary blob from the data file.
func (t *freezerTable) Retrieve(item uint64) ([]byte, error) { func (t *freezerTable) Retrieve(item uint64) ([]byte, error) {
t.lock.RLock()
// Ensure the table and the item is accessible // Ensure the table and the item is accessible
if t.index == nil || t.head == nil { if t.index == nil || t.head == nil {
t.lock.RUnlock()
return nil, errClosed return nil, errClosed
} }
if atomic.LoadUint64(&t.items) <= item { if atomic.LoadUint64(&t.items) <= item {
t.lock.RUnlock()
return nil, errOutOfBounds return nil, errOutOfBounds
} }
// Ensure the item was not deleted from the tail either // Ensure the item was not deleted from the tail either
offset := atomic.LoadUint32(&t.itemOffset) if uint64(t.itemOffset) > item {
if uint64(offset) > item { t.lock.RUnlock()
return nil, errOutOfBounds return nil, errOutOfBounds
} }
t.lock.RLock() startOffset, endOffset, filenum, err := t.getBounds(item - uint64(t.itemOffset))
startOffset, endOffset, filenum, err := t.getBounds(item - uint64(offset))
if err != nil { if err != nil {
t.lock.RUnlock() t.lock.RUnlock()
return nil, err return nil, err

View file

@ -103,27 +103,12 @@ func (t *table) Delete(key []byte) error {
return t.db.Delete(append([]byte(t.prefix), key...)) return t.db.Delete(append([]byte(t.prefix), key...))
} }
// NewIterator creates a binary-alphabetical iterator over the entire keyspace // NewIterator creates a binary-alphabetical iterator over a subset
// contained within the database. // of database content with a particular key prefix, starting at a particular
func (t *table) NewIterator() ethdb.Iterator { // initial key (or after, if it does not exist).
return t.NewIteratorWithPrefix(nil) func (t *table) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
} innerPrefix := append([]byte(t.prefix), prefix...)
iter := t.db.NewIterator(innerPrefix, start)
// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of
// database content starting at a particular initial key (or after, if it does
// not exist).
func (t *table) NewIteratorWithStart(start []byte) ethdb.Iterator {
iter := t.db.NewIteratorWithStart(append([]byte(t.prefix), start...))
return &tableIterator{
iter: iter,
prefix: t.prefix,
}
}
// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset
// of database content with a particular key prefix.
func (t *table) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator {
iter := t.db.NewIteratorWithPrefix(append([]byte(t.prefix), prefix...))
return &tableIterator{ return &tableIterator{
iter: iter, iter: iter,
prefix: t.prefix, prefix: t.prefix,

View file

@ -19,6 +19,8 @@ package rawdb
import ( import (
"bytes" "bytes"
"testing" "testing"
"github.com/ethereum/go-ethereum/ethdb"
) )
func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") } func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") }
@ -96,48 +98,31 @@ func testTableDatabase(t *testing.T, prefix string) {
} }
} }
check := func(iter ethdb.Iterator, expCount, index int) {
count := 0
for iter.Next() {
key, value := iter.Key(), iter.Value()
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
count++
}
if count != expCount {
t.Fatalf("Wrong number of elems, exp %d got %d", expCount, count)
}
iter.Release()
}
// Test iterators // Test iterators
iter := db.NewIterator() check(db.NewIterator(nil, nil), 6, 0)
var index int
for iter.Next() {
key, value := iter.Key(), iter.Value()
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
}
iter.Release()
// Test iterators with prefix // Test iterators with prefix
iter = db.NewIteratorWithPrefix([]byte{0xff, 0xff}) check(db.NewIterator([]byte{0xff, 0xff}, nil), 3, 3)
index = 3
for iter.Next() {
key, value := iter.Key(), iter.Value()
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
}
iter.Release()
// Test iterators with start point // Test iterators with start point
iter = db.NewIteratorWithStart([]byte{0xff, 0xff, 0x02}) check(db.NewIterator(nil, []byte{0xff, 0xff, 0x02}), 2, 4)
index = 4 // Test iterators with prefix and start point
for iter.Next() { check(db.NewIterator([]byte{0xee}, nil), 0, 0)
key, value := iter.Key(), iter.Value() check(db.NewIterator(nil, []byte{0x00}), 6, 0)
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
}
iter.Release()
} }

View file

@ -103,7 +103,6 @@ func (d iterativeDump) onRoot(root common.Hash) {
} }
func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) { func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) {
emptyAddress := (common.Address{})
missingPreimages := 0 missingPreimages := 0
c.onRoot(s.trie.Hash()) c.onRoot(s.trie.Hash())
@ -114,15 +113,14 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
if err := rlp.DecodeBytes(it.Value, &data); err != nil { if err := rlp.DecodeBytes(it.Value, &data); err != nil {
panic(err) panic(err)
} }
addr := common.BytesToAddress(s.trie.GetKey(it.Key))
obj := newObject(nil, addr, data)
account := DumpAccount{ account := DumpAccount{
Balance: data.Balance.String(), Balance: data.Balance.String(),
Nonce: data.Nonce, Nonce: data.Nonce,
Root: common.Bytes2Hex(data.Root[:]), Root: common.Bytes2Hex(data.Root[:]),
CodeHash: common.Bytes2Hex(data.CodeHash), CodeHash: common.Bytes2Hex(data.CodeHash),
} }
if emptyAddress == addr { addrBytes := s.trie.GetKey(it.Key)
if addrBytes == nil {
// Preimage missing // Preimage missing
missingPreimages++ missingPreimages++
if excludeMissingPreimages { if excludeMissingPreimages {
@ -130,6 +128,8 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
} }
account.SecureKey = it.Key account.SecureKey = it.Key
} }
addr := common.BytesToAddress(addrBytes)
obj := newObject(nil, addr, data)
if !excludeCode { if !excludeCode {
account.Code = common.Bytes2Hex(obj.Code(s.db)) account.Code = common.Bytes2Hex(obj.Code(s.db))
} }

View file

@ -51,7 +51,7 @@ func TestNodeIteratorCoverage(t *testing.T) {
t.Errorf("state entry not reported %x", hash) t.Errorf("state entry not reported %x", hash)
} }
} }
it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator() it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator(nil, nil)
for it.Next() { for it.Next() {
key := it.Key() key := it.Key()
if bytes.HasPrefix(key, []byte("secure-key-")) { if bytes.HasPrefix(key, []byte("secure-key-")) {

View file

@ -24,8 +24,10 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
// Account is a slim version of a state.Account, where the root and code hash // Account is a modified version of a state.Account, where the root is replaced
// are replaced with a nil byte slice for empty accounts. // with a byte slice. This format can be used to represent full-consensus format
// or slim-snapshot format which replaces the empty root and code hash as nil
// byte slice.
type Account struct { type Account struct {
Nonce uint64 Nonce uint64
Balance *big.Int Balance *big.Int
@ -33,9 +35,8 @@ type Account struct {
CodeHash []byte CodeHash []byte
} }
// AccountRLP converts a state.Account content into a slim snapshot version RLP // SlimAccount converts a state.Account content into a slim snapshot account
// encoded. func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) Account {
func AccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte {
slim := Account{ slim := Account{
Nonce: nonce, Nonce: nonce,
Balance: balance, Balance: balance,
@ -46,9 +47,40 @@ func AccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byt
if !bytes.Equal(codehash, emptyCode[:]) { if !bytes.Equal(codehash, emptyCode[:]) {
slim.CodeHash = codehash slim.CodeHash = codehash
} }
data, err := rlp.EncodeToBytes(slim) return slim
}
// 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))
if err != nil { if err != nil {
panic(err) panic(err)
} }
return data return data
} }
// FullAccount decodes the data on the 'slim RLP' format and return
// the consensus format account.
func FullAccount(data []byte) (Account, error) {
var account Account
if err := rlp.DecodeBytes(data, &account); err != nil {
return Account{}, err
}
if len(account.Root) == 0 {
account.Root = emptyRoot[:]
}
if len(account.CodeHash) == 0 {
account.CodeHash = emptyCode[:]
}
return account, nil
}
// FullAccountRLP converts data on the 'slim RLP' format into the full RLP-format.
func FullAccountRLP(data []byte) ([]byte, error) {
account, err := FullAccount(data)
if err != nil {
return nil, err
}
return rlp.EncodeToBytes(account)
}

View file

@ -17,6 +17,8 @@
package snapshot package snapshot
import ( import (
"bytes"
"fmt"
"sync" "sync"
"time" "time"
@ -27,80 +29,239 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
// conversionAccount is used for converting between full and slim format. When
// doing this, we can consider 'balance' as a byte array, as it has already
// been converted from big.Int into an rlp-byteslice.
type conversionAccount struct {
Nonce uint64
Balance []byte
Root []byte
CodeHash []byte
}
// SlimToFull converts data on the 'slim RLP' format into the full RLP-format
func SlimToFull(data []byte) ([]byte, error) {
acc := &conversionAccount{}
if err := rlp.DecodeBytes(data, acc); err != nil {
return nil, err
}
if len(acc.Root) == 0 {
acc.Root = emptyRoot[:]
}
if len(acc.CodeHash) == 0 {
acc.CodeHash = emptyCode[:]
}
fullData, err := rlp.EncodeToBytes(acc)
if err != nil {
return nil, err
}
return fullData, nil
}
// trieKV represents a trie key-value pair // trieKV represents a trie key-value pair
type trieKV struct { type trieKV struct {
key common.Hash key common.Hash
value []byte value []byte
} }
type trieGeneratorFn func(in chan (trieKV), out chan (common.Hash)) type (
// trieGeneratorFn is the interface of trie generation which can
// be implemented by different trie algorithm.
trieGeneratorFn func(in chan (trieKV), out chan (common.Hash))
// GenerateTrieRoot takes an account iterator and reproduces the root hash. // leafCallbackFn is the callback invoked at the leaves of the trie,
func GenerateTrieRoot(it AccountIterator) common.Hash { // returns the subtrie root with the specified subtrie identifier.
return generateTrieRoot(it, stdGenerate) leafCallbackFn func(hash common.Hash, stat *generateStats) common.Hash
)
// GenerateAccountTrieRoot takes an account iterator and reproduces the root hash.
func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) {
return generateTrieRoot(it, common.Hash{}, stdGenerate, nil, &generateStats{start: time.Now()}, true)
} }
func generateTrieRoot(it AccountIterator, generatorFn trieGeneratorFn) common.Hash { // GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash.
func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) {
return generateTrieRoot(it, account, stdGenerate, nil, &generateStats{start: time.Now()}, true)
}
// VerifyState takes the whole snapshot tree as the input, traverses all the accounts
// as well as the corresponding storages and compares the re-computed hash with the
// original one(state root and the storage root).
func VerifyState(snaptree *Tree, root common.Hash) error {
acctIt, err := snaptree.AccountIterator(root, common.Hash{})
if err != nil {
return err
}
defer acctIt.Release()
got, err := generateTrieRoot(acctIt, common.Hash{}, stdGenerate, func(account common.Hash, stat *generateStats) common.Hash {
storageIt, err := snaptree.StorageIterator(root, account, common.Hash{})
if err != nil {
return common.Hash{}
}
defer storageIt.Release()
hash, err := generateTrieRoot(storageIt, account, stdGenerate, nil, stat, false)
if err != nil {
return common.Hash{}
}
return hash
}, &generateStats{start: time.Now()}, true)
if err != nil {
return err
}
if got != root {
return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root)
}
return nil
}
// generateStats is a collection of statistics gathered by the trie generator
// for logging purposes.
type generateStats struct {
accounts uint64
slots uint64
curAccount common.Hash
curSlot common.Hash
start time.Time
lock sync.RWMutex
}
// progress records the progress trie generator made recently.
func (stat *generateStats) progress(accounts, slots uint64, curAccount common.Hash, curSlot common.Hash) {
stat.lock.Lock()
defer stat.lock.Unlock()
stat.accounts += accounts
stat.slots += slots
stat.curAccount = curAccount
stat.curSlot = curSlot
}
// report prints the cumulative progress statistic smartly.
func (stat *generateStats) report() {
stat.lock.RLock()
defer stat.lock.RUnlock()
var ctx []interface{}
if stat.curSlot != (common.Hash{}) {
ctx = append(ctx, []interface{}{
"in", stat.curAccount,
"at", stat.curSlot,
}...)
} else {
ctx = append(ctx, []interface{}{"at", stat.curAccount}...)
}
// Add the usual measurements
ctx = append(ctx, []interface{}{"accounts", stat.accounts}...)
if stat.slots != 0 {
ctx = append(ctx, []interface{}{"slots", stat.slots}...)
}
ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...)
log.Info("Generating trie hash from snapshot", ctx...)
}
// reportDone prints the last log when the whole generation is finished.
func (stat *generateStats) reportDone() {
stat.lock.RLock()
defer stat.lock.RUnlock()
var ctx []interface{}
ctx = append(ctx, []interface{}{"accounts", stat.accounts}...)
if stat.slots != 0 {
ctx = append(ctx, []interface{}{"slots", stat.slots}...)
}
ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...)
log.Info("Generated trie hash from snapshot", ctx...)
}
// generateTrieRoot generates the trie hash based on the snapshot iterator.
// It can be used for generating account trie, storage trie or even the
// whole state which connects the accounts and the corresponding storages.
func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) {
var ( var (
in = make(chan trieKV) // chan to pass leaves in = make(chan trieKV) // chan to pass leaves
out = make(chan common.Hash) // chan to collect result out = make(chan common.Hash, 1) // chan to collect result
wg sync.WaitGroup stoplog = make(chan bool, 1) // 1-size buffer, works when logging is not enabled
wg sync.WaitGroup
) )
// Spin up a go-routine for trie hash re-generation
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done()
generatorFn(in, out) generatorFn(in, out)
wg.Done()
}() }()
// Feed leaves
start := time.Now() // Spin up a go-routine for progress logging
logged := time.Now() if report && stats != nil {
accounts := 0 wg.Add(1)
for it.Next() { go func() {
slimData := it.Account() defer wg.Done()
fullData, _ := SlimToFull(slimData)
l := trieKV{it.Hash(), fullData} timer := time.NewTimer(0)
in <- l defer timer.Stop()
if time.Since(logged) > 8*time.Second {
log.Info("Generating trie hash from snapshot", for {
"at", l.key, "accounts", accounts, "elapsed", time.Since(start)) select {
logged = time.Now() case <-timer.C:
} stats.report()
accounts++ timer.Reset(time.Second * 8)
case success := <-stoplog:
if success {
stats.reportDone()
}
return
}
}
}()
} }
close(in) // stop is a helper function to shutdown the background threads
result := <-out // and return the re-generated trie hash.
log.Info("Generated trie hash from snapshot", "accounts", accounts, "elapsed", time.Since(start)) stop := func(success bool) common.Hash {
wg.Wait() close(in)
return result result := <-out
stoplog <- success
wg.Wait()
return result
}
var (
logged = time.Now()
processed = uint64(0)
leaf trieKV
last common.Hash
)
// Start to feed leaves
for it.Next() {
if account == (common.Hash{}) {
var (
err error
fullData []byte
)
if leafCallback == nil {
fullData, err = FullAccountRLP(it.(AccountIterator).Account())
if err != nil {
stop(false)
return common.Hash{}, err
}
} else {
account, err := FullAccount(it.(AccountIterator).Account())
if err != nil {
stop(false)
return common.Hash{}, err
}
// Apply the leaf callback. Normally the callback is used to traverse
// the storage trie and re-generate the subtrie root.
subroot := leafCallback(it.Hash(), stats)
if !bytes.Equal(account.Root, subroot.Bytes()) {
stop(false)
return common.Hash{}, fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot)
}
fullData, err = rlp.EncodeToBytes(account)
if err != nil {
stop(false)
return common.Hash{}, err
}
}
leaf = trieKV{it.Hash(), fullData}
} else {
leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())}
}
in <- leaf
// Accumulate the generaation statistic if it's required.
processed++
if time.Since(logged) > 3*time.Second && stats != nil {
if account == (common.Hash{}) {
stats.progress(processed, 0, it.Hash(), common.Hash{})
} else {
stats.progress(0, processed, account, it.Hash())
}
logged, processed = time.Now(), 0
}
last = it.Hash()
}
// Commit the last part statistic.
if processed > 0 && stats != nil {
if account == (common.Hash{}) {
stats.progress(processed, 0, last, common.Hash{})
} else {
stats.progress(0, processed, account, last)
}
}
result := stop(true)
return result, nil
} }
// stdGenerate is a very basic hexary trie builder which uses the same Trie // stdGenerate is a very basic hexary trie builder which uses the same Trie

View file

@ -105,6 +105,13 @@ type diffLayer struct {
root common.Hash // Root hash to which this snapshot diff belongs to root common.Hash // Root hash to which this snapshot diff belongs to
stale uint32 // Signals that the layer became stale (state progressed) stale uint32 // Signals that the layer became stale (state progressed)
// destructSet is a very special helper marker. If an account is marked as
// deleted, then it's recorded in this set. However it's allowed that an account
// is included here but still available in other sets(e.g. storageData). The
// reason is the diff layer includes all the changes in a *block*. It can
// happen that in the tx_1, account A is self-destructed while in the tx_2
// it's recreated. But we still need this marker to indicate the "old" A is
// deleted, all data in other set belongs to the "new" A.
destructSet map[common.Hash]struct{} // Keyed markers for deleted (and potentially) recreated accounts destructSet map[common.Hash]struct{} // Keyed markers for deleted (and potentially) recreated accounts
accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil
accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted)
@ -169,6 +176,7 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s
destructSet: destructs, destructSet: destructs,
accountData: accounts, accountData: accounts,
storageData: storage, storageData: storage,
storageList: make(map[common.Hash][]common.Hash),
} }
switch parent := parent.(type) { switch parent := parent.(type) {
case *diskLayer: case *diskLayer:
@ -194,11 +202,6 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s
dl.memory += uint64(common.HashLength + len(data)) dl.memory += uint64(common.HashLength + len(data))
snapshotDirtyAccountWriteMeter.Mark(int64(len(data))) snapshotDirtyAccountWriteMeter.Mark(int64(len(data)))
} }
// Fill the storage hashes and sort them for the iterator
dl.storageList = make(map[common.Hash][]common.Hash)
for accountHash := range destructs {
dl.storageList[accountHash] = nil
}
// Determine memory size and track the dirty writes // Determine memory size and track the dirty writes
for _, slots := range storage { for _, slots := range storage {
for _, data := range slots { for _, data := range slots {
@ -206,7 +209,7 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s
snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) snapshotDirtyStorageWriteMeter.Mark(int64(len(data)))
} }
} }
dl.memory += uint64(len(dl.storageList) * common.HashLength) dl.memory += uint64(len(destructs) * common.HashLength)
return dl return dl
} }
@ -287,6 +290,8 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) {
// AccountRLP directly retrieves the account RLP associated with a particular // AccountRLP directly retrieves the account RLP associated with a particular
// hash in the snapshot slim data format. // hash in the snapshot slim data format.
//
// Note the returned account is not a copy, please don't modify it.
func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) {
// Check the bloom filter first whether there's even a point in reaching into // Check the bloom filter first whether there's even a point in reaching into
// all the maps in all the layers below // all the maps in all the layers below
@ -347,6 +352,8 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) {
// Storage directly retrieves the storage data associated with a particular hash, // Storage directly retrieves the storage data associated with a particular hash,
// within a particular account. If the slot is unknown to this diff, it's parent // within a particular account. If the slot is unknown to this diff, it's parent
// is consulted. // is consulted.
//
// Note the returned slot is not a copy, please don't modify it.
func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
// Check the bloom filter first whether there's even a point in reaching into // Check the bloom filter first whether there's even a point in reaching into
// all the maps in all the layers below // all the maps in all the layers below
@ -502,22 +509,34 @@ func (dl *diffLayer) AccountList() []common.Hash {
} }
} }
sort.Sort(hashes(dl.accountList)) sort.Sort(hashes(dl.accountList))
dl.memory += uint64(len(dl.accountList) * common.HashLength)
return dl.accountList return dl.accountList
} }
// StorageList returns a sorted list of all storage slot hashes in this difflayer // StorageList returns a sorted list of all storage slot hashes in this difflayer
// for the given account. // for the given account. If the whole storage is destructed in this layer, then
// an additional flag *destructed = true* will be returned, otherwise the flag is
// false. Besides, the returned list will include the hash of deleted storage slot.
// Note a special case is an account is deleted in a prior tx but is recreated in
// the following tx with some storage slots set. In this case the returned list is
// not empty but the flag is true.
// //
// Note, the returned slice is not a copy, so do not modify it. // Note, the returned slice is not a copy, so do not modify it.
func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash { func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool) {
// If an old list already exists, return it
dl.lock.RLock() dl.lock.RLock()
list := dl.storageList[accountHash] _, destructed := dl.destructSet[accountHash]
if _, ok := dl.storageData[accountHash]; !ok {
// Account not tracked by this layer
dl.lock.RUnlock()
return nil, destructed
}
// If an old list already exists, return it
if list, exist := dl.storageList[accountHash]; exist {
dl.lock.RUnlock()
return list, destructed // the cached list can't be nil
}
dl.lock.RUnlock() dl.lock.RUnlock()
if list != nil {
return list
}
// No old sorted account list exists, generate a new one // No old sorted account list exists, generate a new one
dl.lock.Lock() dl.lock.Lock()
defer dl.lock.Unlock() defer dl.lock.Unlock()
@ -529,5 +548,6 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
} }
sort.Sort(hashes(storageList)) sort.Sort(hashes(storageList))
dl.storageList[accountHash] = storageList dl.storageList[accountHash] = storageList
return storageList dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
return storageList, destructed
} }

View file

@ -109,7 +109,8 @@ func TestMergeBasics(t *testing.T) {
if have, want := len(merged.storageList), i; have != want { if have, want := len(merged.storageList), i; have != want {
t.Errorf("[1] storageList wrong: have %v, want %v", have, want) t.Errorf("[1] storageList wrong: have %v, want %v", have, want)
} }
if have, want := len(merged.StorageList(aHash)), len(sMap); 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) t.Errorf("[2] StorageList() wrong: have %v, want %v", have, want)
} }
if have, want := len(merged.storageList[aHash]), len(sMap); have != want { if have, want := len(merged.storageList[aHash]), len(sMap); have != want {
@ -131,7 +132,7 @@ func TestMergeDelete(t *testing.T) {
flipDrops := func() map[common.Hash]struct{} { flipDrops := func() map[common.Hash]struct{} {
return map[common.Hash]struct{}{ return map[common.Hash]struct{}{
h2: struct{}{}, h2: {},
} }
} }
flipAccs := func() map[common.Hash][]byte { flipAccs := func() map[common.Hash][]byte {
@ -141,7 +142,7 @@ func TestMergeDelete(t *testing.T) {
} }
flopDrops := func() map[common.Hash]struct{} { flopDrops := func() map[common.Hash]struct{} {
return map[common.Hash]struct{}{ return map[common.Hash]struct{}{
h1: struct{}{}, h1: {},
} }
} }
flopAccs := func() map[common.Hash][]byte { flopAccs := func() map[common.Hash][]byte {

View file

@ -18,11 +18,15 @@ package snapshot
import ( import (
"bytes" "bytes"
"io/ioutil"
"os"
"testing" "testing"
"github.com/VictoriaMetrics/fastcache" "github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/leveldb"
"github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/ethdb/memorydb"
) )
@ -117,10 +121,10 @@ func TestDiskMerge(t *testing.T) {
// Modify or delete some accounts, flatten everything onto disk // Modify or delete some accounts, flatten everything onto disk
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
accDelNoCache: struct{}{}, accDelNoCache: {},
accDelCache: struct{}{}, accDelCache: {},
conNukeNoCache: struct{}{}, conNukeNoCache: {},
conNukeCache: struct{}{}, conNukeCache: {},
}, map[common.Hash][]byte{ }, map[common.Hash][]byte{
accModNoCache: reverse(accModNoCache[:]), accModNoCache: reverse(accModNoCache[:]),
accModCache: reverse(accModCache[:]), accModCache: reverse(accModCache[:]),
@ -340,10 +344,10 @@ func TestDiskPartialMerge(t *testing.T) {
// Modify or delete some accounts, flatten everything onto disk // Modify or delete some accounts, flatten everything onto disk
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
accDelNoCache: struct{}{}, accDelNoCache: {},
accDelCache: struct{}{}, accDelCache: {},
conNukeNoCache: struct{}{}, conNukeNoCache: {},
conNukeCache: struct{}{}, conNukeCache: {},
}, map[common.Hash][]byte{ }, map[common.Hash][]byte{
accModNoCache: reverse(accModNoCache[:]), accModNoCache: reverse(accModNoCache[:]),
accModCache: reverse(accModCache[:]), accModCache: reverse(accModCache[:]),
@ -432,4 +436,76 @@ func TestDiskPartialMerge(t *testing.T) {
// This test case is a tiny specialized case of TestDiskPartialMerge, which tests // This test case is a tiny specialized case of TestDiskPartialMerge, which tests
// some very specific cornercases that random tests won't ever trigger. // some very specific cornercases that random tests won't ever trigger.
func TestDiskMidAccountPartialMerge(t *testing.T) { 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)
}
}
}
} }

View file

@ -42,7 +42,7 @@ var (
) )
// generatorStats is a collection of statistics gathered by the snapshot generator // generatorStats is a collection of statistics gathered by the snapshot generator
// for logging purposes. // for logging purposes.
type generatorStats struct { type generatorStats struct {
wiping chan struct{} // Notification channel if wiping is in progress wiping chan struct{} // Notification channel if wiping is in progress
origin uint64 // Origin prefix where generation started origin uint64 // Origin prefix where generation started
@ -167,7 +167,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
log.Crit("Invalid account encountered during snapshot creation", "err", err) log.Crit("Invalid account encountered during snapshot creation", "err", err)
} }
data := AccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
// If the account is not yet in-progress, write it out // If the account is not yet in-progress, write it out
if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) {

View file

@ -26,9 +26,9 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
) )
// AccountIterator is an iterator to step over all the accounts in a snapshot, // Iterator is a iterator to step over all the accounts or the specific
// which may or may npt be composed of multiple layers. // storage in a snapshot which may or may not be composed of multiple layers.
type AccountIterator interface { type Iterator interface {
// Next steps the iterator forward one element, returning false if exhausted, // Next steps the iterator forward one element, returning false if exhausted,
// or an error if iteration failed for some reason (e.g. root being iterated // or an error if iteration failed for some reason (e.g. root being iterated
// becomes stale and garbage collected). // becomes stale and garbage collected).
@ -38,18 +38,35 @@ type AccountIterator interface {
// caused a premature iteration exit (e.g. snapshot stack becoming stale). // caused a premature iteration exit (e.g. snapshot stack becoming stale).
Error() error Error() error
// Hash returns the hash of the account the iterator is currently at. // Hash returns the hash of the account or storage slot the iterator is
// currently at.
Hash() common.Hash Hash() common.Hash
// Account returns the RLP encoded slim account the iterator is currently at.
// An error will be returned if the iterator becomes invalid (e.g. snaph
Account() []byte
// Release releases associated resources. Release should always succeed and // Release releases associated resources. Release should always succeed and
// can be called multiple times without causing error. // can be called multiple times without causing error.
Release() Release()
} }
// AccountIterator is a iterator to step over all the accounts in a snapshot,
// which may or may not be composed of multiple layers.
type AccountIterator interface {
Iterator
// Account returns the RLP encoded slim account the iterator is currently at.
// An error will be returned if the iterator becomes invalid
Account() []byte
}
// StorageIterator is a iterator to step over the specific storage in a snapshot,
// which may or may not be composed of multiple layers.
type StorageIterator interface {
Iterator
// Slot returns the storage slot the iterator is currently at. An error will
// be returned if the iterator becomes invalid
Slot() []byte
}
// diffAccountIterator is an account iterator that steps over the accounts (both // diffAccountIterator is an account iterator that steps over the accounts (both
// live and deleted) contained within a single diff layer. Higher order iterators // live and deleted) contained within a single diff layer. Higher order iterators
// will use the deleted accounts to skip deeper iterators. // will use the deleted accounts to skip deeper iterators.
@ -70,7 +87,7 @@ func (dl *diffLayer) AccountIterator(seek common.Hash) AccountIterator {
// Seek out the requested starting account // Seek out the requested starting account
hashes := dl.AccountList() hashes := dl.AccountList()
index := sort.Search(len(hashes), func(i int) bool { index := sort.Search(len(hashes), func(i int) bool {
return bytes.Compare(seek[:], hashes[i][:]) < 0 return bytes.Compare(seek[:], hashes[i][:]) <= 0
}) })
// Assemble and returned the already seeked iterator // Assemble and returned the already seeked iterator
return &diffAccountIterator{ return &diffAccountIterator{
@ -120,11 +137,14 @@ func (it *diffAccountIterator) Hash() common.Hash {
// This method assumes that flattening does not delete elements from // This method assumes that flattening does not delete elements from
// the accountdata mapping (writing nil into it is fine though), and will panic // the accountdata mapping (writing nil into it is fine though), and will panic
// if elements have been deleted. // if elements have been deleted.
//
// Note the returned account is not a copy, please don't modify it.
func (it *diffAccountIterator) Account() []byte { func (it *diffAccountIterator) Account() []byte {
it.layer.lock.RLock() it.layer.lock.RLock()
blob, ok := it.layer.accountData[it.curHash] blob, ok := it.layer.accountData[it.curHash]
if !ok { if !ok {
if _, ok := it.layer.destructSet[it.curHash]; ok { if _, ok := it.layer.destructSet[it.curHash]; ok {
it.layer.lock.RUnlock()
return nil return nil
} }
panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash))
@ -148,10 +168,10 @@ type diskAccountIterator struct {
// AccountIterator creates an account iterator over a disk layer. // AccountIterator creates an account iterator over a disk layer.
func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator {
// TODO: Fix seek position, or remove seek parameter pos := common.TrimRightZeroes(seek[:])
return &diskAccountIterator{ return &diskAccountIterator{
layer: dl, layer: dl,
it: dl.diskdb.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix), it: dl.diskdb.NewIterator(rawdb.SnapshotAccountPrefix, pos),
} }
} }
@ -163,7 +183,7 @@ func (it *diskAccountIterator) Next() bool {
} }
// Try to advance the iterator and release it if we reached the end // Try to advance the iterator and release it if we reached the end
for { for {
if !it.it.Next() || !bytes.HasPrefix(it.it.Key(), rawdb.SnapshotAccountPrefix) { if !it.it.Next() {
it.it.Release() it.it.Release()
it.it = nil it.it = nil
return false return false
@ -181,12 +201,15 @@ func (it *diskAccountIterator) Next() bool {
// A diff layer is immutable after creation content wise and can always be fully // A diff layer is immutable after creation content wise and can always be fully
// iterated without error, so this method always returns nil. // iterated without error, so this method always returns nil.
func (it *diskAccountIterator) Error() error { func (it *diskAccountIterator) Error() error {
if it.it == nil {
return nil // Iterator is exhausted and released
}
return it.it.Error() return it.it.Error()
} }
// Hash returns the hash of the account the iterator is currently at. // Hash returns the hash of the account the iterator is currently at.
func (it *diskAccountIterator) Hash() common.Hash { func (it *diskAccountIterator) Hash() common.Hash {
return common.BytesToHash(it.it.Key()) return common.BytesToHash(it.it.Key()) // The prefix will be truncated
} }
// Account returns the RLP encoded slim account the iterator is currently at. // Account returns the RLP encoded slim account the iterator is currently at.
@ -202,3 +225,176 @@ func (it *diskAccountIterator) Release() {
it.it = nil it.it = nil
} }
} }
// diffStorageIterator is a storage iterator that steps over the specific storage
// (both live and deleted) contained within a single diff layer. Higher order
// iterators will use the deleted slot to skip deeper iterators.
type diffStorageIterator struct {
// curHash is the current hash the iterator is positioned on. The field is
// explicitly tracked since the referenced diff layer might go stale after
// the iterator was positioned and we don't want to fail accessing the old
// hash as long as the iterator is not touched any more.
curHash common.Hash
account common.Hash
layer *diffLayer // Live layer to retrieve values from
keys []common.Hash // Keys left in the layer to iterate
fail error // Any failures encountered (stale)
}
// StorageIterator creates a storage iterator over a single diff layer.
// Execept the storage iterator is returned, there is an additional flag
// "destructed" returned. If it's true then it means the whole storage is
// destructed in this layer(maybe recreated too), don't bother deeper layer
// for storage retrieval.
func (dl *diffLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) {
// Create the storage for this account even it's marked
// as destructed. The iterator is for the new one which
// just has the same adddress as the deleted one.
hashes, destructed := dl.StorageList(account)
index := sort.Search(len(hashes), func(i int) bool {
return bytes.Compare(seek[:], hashes[i][:]) <= 0
})
// Assemble and returned the already seeked iterator
return &diffStorageIterator{
layer: dl,
account: account,
keys: hashes[index:],
}, destructed
}
// Next steps the iterator forward one element, returning false if exhausted.
func (it *diffStorageIterator) Next() bool {
// If the iterator was already stale, consider it a programmer error. Although
// we could just return false here, triggering this path would probably mean
// somebody forgot to check for Error, so lets blow up instead of undefined
// behavior that's hard to debug.
if it.fail != nil {
panic(fmt.Sprintf("called Next of failed iterator: %v", it.fail))
}
// Stop iterating if all keys were exhausted
if len(it.keys) == 0 {
return false
}
if it.layer.Stale() {
it.fail, it.keys = ErrSnapshotStale, nil
return false
}
// Iterator seems to be still alive, retrieve and cache the live hash
it.curHash = it.keys[0]
// key cached, shift the iterator and notify the user of success
it.keys = it.keys[1:]
return true
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit (e.g. snapshot stack becoming stale).
func (it *diffStorageIterator) Error() error {
return it.fail
}
// Hash returns the hash of the storage slot the iterator is currently at.
func (it *diffStorageIterator) Hash() common.Hash {
return it.curHash
}
// Slot returns the raw storage slot value the iterator is currently at.
// This method may _fail_, if the underlying layer has been flattened between
// the call to Next and Value. That type of error will set it.Err.
// This method assumes that flattening does not delete elements from
// the storage mapping (writing nil into it is fine though), and will panic
// if elements have been deleted.
//
// Note the returned slot is not a copy, please don't modify it.
func (it *diffStorageIterator) Slot() []byte {
it.layer.lock.RLock()
storage, ok := it.layer.storageData[it.account]
if !ok {
panic(fmt.Sprintf("iterator referenced non-existent account storage: %x", it.account))
}
// Storage slot might be nil(deleted), but it must exist
blob, ok := storage[it.curHash]
if !ok {
panic(fmt.Sprintf("iterator referenced non-existent storage slot: %x", it.curHash))
}
it.layer.lock.RUnlock()
if it.layer.Stale() {
it.fail, it.keys = ErrSnapshotStale, nil
}
return blob
}
// Release is a noop for diff account iterators as there are no held resources.
func (it *diffStorageIterator) Release() {}
// diskStorageIterator is a storage iterator that steps over the live storage
// contained within a disk layer.
type diskStorageIterator struct {
layer *diskLayer
account common.Hash
it ethdb.Iterator
}
// StorageIterator creates a storage iterator over a disk layer.
// If the whole storage is destructed, then all entries in the disk
// layer are deleted already. So the "destructed" flag returned here
// is always false.
func (dl *diskLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) {
pos := common.TrimRightZeroes(seek[:])
return &diskStorageIterator{
layer: dl,
account: account,
it: dl.diskdb.NewIterator(append(rawdb.SnapshotStoragePrefix, account.Bytes()...), pos),
}, false
}
// Next steps the iterator forward one element, returning false if exhausted.
func (it *diskStorageIterator) Next() bool {
// If the iterator was already exhausted, don't bother
if it.it == nil {
return false
}
// Try to advance the iterator and release it if we reached the end
for {
if !it.it.Next() {
it.it.Release()
it.it = nil
return false
}
if len(it.it.Key()) == len(rawdb.SnapshotStoragePrefix)+common.HashLength+common.HashLength {
break
}
}
return true
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit (e.g. snapshot stack becoming stale).
//
// A diff layer is immutable after creation content wise and can always be fully
// iterated without error, so this method always returns nil.
func (it *diskStorageIterator) Error() error {
if it.it == nil {
return nil // Iterator is exhausted and released
}
return it.it.Error()
}
// Hash returns the hash of the storage slot the iterator is currently at.
func (it *diskStorageIterator) Hash() common.Hash {
return common.BytesToHash(it.it.Key()) // The prefix will be truncated
}
// Slot returns the raw strorage slot content the iterator is currently at.
func (it *diskStorageIterator) Slot() []byte {
return it.it.Value()
}
// Release releases the database snapshot held during iteration.
func (it *diskStorageIterator) Release() {
// The iterator is auto-released on exhaustion, so make sure it's still alive
if it.it != nil {
it.it.Release()
it.it = nil
}
}

View file

@ -22,29 +22,91 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// binaryAccountIterator is a simplistic iterator to step over the accounts in // binaryIterator is a simplistic iterator to step over the accounts or storage
// a snapshot, which may or may npt be composed of multiple layers. Performance // in a snapshot, which may or may not be composed of multiple layers. Performance
// wise this iterator is slow, it's meant for cross validating the fast one, // wise this iterator is slow, it's meant for cross validating the fast one,
type binaryAccountIterator struct { type binaryIterator struct {
a *diffAccountIterator a Iterator
b AccountIterator b Iterator
aDone bool aDone bool
bDone bool bDone bool
k common.Hash accountIterator bool
fail error k common.Hash
account common.Hash
fail error
} }
// newBinaryAccountIterator creates a simplistic account iterator to step over // initBinaryAccountIterator creates a simplistic iterator to step over all the
// all the accounts in a slow, but eaily verifiable way. // accounts in a slow, but eaily verifiable way. Note this function is used for
func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { // initialization, use `newBinaryAccountIterator` as the API.
func (dl *diffLayer) initBinaryAccountIterator() Iterator {
parent, ok := dl.parent.(*diffLayer) parent, ok := dl.parent.(*diffLayer)
if !ok { if !ok {
// parent is the disk layer l := &binaryIterator{
return dl.AccountIterator(common.Hash{}) a: dl.AccountIterator(common.Hash{}),
b: dl.Parent().AccountIterator(common.Hash{}),
accountIterator: true,
}
l.aDone = !l.a.Next()
l.bDone = !l.b.Next()
return l
} }
l := &binaryAccountIterator{ l := &binaryIterator{
a: dl.AccountIterator(common.Hash{}).(*diffAccountIterator), a: dl.AccountIterator(common.Hash{}),
b: parent.newBinaryAccountIterator(), b: parent.initBinaryAccountIterator(),
accountIterator: true,
}
l.aDone = !l.a.Next()
l.bDone = !l.b.Next()
return l
}
// initBinaryStorageIterator creates a simplistic iterator to step over all the
// storage slots in a slow, but eaily verifiable way. Note this function is used
// for initialization, use `newBinaryStorageIterator` as the API.
func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
parent, ok := dl.parent.(*diffLayer)
if !ok {
// If the storage in this layer is already destructed, discard all
// deeper layers but still return an valid single-branch iterator.
a, destructed := dl.StorageIterator(account, common.Hash{})
if destructed {
l := &binaryIterator{
a: a,
account: account,
}
l.aDone = !l.a.Next()
l.bDone = true
return l
}
// The parent is disk layer, don't need to take care "destructed"
// anymore.
b, _ := dl.Parent().StorageIterator(account, common.Hash{})
l := &binaryIterator{
a: a,
b: b,
account: account,
}
l.aDone = !l.a.Next()
l.bDone = !l.b.Next()
return l
}
// If the storage in this layer is already destructed, discard all
// deeper layers but still return an valid single-branch iterator.
a, destructed := dl.StorageIterator(account, common.Hash{})
if destructed {
l := &binaryIterator{
a: a,
account: account,
}
l.aDone = !l.a.Next()
l.bDone = true
return l
}
l := &binaryIterator{
a: a,
b: parent.initBinaryStorageIterator(account),
account: account,
} }
l.aDone = !l.a.Next() l.aDone = !l.a.Next()
l.bDone = !l.b.Next() l.bDone = !l.b.Next()
@ -54,23 +116,22 @@ func (dl *diffLayer) newBinaryAccountIterator() AccountIterator {
// Next steps the iterator forward one element, returning false if exhausted, // Next steps the iterator forward one element, returning false if exhausted,
// or an error if iteration failed for some reason (e.g. root being iterated // or an error if iteration failed for some reason (e.g. root being iterated
// becomes stale and garbage collected). // becomes stale and garbage collected).
func (it *binaryAccountIterator) Next() bool { func (it *binaryIterator) Next() bool {
if it.aDone && it.bDone { if it.aDone && it.bDone {
return false return false
} }
nextB := it.b.Hash()
first: first:
nextA := it.a.Hash()
if it.aDone { if it.aDone {
it.k = it.b.Hash()
it.bDone = !it.b.Next() it.bDone = !it.b.Next()
it.k = nextB
return true return true
} }
if it.bDone { if it.bDone {
it.k = it.a.Hash()
it.aDone = !it.a.Next() it.aDone = !it.a.Next()
it.k = nextA
return true return true
} }
nextA, nextB := it.a.Hash(), it.b.Hash()
if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 { if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 {
it.aDone = !it.a.Next() it.aDone = !it.a.Next()
it.k = nextA it.k = nextA
@ -87,20 +148,43 @@ first:
// Error returns any failure that occurred during iteration, which might have // Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit (e.g. snapshot stack becoming stale). // caused a premature iteration exit (e.g. snapshot stack becoming stale).
func (it *binaryAccountIterator) Error() error { func (it *binaryIterator) Error() error {
return it.fail return it.fail
} }
// Hash returns the hash of the account the iterator is currently at. // Hash returns the hash of the account the iterator is currently at.
func (it *binaryAccountIterator) Hash() common.Hash { func (it *binaryIterator) Hash() common.Hash {
return it.k return it.k
} }
// Account returns the RLP encoded slim account the iterator is currently at, or // Account returns the RLP encoded slim account the iterator is currently at, or
// nil if the iterated snapshot stack became stale (you can check Error after // nil if the iterated snapshot stack became stale (you can check Error after
// to see if it failed or not). // to see if it failed or not).
func (it *binaryAccountIterator) Account() []byte { //
blob, err := it.a.layer.AccountRLP(it.k) // Note the returned account is not a copy, please don't modify it.
func (it *binaryIterator) Account() []byte {
if !it.accountIterator {
return nil
}
// The topmost iterator must be `diffAccountIterator`
blob, err := it.a.(*diffAccountIterator).layer.AccountRLP(it.k)
if err != nil {
it.fail = err
return nil
}
return blob
}
// Slot returns the raw storage slot data the iterator is currently at, or
// nil if the iterated snapshot stack became stale (you can check Error after
// to see if it failed or not).
//
// Note the returned slot is not a copy, please don't modify it.
func (it *binaryIterator) Slot() []byte {
if it.accountIterator {
return nil
}
blob, err := it.a.(*diffStorageIterator).layer.Storage(it.account, it.k)
if err != nil { if err != nil {
it.fail = err it.fail = err
return nil return nil
@ -109,7 +193,21 @@ func (it *binaryAccountIterator) Account() []byte {
} }
// Release recursively releases all the iterators in the stack. // Release recursively releases all the iterators in the stack.
func (it *binaryAccountIterator) Release() { func (it *binaryIterator) Release() {
it.a.Release() it.a.Release()
it.b.Release() it.b.Release()
} }
// newBinaryAccountIterator creates a simplistic account iterator to step over
// all the accounts in a slow, but eaily verifiable way.
func (dl *diffLayer) newBinaryAccountIterator() AccountIterator {
iter := dl.initBinaryAccountIterator()
return iter.(AccountIterator)
}
// newBinaryStorageIterator creates a simplistic account iterator to step over
// all the storage slots in a slow, but eaily verifiable way.
func (dl *diffLayer) newBinaryStorageIterator(account common.Hash) StorageIterator {
iter := dl.initBinaryStorageIterator(account)
return iter.(StorageIterator)
}

View file

@ -24,23 +24,23 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// weightedAccountIterator is an account iterator with an assigned weight. It is // weightedIterator is a iterator with an assigned weight. It is used to prioritise
// used to prioritise which account is the correct one if multiple iterators find // which account or storage slot is the correct one if multiple iterators find the
// the same one (modified in multiple consecutive blocks). // same one (modified in multiple consecutive blocks).
type weightedAccountIterator struct { type weightedIterator struct {
it AccountIterator it Iterator
priority int priority int
} }
// weightedAccountIterators is a set of iterators implementing the sort.Interface. // weightedIterators is a set of iterators implementing the sort.Interface.
type weightedAccountIterators []*weightedAccountIterator type weightedIterators []*weightedIterator
// Len implements sort.Interface, returning the number of active iterators. // Len implements sort.Interface, returning the number of active iterators.
func (its weightedAccountIterators) Len() int { return len(its) } func (its weightedIterators) Len() int { return len(its) }
// Less implements sort.Interface, returning which of two iterators in the stack // Less implements sort.Interface, returning which of two iterators in the stack
// is before the other. // is before the other.
func (its weightedAccountIterators) Less(i, j int) bool { func (its weightedIterators) Less(i, j int) bool {
// Order the iterators primarily by the account hashes // Order the iterators primarily by the account hashes
hashI := its[i].it.Hash() hashI := its[i].it.Hash()
hashJ := its[j].it.Hash() hashJ := its[j].it.Hash()
@ -51,45 +51,64 @@ func (its weightedAccountIterators) Less(i, j int) bool {
case 1: case 1:
return false return false
} }
// Same account in multiple layers, split by priority // Same account/storage-slot in multiple layers, split by priority
return its[i].priority < its[j].priority return its[i].priority < its[j].priority
} }
// Swap implements sort.Interface, swapping two entries in the iterator stack. // Swap implements sort.Interface, swapping two entries in the iterator stack.
func (its weightedAccountIterators) Swap(i, j int) { func (its weightedIterators) Swap(i, j int) {
its[i], its[j] = its[j], its[i] its[i], its[j] = its[j], its[i]
} }
// fastAccountIterator is a more optimized multi-layer iterator which maintains a // fastIterator is a more optimized multi-layer iterator which maintains a
// direct mapping of all iterators leading down to the bottom layer. // direct mapping of all iterators leading down to the bottom layer.
type fastAccountIterator struct { type fastIterator struct {
tree *Tree // Snapshot tree to reinitialize stale sub-iterators with tree *Tree // Snapshot tree to reinitialize stale sub-iterators with
root common.Hash // Root hash to reinitialize stale sub-iterators through root common.Hash // Root hash to reinitialize stale sub-iterators through
curAccount []byte
iterators weightedAccountIterators curAccount []byte
curSlot []byte
iterators weightedIterators
initiated bool initiated bool
account bool
fail error fail error
} }
// newFastAccountIterator creates a new hierarhical account iterator with one // newFastIterator creates a new hierarhical account or storage iterator with one
// element per diff layer. The returned combo iterator can be used to walk over // element per diff layer. The returned combo iterator can be used to walk over
// the entire snapshot diff stack simultaneously. // the entire snapshot diff stack simultaneously.
func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) { func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash, accountIterator bool) (*fastIterator, error) {
snap := tree.Snapshot(root) snap := tree.Snapshot(root)
if snap == nil { if snap == nil {
return nil, fmt.Errorf("unknown snapshot: %x", root) return nil, fmt.Errorf("unknown snapshot: %x", root)
} }
fi := &fastAccountIterator{ fi := &fastIterator{
tree: tree, tree: tree,
root: root, root: root,
account: accountIterator,
} }
current := snap.(snapshot) current := snap.(snapshot)
for depth := 0; current != nil; depth++ { for depth := 0; current != nil; depth++ {
fi.iterators = append(fi.iterators, &weightedAccountIterator{ if accountIterator {
it: current.AccountIterator(seek), fi.iterators = append(fi.iterators, &weightedIterator{
priority: depth, it: current.AccountIterator(seek),
}) priority: depth,
})
} else {
// If the whole storage is destructed in this layer, don't
// bother deeper layer anymore. But we should still keep
// the iterator for this layer, since the iterator can contain
// some valid slots which belongs to the re-created account.
it, destructed := current.StorageIterator(account, seek)
fi.iterators = append(fi.iterators, &weightedIterator{
it: it,
priority: depth,
})
if destructed {
break
}
}
current = current.Parent() current = current.Parent()
} }
fi.init() fi.init()
@ -98,7 +117,7 @@ func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (Acc
// init walks over all the iterators and resolves any clashes between them, after // init walks over all the iterators and resolves any clashes between them, after
// which it prepares the stack for step-by-step iteration. // which it prepares the stack for step-by-step iteration.
func (fi *fastAccountIterator) init() { func (fi *fastIterator) init() {
// Track which account hashes are iterators positioned on // Track which account hashes are iterators positioned on
var positioned = make(map[common.Hash]int) var positioned = make(map[common.Hash]int)
@ -153,7 +172,7 @@ func (fi *fastAccountIterator) init() {
} }
// Next steps the iterator forward one element, returning false if exhausted. // Next steps the iterator forward one element, returning false if exhausted.
func (fi *fastAccountIterator) Next() bool { func (fi *fastIterator) Next() bool {
if len(fi.iterators) == 0 { if len(fi.iterators) == 0 {
return false return false
} }
@ -161,21 +180,25 @@ func (fi *fastAccountIterator) Next() bool {
// Don't forward first time -- we had to 'Next' once in order to // Don't forward first time -- we had to 'Next' once in order to
// do the sorting already // do the sorting already
fi.initiated = true fi.initiated = true
fi.curAccount = fi.iterators[0].it.Account() if fi.account {
fi.curAccount = fi.iterators[0].it.(AccountIterator).Account()
} else {
fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot()
}
if innerErr := fi.iterators[0].it.Error(); innerErr != nil { if innerErr := fi.iterators[0].it.Error(); innerErr != nil {
fi.fail = innerErr fi.fail = innerErr
return false return false
} }
if fi.curAccount != nil { if fi.curAccount != nil || fi.curSlot != nil {
return true return true
} }
// Implicit else: we've hit a nil-account, and need to fall through to the // Implicit else: we've hit a nil-account or nil-slot, and need to
// loop below to land on something non-nil // fall through to the loop below to land on something non-nil
} }
// If an account is deleted in one of the layers, the key will still be there, // If an account or a slot is deleted in one of the layers, the key will
// but the actual value will be nil. However, the iterator should not // still be there, but the actual value will be nil. However, the iterator
// export nil-values (but instead simply omit the key), so we need to loop // should not export nil-values (but instead simply omit the key), so we
// here until we either // need to loop here until we either
// - get a non-nil value, // - get a non-nil value,
// - hit an error, // - hit an error,
// - or exhaust the iterator // - or exhaust the iterator
@ -183,12 +206,16 @@ func (fi *fastAccountIterator) Next() bool {
if !fi.next(0) { if !fi.next(0) {
return false // exhausted return false // exhausted
} }
fi.curAccount = fi.iterators[0].it.Account() if fi.account {
fi.curAccount = fi.iterators[0].it.(AccountIterator).Account()
} else {
fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot()
}
if innerErr := fi.iterators[0].it.Error(); innerErr != nil { if innerErr := fi.iterators[0].it.Error(); innerErr != nil {
fi.fail = innerErr fi.fail = innerErr
return false // error return false // error
} }
if fi.curAccount != nil { if fi.curAccount != nil || fi.curSlot != nil {
break // non-nil value found break // non-nil value found
} }
} }
@ -201,7 +228,7 @@ func (fi *fastAccountIterator) Next() bool {
// For example, if the iterated hashes become [2,3,5,5,8,9,10], then we should // For example, if the iterated hashes become [2,3,5,5,8,9,10], then we should
// invoke next(3), which will call Next on elem 3 (the second '5') and will // invoke next(3), which will call Next on elem 3 (the second '5') and will
// cascade along the list, applying the same operation if needed. // cascade along the list, applying the same operation if needed.
func (fi *fastAccountIterator) next(idx int) bool { func (fi *fastIterator) next(idx int) bool {
// If this particular iterator got exhausted, remove it and return true (the // If this particular iterator got exhausted, remove it and return true (the
// next one is surely not exhausted yet, otherwise it would have been removed // next one is surely not exhausted yet, otherwise it would have been removed
// already). // already).
@ -262,7 +289,7 @@ func (fi *fastAccountIterator) next(idx int) bool {
} }
// move advances an iterator to another position in the list. // move advances an iterator to another position in the list.
func (fi *fastAccountIterator) move(index, newpos int) { func (fi *fastIterator) move(index, newpos int) {
elem := fi.iterators[index] elem := fi.iterators[index]
copy(fi.iterators[index:], fi.iterators[index+1:newpos+1]) copy(fi.iterators[index:], fi.iterators[index+1:newpos+1])
fi.iterators[newpos] = elem fi.iterators[newpos] = elem
@ -270,23 +297,30 @@ func (fi *fastAccountIterator) move(index, newpos int) {
// Error returns any failure that occurred during iteration, which might have // Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit (e.g. snapshot stack becoming stale). // caused a premature iteration exit (e.g. snapshot stack becoming stale).
func (fi *fastAccountIterator) Error() error { func (fi *fastIterator) Error() error {
return fi.fail return fi.fail
} }
// Hash returns the current key // Hash returns the current key
func (fi *fastAccountIterator) Hash() common.Hash { func (fi *fastIterator) Hash() common.Hash {
return fi.iterators[0].it.Hash() return fi.iterators[0].it.Hash()
} }
// Account returns the current key // Account returns the current account blob.
func (fi *fastAccountIterator) Account() []byte { // Note the returned account is not a copy, please don't modify it.
func (fi *fastIterator) Account() []byte {
return fi.curAccount return fi.curAccount
} }
// Slot returns the current storage slot.
// Note the returned slot is not a copy, please don't modify it.
func (fi *fastIterator) Slot() []byte {
return fi.curSlot
}
// Release iterates over all the remaining live layer iterators and releases each // Release iterates over all the remaining live layer iterators and releases each
// of thme individually. // of thme individually.
func (fi *fastAccountIterator) Release() { func (fi *fastIterator) Release() {
for _, it := range fi.iterators { for _, it := range fi.iterators {
it.it.Release() it.it.Release()
} }
@ -294,9 +328,23 @@ func (fi *fastAccountIterator) Release() {
} }
// Debug is a convencience helper during testing // Debug is a convencience helper during testing
func (fi *fastAccountIterator) Debug() { func (fi *fastIterator) Debug() {
for _, it := range fi.iterators { for _, it := range fi.iterators {
fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0]) fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0])
} }
fmt.Println() fmt.Println()
} }
// newFastAccountIterator creates a new hierarhical account iterator with one
// element per diff layer. The returned combo iterator can be used to walk over
// the entire snapshot diff stack simultaneously.
func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) {
return newFastIterator(tree, root, common.Hash{}, seek, true)
}
// newFastStorageIterator creates a new hierarhical storage iterator with one
// element per diff layer. The returned combo iterator can be used to walk over
// the entire snapshot diff stack simultaneously.
func newFastStorageIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) {
return newFastIterator(tree, root, account, seek, false)
}

View file

@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
) )
// TestAccountIteratorBasics tests some simple single-layer iteration // TestAccountIteratorBasics tests some simple single-layer(diff and disk) iteration
func TestAccountIteratorBasics(t *testing.T) { func TestAccountIteratorBasics(t *testing.T) {
var ( var (
destructs = make(map[common.Hash]struct{}) destructs = make(map[common.Hash]struct{})
@ -53,9 +53,55 @@ func TestAccountIteratorBasics(t *testing.T) {
} }
} }
// Add some (identical) layers on top // Add some (identical) layers on top
parent := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage))
it := parent.AccountIterator(common.Hash{}) it := diffLayer.AccountIterator(common.Hash{})
verifyIterator(t, 100, it) 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 { type testIterator struct {
@ -87,6 +133,10 @@ func (ti *testIterator) Account() []byte {
return nil return nil
} }
func (ti *testIterator) Slot() []byte {
return nil
}
func (ti *testIterator) Release() {} func (ti *testIterator) Release() {}
func TestFastIteratorBasics(t *testing.T) { func TestFastIteratorBasics(t *testing.T) {
@ -102,13 +152,12 @@ func TestFastIteratorBasics(t *testing.T) {
{9, 10}, {10, 13, 15, 16}}, {9, 10}, {10, 13, 15, 16}},
expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}},
} { } {
var iterators []*weightedAccountIterator var iterators []*weightedIterator
for i, data := range tc.lists { for i, data := range tc.lists {
it := newTestIterator(data...) it := newTestIterator(data...)
iterators = append(iterators, &weightedAccountIterator{it, i}) iterators = append(iterators, &weightedIterator{it, i})
} }
fi := &fastAccountIterator{ fi := &fastIterator{
iterators: iterators, iterators: iterators,
initiated: false, initiated: false,
} }
@ -122,7 +171,15 @@ func TestFastIteratorBasics(t *testing.T) {
} }
} }
func verifyIterator(t *testing.T, expCount int, it AccountIterator) { type verifyContent int
const (
verifyNothing verifyContent = iota
verifyAccount
verifyStorage
)
func verifyIterator(t *testing.T, expCount int, it Iterator, verify verifyContent) {
t.Helper() t.Helper()
var ( var (
@ -134,10 +191,13 @@ func verifyIterator(t *testing.T, expCount int, it AccountIterator) {
if bytes.Compare(last[:], hash[:]) >= 0 { if bytes.Compare(last[:], hash[:]) >= 0 {
t.Errorf("wrong order: %x >= %x", last, hash) t.Errorf("wrong order: %x >= %x", last, hash)
} }
if it.Account() == nil { 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) t.Errorf("iterator returned nil-value for hash %x", hash)
} }
count++ last = hash
} }
if count != expCount { if count != expCount {
t.Errorf("iterator count mismatch: have %d, want %d", count, expCount) t.Errorf("iterator count mismatch: have %d, want %d", count, expCount)
@ -173,13 +233,74 @@ func TestAccountIteratorTraversal(t *testing.T) {
// Verify the single and multi-layer iterators // Verify the single and multi-layer iterators
head := snaps.Snapshot(common.HexToHash("0x04")) head := snaps.Snapshot(common.HexToHash("0x04"))
verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{})) verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing)
verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator()) verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount)
it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{})
defer it.Release() verifyIterator(t, 7, it, verifyAccount)
it.Release()
verifyIterator(t, 7, it) // 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 // TestAccountIteratorTraversalValues tests some multi-layer iteration, where we
@ -242,8 +363,6 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil) snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil)
it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{})
defer it.Release()
head := snaps.Snapshot(common.HexToHash("0x09")) head := snaps.Snapshot(common.HexToHash("0x09"))
for it.Next() { for it.Next() {
hash := it.Hash() hash := it.Hash()
@ -255,6 +374,128 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) 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. // This testcase is notorious, all layers contain the exact same 200 accounts.
@ -285,13 +526,27 @@ func TestAccountIteratorLargeTraversal(t *testing.T) {
} }
// Iterate the entire stack and ensure everything is hit only once // Iterate the entire stack and ensure everything is hit only once
head := snaps.Snapshot(common.HexToHash("0x80")) head := snaps.Snapshot(common.HexToHash("0x80"))
verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{})) verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing)
verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator()) verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount)
it, _ := snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{}) it, _ := snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{})
defer it.Release() verifyIterator(t, 200, it, verifyAccount)
it.Release()
verifyIterator(t, 200, it) // 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 // TestAccountIteratorFlattening tests what happens when we
@ -351,40 +606,112 @@ func TestAccountIteratorSeek(t *testing.T) {
snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil,
randomAccountSet("0xcc", "0xf0", "0xff"), nil) randomAccountSet("0xcc", "0xf0", "0xff"), nil)
// Construct various iterators and ensure their tranversal is correct // 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")) it, _ := snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd"))
defer it.Release() defer it.Release()
verifyIterator(t, 3, it) // expected: ee, f0, ff verifyIterator(t, 3, it, verifyAccount) // expected: ee, f0, ff
it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa")) it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"))
defer it.Release() defer it.Release()
verifyIterator(t, 3, it) // expected: ee, f0, ff verifyIterator(t, 4, it, verifyAccount) // expected: aa, ee, f0, ff
it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff")) it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff"))
defer it.Release() defer it.Release()
verifyIterator(t, 0, it) // expected: nothing 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")) it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb"))
defer it.Release() defer it.Release()
verifyIterator(t, 5, it) // expected: cc, dd, ee, f0, ff verifyIterator(t, 6, it, verifyAccount) // expected: bb, cc, dd, ee, f0, ff
it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef")) it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef"))
defer it.Release() defer it.Release()
verifyIterator(t, 2, it) // expected: f0, ff verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff
it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0")) it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0"))
defer it.Release() defer it.Release()
verifyIterator(t, 1, it) // expected: ff verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff
it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff")) it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff"))
defer it.Release() defer it.Release()
verifyIterator(t, 0, it) // expected: nothing 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
} }
// TestIteratorDeletions tests that the iterator behaves correct when there are 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 // deleted accounts (where the Account() value is nil). The iterator
// should not output any accounts or nil-values for those cases. // should not output any accounts or nil-values for those cases.
func TestIteratorDeletions(t *testing.T) { func TestAccountIteratorDeletions(t *testing.T) {
// Create an empty base layer and a snapshot tree out of it // Create an empty base layer and a snapshot tree out of it
base := &diskLayer{ base := &diskLayer{
diskdb: rawdb.NewMemoryDatabase(), diskdb: rawdb.NewMemoryDatabase(),
@ -402,7 +729,7 @@ func TestIteratorDeletions(t *testing.T) {
deleted := common.HexToHash("0x22") deleted := common.HexToHash("0x22")
destructed := map[common.Hash]struct{}{ destructed := map[common.Hash]struct{}{
deleted: struct{}{}, deleted: {},
} }
snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"),
destructed, randomAccountSet("0x11", "0x33"), nil) destructed, randomAccountSet("0x11", "0x33"), nil)
@ -413,7 +740,7 @@ func TestIteratorDeletions(t *testing.T) {
// The output should be 11,33,44,55 // The output should be 11,33,44,55
it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{})
// Do a quick check // Do a quick check
verifyIterator(t, 4, it) verifyIterator(t, 4, it, verifyAccount)
it.Release() it.Release()
// And a more detailed verification that we indeed do not see '0x22' // And a more detailed verification that we indeed do not see '0x22'
@ -430,6 +757,63 @@ func TestIteratorDeletions(t *testing.T) {
} }
} }
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 // 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 // exact same 200 accounts. That means that we need to process 2000 items, but
// only spit out 200 values eventually. // only spit out 200 values eventually.

View file

@ -158,7 +158,11 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
} }
accountData := make(map[common.Hash][]byte) accountData := make(map[common.Hash][]byte)
for _, entry := range accounts { for _, entry := range accounts {
accountData[entry.Hash] = entry.Blob if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
accountData[entry.Hash] = entry.Blob
} else {
accountData[entry.Hash] = nil
}
} }
var storage []journalStorage var storage []journalStorage
if err := r.Decode(&storage); err != nil { if err := r.Decode(&storage); err != nil {
@ -168,7 +172,11 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
for _, entry := range storage { for _, entry := range storage {
slots := make(map[common.Hash][]byte) slots := make(map[common.Hash][]byte)
for i, key := range entry.Keys { for i, key := range entry.Keys {
slots[key] = entry.Vals[i] if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
slots[key] = entry.Vals[i]
} else {
slots[key] = nil
}
} }
storageData[entry.Hash] = slots storageData[entry.Hash] = slots
} }

View file

@ -138,6 +138,9 @@ type snapshot interface {
// AccountIterator creates an account iterator over an arbitrary layer. // AccountIterator creates an account iterator over an arbitrary layer.
AccountIterator(seek common.Hash) AccountIterator AccountIterator(seek common.Hash) AccountIterator
// StorageIterator creates a storage iterator over an arbitrary layer.
StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool)
} }
// SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent // SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent
@ -601,3 +604,9 @@ func (t *Tree) Rebuild(root common.Hash) {
func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) { func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) {
return newFastAccountIterator(t, root, seek) return newFastAccountIterator(t, root, seek)
} }
// StorageIterator creates a new storage iterator for the specified root hash and
// account. The iterator will be move to the specific start position.
func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) {
return newFastStorageIterator(t, root, account, seek)
}

View file

@ -60,6 +60,29 @@ func randomAccountSet(hashes ...string) map[common.Hash][]byte {
return accounts 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 // 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 // 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. // to check internal corner case around the bottom-most memory accumulator.

View file

@ -92,7 +92,7 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
// Iterate over the key-range and delete all of them // Iterate over the key-range and delete all of them
start, logged := time.Now(), time.Now() start, logged := time.Now(), time.Now()
it := db.NewIteratorWithStart(prefix) it := db.NewIterator(prefix, nil)
for it.Next() { for it.Next() {
// Skip any keys with the correct prefix but wrong lenth (trie nodes) // Skip any keys with the correct prefix but wrong lenth (trie nodes)
key := it.Key() key := it.Key()
@ -113,7 +113,8 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
return err return err
} }
batch.Reset() batch.Reset()
it = db.NewIteratorWithStart(key) seekPos := key[len(prefix):]
it = db.NewIterator(prefix, seekPos)
if time.Since(logged) > 8*time.Second { if time.Since(logged) > 8*time.Second {
log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))

View file

@ -60,7 +60,7 @@ func TestWipe(t *testing.T) {
// Sanity check that all the keys are present // Sanity check that all the keys are present
var items int var items int
it := db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -69,7 +69,7 @@ func TestWipe(t *testing.T) {
items++ items++
} }
} }
it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -88,7 +88,7 @@ func TestWipe(t *testing.T) {
<-wipeSnapshot(db, true) <-wipeSnapshot(db, true)
// Iterate over the database end ensure no snapshot information remains // Iterate over the database end ensure no snapshot information remains
it = db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -97,7 +97,7 @@ func TestWipe(t *testing.T) {
t.Errorf("snapshot entry remained after wipe: %x", key) t.Errorf("snapshot entry remained after wipe: %x", key)
} }
} }
it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -112,7 +112,7 @@ func TestWipe(t *testing.T) {
// Iterate over the database and ensure miscellaneous items are present // Iterate over the database and ensure miscellaneous items are present
items = 0 items = 0
it = db.NewIterator() it = db.NewIterator(nil, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {

View file

@ -18,6 +18,7 @@
package state package state
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -294,9 +295,12 @@ func (s *StateDB) GetCodeSize(addr common.Address) int {
if stateObject.code != nil { if stateObject.code != nil {
return len(stateObject.code) return len(stateObject.code)
} }
if bytes.Equal(stateObject.CodeHash(), emptyCode[:]) {
return 0
}
size, err := s.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash())) size, err := s.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash()))
if err != nil { if err != nil {
s.setError(err) s.setError(fmt.Errorf("GetCodeSize (%x) error: %v", addr[:], err))
} }
return size return size
} }
@ -465,14 +469,16 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
if err != nil { if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
} }
s.setError(s.trie.TryUpdate(addr[:], data)) if err = s.trie.TryUpdate(addr[:], data); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
}
// If state snapshotting is active, cache the data til commit. Note, this // If state snapshotting is active, cache the data til commit. Note, this
// update mechanism is not symmetric to the deletion, because whereas it is // update mechanism is not symmetric to the deletion, because whereas it is
// enough to track account updates at commit time, deletions need tracking // enough to track account updates at commit time, deletions need tracking
// at transaction boundary level to ensure we capture state clearing. // at transaction boundary level to ensure we capture state clearing.
if s.snap != nil { if s.snap != nil {
s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(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)
} }
} }
@ -484,7 +490,9 @@ func (s *StateDB) deleteStateObject(obj *stateObject) {
} }
// Delete the account from the trie // Delete the account from the trie
addr := obj.Address() addr := obj.Address()
s.setError(s.trie.TryDelete(addr[:])) if err := s.trie.TryDelete(addr[:]); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
}
} }
// getStateObject retrieves a state object given by the address, returning nil if // getStateObject retrieves a state object given by the address, returning nil if
@ -536,8 +544,11 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now()) defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now())
} }
enc, err := s.trie.TryGet(addr[:]) enc, err := s.trie.TryGet(addr[:])
if err != nil {
s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr[:], err))
return nil
}
if len(enc) == 0 { if len(enc) == 0 {
s.setError(err)
return nil return nil
} }
if err := rlp.DecodeBytes(enc, &data); err != nil { if err := rlp.DecodeBytes(enc, &data); err != nil {
@ -804,6 +815,9 @@ func (s *StateDB) clearJournalAndRefund() {
// Commit writes the state to the underlying in-memory trie database. // Commit writes the state to the underlying in-memory trie database.
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
if s.dbErr != nil {
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
}
// Finalize any pending changes and merge everything into the tries // Finalize any pending changes and merge everything into the tries
s.IntermediateRoot(deleteEmptyObjects) s.IntermediateRoot(deleteEmptyObjects)

View file

@ -60,7 +60,7 @@ func TestUpdateLeaks(t *testing.T) {
} }
// Ensure that no data was leaked into the database // Ensure that no data was leaked into the database
it := db.NewIterator() it := db.NewIterator(nil, nil)
for it.Next() { for it.Next() {
t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value()) t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value())
} }
@ -118,7 +118,7 @@ func TestIntermediateLeaks(t *testing.T) {
t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex())
} }
it := finalDb.NewIterator() it := finalDb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
key, fvalue := it.Key(), it.Value() key, fvalue := it.Key(), it.Value()
tvalue, err := transDb.Get(key) tvalue, err := transDb.Get(key)
@ -131,7 +131,7 @@ func TestIntermediateLeaks(t *testing.T) {
} }
it.Release() it.Release()
it = transDb.NewIterator() it = transDb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
key, tvalue := it.Key(), it.Value() key, tvalue := it.Key(), it.Value()
fvalue, err := finalDb.Get(key) fvalue, err := finalDb.Get(key)
@ -680,3 +680,50 @@ func TestDeleteCreateRevert(t *testing.T) {
t.Fatalf("self-destructed contract came alive") t.Fatalf("self-destructed contract came alive")
} }
} }
// TestMissingTrieNodes tests that if the statedb fails to load parts of the trie,
// the Commit operation fails with an error
// If we are missing trie nodes, we should not continue writing to the trie
func TestMissingTrieNodes(t *testing.T) {
// Create an initial state with a few accounts
memDb := rawdb.NewMemoryDatabase()
db := NewDatabase(memDb)
var root common.Hash
state, _ := New(common.Hash{}, db, nil)
addr := toAddr([]byte("so"))
{
state.SetBalance(addr, big.NewInt(1))
state.SetCode(addr, []byte{1, 2, 3})
a2 := toAddr([]byte("another"))
state.SetBalance(a2, big.NewInt(100))
state.SetCode(a2, []byte{1, 2, 4})
root, _ = state.Commit(false)
t.Logf("root: %x", root)
// force-flush
state.Database().TrieDB().Cap(0)
}
// Create a new state on the old root
state, _ = New(root, db, nil)
// Now we clear out the memdb
it := memDb.NewIterator(nil, nil)
for it.Next() {
k := it.Key()
// Leave the root intact
if !bytes.Equal(k, root[:]) {
t.Logf("key: %x", k)
memDb.Delete(k)
}
}
balance := state.GetBalance(addr)
// The removed elem should lead to it returning zero balance
if exp, got := uint64(0), balance.Uint64(); got != exp {
t.Errorf("expected %d, got %d", exp, got)
}
// Modify the state
state.SetBalance(addr, big.NewInt(2))
root, err := state.Commit(false)
if err == nil {
t.Fatalf("expected error, got root :%x", root)
}
}

View file

@ -89,6 +89,6 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co
context := NewEVMContext(msg, header, bc, author) context := NewEVMContext(msg, header, bc, author)
vm := vm.NewEVM(context, statedb, config, cfg) vm := vm.NewEVM(context, statedb, config, cfg)
_, _, _, err = ApplyMessage(vm, msg, gaspool) _, err = ApplyMessage(vm, msg, gaspool)
return err return err
} }

View file

@ -96,7 +96,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// about the transaction and calling mechanisms. // about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg) vmenv := vm.NewEVM(context, statedb, config, cfg)
// Apply the transaction to the current state (included in the env) // Apply the transaction to the current state (included in the env)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp) result, err := ApplyMessage(vmenv, msg, gp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -107,13 +107,13 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
} else { } else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
} }
*usedGas += gas *usedGas += result.UsedGas
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
// based on the eip phase, we're passing whether the root touch-delete accounts. // based on the eip phase, we're passing whether the root touch-delete accounts.
receipt := types.NewReceipt(root, failed, *usedGas) receipt := types.NewReceipt(root, result.Failed(), *usedGas)
receipt.TxHash = tx.Hash() receipt.TxHash = tx.Hash()
receipt.GasUsed = gas receipt.GasUsed = result.UsedGas
// if the transaction created a contract, store the creation address in the receipt. // if the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil { if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())

View file

@ -17,20 +17,14 @@
package core package core
import ( import (
"errors"
"math" "math"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
var (
errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas")
)
/* /*
The State Transitioning Model The State Transitioning Model
@ -63,7 +57,6 @@ type StateTransition struct {
// Message represents a message sent to a contract. // Message represents a message sent to a contract.
type Message interface { type Message interface {
From() common.Address From() common.Address
//FromFrontier() (common.Address, error)
To() *common.Address To() *common.Address
GasPrice() *big.Int GasPrice() *big.Int
@ -75,6 +68,41 @@ type Message interface {
Data() []byte Data() []byte
} }
// ExecutionResult includes all output after executing given evm
// message no matter the execution itself is successful or not.
type ExecutionResult struct {
UsedGas uint64 // Total used gas but include the refunded gas
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
}
// Unwrap returns the internal evm error which allows us for further
// analysis outside.
func (result *ExecutionResult) Unwrap() error {
return result.Err
}
// Failed returns the indicator whether the execution is successful or not
func (result *ExecutionResult) Failed() bool { return result.Err != nil }
// Return is a helper function to help caller distinguish between revert reason
// and function return. Return returns the data after execution if no error occurs.
func (result *ExecutionResult) Return() []byte {
if result.Err != nil {
return nil
}
return common.CopyBytes(result.ReturnData)
}
// Revert returns the concrete revert reason if the execution is aborted by `REVERT`
// opcode. Note the reason can be nil if no data supplied with revert opcode.
func (result *ExecutionResult) Revert() []byte {
if result.Err != vm.ErrExecutionReverted {
return nil
}
return common.CopyBytes(result.ReturnData)
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) { func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) {
// Set the starting gas for the raw transaction // Set the starting gas for the raw transaction
@ -99,13 +127,13 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo
nonZeroGas = params.TxDataNonZeroGasEIP2028 nonZeroGas = params.TxDataNonZeroGasEIP2028
} }
if (math.MaxUint64-gas)/nonZeroGas < nz { if (math.MaxUint64-gas)/nonZeroGas < nz {
return 0, vm.ErrOutOfGas return 0, ErrGasUintOverflow
} }
gas += nz * nonZeroGas gas += nz * nonZeroGas
z := uint64(len(data)) - nz z := uint64(len(data)) - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z { if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, vm.ErrOutOfGas return 0, ErrGasUintOverflow
} }
gas += z * params.TxDataZeroGas gas += z * params.TxDataZeroGas
} }
@ -132,7 +160,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
// the gas used (which includes gas refunds) and an error if it failed. An error always // the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular // indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block. // state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) { func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
return NewStateTransition(evm, msg, gp).TransitionDb() return NewStateTransition(evm, msg, gp).TransitionDb()
} }
@ -144,19 +172,10 @@ func (st *StateTransition) to() common.Address {
return *st.msg.To() return *st.msg.To()
} }
func (st *StateTransition) useGas(amount uint64) error {
if st.gas < amount {
return vm.ErrOutOfGas
}
st.gas -= amount
return nil
}
func (st *StateTransition) buyGas() error { func (st *StateTransition) buyGas() error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas return ErrInsufficientFunds
} }
if err := st.gp.SubGas(st.msg.Gas()); err != nil { if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err return err
@ -182,11 +201,32 @@ func (st *StateTransition) preCheck() error {
} }
// TransitionDb will transition the state by applying the current message and // TransitionDb will transition the state by applying the current message and
// returning the result including the used gas. It returns an error if failed. // returning the evm execution result with following fields.
// An error indicates a consensus issue. //
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) { // - used gas:
if err = st.preCheck(); err != nil { // total gas used (including gas being refunded)
return // - returndata:
// the returned data from evm
// - concrete execution error:
// various **EVM** error which aborts the execution,
// e.g. ErrOutOfGas, ErrExecutionReverted
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
// 1. the nonce of the message caller is correct
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
// 3. the amount of gas required is available in the block
// 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call
// Check clauses 1-3, buy gas if everything is correct
if err := st.preCheck(); err != nil {
return nil, err
} }
msg := st.msg msg := st.msg
sender := vm.AccountRef(msg.From()) sender := vm.AccountRef(msg.From())
@ -194,42 +234,39 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber) istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
contractCreation := msg.To() == nil contractCreation := msg.To() == nil
// Pay intrinsic gas // Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul) gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
if err != nil { if err != nil {
return nil, 0, false, err return nil, err
} }
if err = st.useGas(gas); err != nil { if st.gas < gas {
return nil, 0, false, err return nil, ErrIntrinsicGas
} }
st.gas -= gas
// Check clause 6
if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) {
return nil, ErrInsufficientFundsForTransfer
}
var ( var (
evm = st.evm ret []byte
// vm errors do not effect consensus and are therefor vmerr error // vm errors do not effect consensus and are therefore not assigned to err
// not assigned to err, except for insufficient balance
// error.
vmerr error
) )
if contractCreation { if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
} else { } else {
// Increment the nonce for the next transaction // Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
if vmerr != nil {
log.Debug("VM returned with error", "err", vmerr)
// The only possible consensus-error would be if there wasn't
// sufficient balance to make the transfer happen. The first
// balance transfer may never fail.
if vmerr == vm.ErrInsufficientBalance {
return nil, 0, false, vmerr
}
} }
st.refundGas() st.refundGas()
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err return &ExecutionResult{
UsedGas: st.gasUsed(),
Err: vmerr,
ReturnData: ret,
}, nil
} }
func (st *StateTransition) refundGas() { func (st *StateTransition) refundGas() {

View file

@ -59,10 +59,6 @@ var (
// ErrInvalidSender is returned if the transaction contains an invalid signature. // ErrInvalidSender is returned if the transaction contains an invalid signature.
ErrInvalidSender = errors.New("invalid sender") 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 // ErrUnderpriced is returned if a transaction's gas price is below the minimum
// configured for the transaction pool. // configured for the transaction pool.
ErrUnderpriced = errors.New("transaction underpriced") ErrUnderpriced = errors.New("transaction underpriced")
@ -71,14 +67,6 @@ var (
// with a different one without the required price bump. // with a different one without the required price bump.
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") 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 // ErrGasLimit is returned if a transaction's requested gas limit exceeds the
// maximum allowance of the current block. // maximum allowance of the current block.
ErrGasLimit = errors.New("exceeds block gas limit") ErrGasLimit = errors.New("exceeds block gas limit")

View file

@ -117,7 +117,7 @@ func validateTxPoolInternals(pool *TxPool) error {
last = nonce last = nonce
} }
} }
if nonce := pool.Nonce(addr); nonce != last+1 { if nonce := pool.pendingNonces.get(addr); nonce != last+1 {
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1) return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
} }
} }
@ -857,8 +857,12 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
// //
// This logic should not hold for local transactions, unless the local tracking // This logic should not hold for local transactions, unless the local tracking
// mechanism is disabled. // mechanism is disabled.
func TestTransactionQueueTimeLimiting(t *testing.T) { testTransactionQueueTimeLimiting(t, false) } func TestTransactionQueueTimeLimiting(t *testing.T) {
func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) { testTransactionQueueTimeLimiting(t, true) } testTransactionQueueTimeLimiting(t, false)
}
func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) {
testTransactionQueueTimeLimiting(t, true)
}
func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
// Reduce the eviction interval to a testable amount // Reduce the eviction interval to a testable amount

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