mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
Merge branch 'master' of github.com:ethereum/go-ethereum
This commit is contained in:
commit
c2f756bf17
140 changed files with 9487 additions and 1690 deletions
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -1,3 +1,9 @@
|
||||||
|
Hi there,
|
||||||
|
|
||||||
|
please note that this is an issue tracker reserved for bug reports and feature requests.
|
||||||
|
|
||||||
|
For general questions please use the gitter channel or the Ethereum stack exchange at https://ethereum.stackexchange.com.
|
||||||
|
|
||||||
#### System information
|
#### System information
|
||||||
|
|
||||||
Geth version: `geth version`
|
Geth version: `geth version`
|
||||||
|
|
|
||||||
28
.travis.yml
28
.travis.yml
|
|
@ -38,7 +38,7 @@ matrix:
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage -misspell
|
- go run build/ci.go test -coverage
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: 1.9.x
|
go: 1.9.x
|
||||||
|
|
@ -48,7 +48,21 @@ matrix:
|
||||||
- brew install caskroom/cask/brew-cask
|
- brew install caskroom/cask/brew-cask
|
||||||
- brew cask install osxfuse
|
- brew cask install osxfuse
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage -misspell
|
- go run build/ci.go test -coverage
|
||||||
|
|
||||||
|
# This builder only tests code linters on latest version of Go
|
||||||
|
- os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
go: 1.9.x
|
||||||
|
env:
|
||||||
|
- lint
|
||||||
|
script:
|
||||||
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||||
|
- sudo modprobe fuse
|
||||||
|
- sudo chmod 666 /dev/fuse
|
||||||
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
|
- go run build/ci.go lint
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA and Linux Azure uploads
|
# This builder does the Ubuntu PPA and Linux Azure uploads
|
||||||
- os: linux
|
- os: linux
|
||||||
|
|
@ -133,16 +147,16 @@ matrix:
|
||||||
- azure-android
|
- azure-android
|
||||||
- maven-android
|
- maven-android
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz | tar -xz
|
- curl https://storage.googleapis.com/golang/go1.9.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
|
||||||
script:
|
script:
|
||||||
# Build the Android archive and upload it to Maven Central and Azure
|
# Build the Android archive and upload it to Maven Central and Azure
|
||||||
- curl https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip -o android-ndk-r14b.zip
|
- curl https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip -o android-ndk-r15c.zip
|
||||||
- unzip -q android-ndk-r14b.zip && rm android-ndk-r14b.zip
|
- unzip -q android-ndk-r15c.zip && rm android-ndk-r15c.zip
|
||||||
- mv android-ndk-r14b $HOME
|
- mv android-ndk-r15c $HOME
|
||||||
- export ANDROID_NDK=$HOME/android-ndk-r14b
|
- export ANDROID_NDK=$HOME/android-ndk-r15c
|
||||||
|
|
||||||
- mkdir -p $GOPATH/src/github.com/ethereum
|
- mkdir -p $GOPATH/src/github.com/ethereum
|
||||||
- ln -s `pwd` $GOPATH/src/github.com/ethereum
|
- ln -s `pwd` $GOPATH/src/github.com/ethereum
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ The go-ethereum project comes with several wrappers/executables found in the `cm
|
||||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default) archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default) archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
||||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow insolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||||
| `swarm` | swarm daemon and tools. This is the entrypoint for the swarm network. `swarm --help` for command line options and subcommands. See https://swarm-guide.readthedocs.io for swarm documentation. |
|
| `swarm` | swarm daemon and tools. This is the entrypoint for the swarm network. `swarm --help` for command line options and subcommands. See https://swarm-guide.readthedocs.io for swarm documentation. |
|
||||||
|
|
@ -130,6 +130,8 @@ docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
|
||||||
|
|
||||||
This will start geth in fast sync mode with a DB memory allowance of 512MB just as the above command does. It will also create a persistent volume in your home directory for saving your blockchain as well as map the default ports. There is also an `alpine` tag available for a slim version of the image.
|
This will start geth in fast sync mode with a DB memory allowance of 512MB just as the above command does. It will also create a persistent volume in your home directory for saving your blockchain as well as map the default ports. There is also an `alpine` tag available for a slim version of the image.
|
||||||
|
|
||||||
|
Do not forget `--rpcaddr 0.0.0.0`, if you want to access RPC from other containers and/or hosts. By default, `geth` binds to the local interface and RPC endpoints is not accessible from the outside.
|
||||||
|
|
||||||
### Programatically interfacing Geth nodes
|
### Programatically interfacing Geth nodes
|
||||||
|
|
||||||
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.7.2
|
1.7.3
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
|
|
@ -76,106 +72,27 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
return append(method.Id(), arguments...), nil
|
return append(method.Id(), arguments...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// these variable are used to determine certain types during type assertion for
|
|
||||||
// assignment.
|
|
||||||
var (
|
|
||||||
r_interSlice = reflect.TypeOf([]interface{}{})
|
|
||||||
r_hash = reflect.TypeOf(common.Hash{})
|
|
||||||
r_bytes = reflect.TypeOf([]byte{})
|
|
||||||
r_byte = reflect.TypeOf(byte(0))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Unpack output in v according to the abi specification
|
// Unpack output in v according to the abi specification
|
||||||
func (abi ABI) Unpack(v interface{}, name string, output []byte) error {
|
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||||
var method = abi.Methods[name]
|
if err = bytesAreProper(output); err != nil {
|
||||||
|
|
||||||
if len(output) == 0 {
|
|
||||||
return fmt.Errorf("abi: unmarshalling empty output")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the passed value is a pointer
|
|
||||||
valueOf := reflect.ValueOf(v)
|
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
value = valueOf.Elem()
|
|
||||||
typ = value.Type()
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(method.Outputs) > 1 {
|
|
||||||
switch value.Kind() {
|
|
||||||
// struct will match named return values to the struct's field
|
|
||||||
// names
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < len(method.Outputs); i++ {
|
|
||||||
marshalledValue, err := toGoType(i, method.Outputs[i], output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
// since there can't be naming collisions with contracts and events,
|
||||||
|
// we need to decide whether we're calling a method or an event
|
||||||
for j := 0; j < typ.NumField(); j++ {
|
var unpack unpacker
|
||||||
field := typ.Field(j)
|
if method, ok := abi.Methods[name]; ok {
|
||||||
// TODO read tags: `abi:"fieldName"`
|
unpack = method
|
||||||
if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
|
} else if event, ok := abi.Events[name]; ok {
|
||||||
if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
|
unpack = event
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
|
||||||
if !value.Type().AssignableTo(r_interSlice) {
|
|
||||||
return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the slice already contains values, set those instead of the interface slice itself.
|
|
||||||
if value.Len() > 0 {
|
|
||||||
if len(method.Outputs) > value.Len() {
|
|
||||||
return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(method.Outputs), value.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(method.Outputs); i++ {
|
|
||||||
marshalledValue, err := toGoType(i, method.Outputs[i], output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
|
||||||
if err := set(value.Index(i).Elem(), reflectValue, method.Outputs[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new slice and start appending the unmarshalled
|
|
||||||
// values to the new interface slice.
|
|
||||||
z := reflect.MakeSlice(typ, 0, len(method.Outputs))
|
|
||||||
for i := 0; i < len(method.Outputs); i++ {
|
|
||||||
marshalledValue, err := toGoType(i, method.Outputs[i], output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
z = reflect.Append(z, reflect.ValueOf(marshalledValue))
|
|
||||||
}
|
|
||||||
value.Set(z)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
marshalledValue, err := toGoType(0, method.Outputs[0], output)
|
return fmt.Errorf("abi: could not locate named method or event.")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// requires a struct to unpack into for a tuple return...
|
||||||
|
if unpack.isTupleReturn() {
|
||||||
|
return unpack.tupleUnpack(v, output)
|
||||||
|
}
|
||||||
|
return unpack.singleUnpack(v, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
|
|
|
||||||
|
|
@ -29,25 +29,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// formatSilceOutput add padding to the value and adds a size
|
|
||||||
func formatSliceOutput(v ...[]byte) []byte {
|
|
||||||
off := common.LeftPadBytes(big.NewInt(int64(len(v))).Bytes(), 32)
|
|
||||||
output := append(off, make([]byte, 0, len(v)*32)...)
|
|
||||||
|
|
||||||
for _, value := range v {
|
|
||||||
output = append(output, common.LeftPadBytes(value, 32)...)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
// quick helper padding
|
|
||||||
func pad(input []byte, size int, left bool) []byte {
|
|
||||||
if left {
|
|
||||||
return common.LeftPadBytes(input, size)
|
|
||||||
}
|
|
||||||
return common.RightPadBytes(input, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsondata = `
|
const jsondata = `
|
||||||
[
|
[
|
||||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
{ "type" : "function", "name" : "balance", "constant" : true },
|
||||||
|
|
@ -191,7 +172,7 @@ func TestMethodSignature(t *testing.T) {
|
||||||
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
|
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
|
||||||
}
|
}
|
||||||
|
|
||||||
uintt, _ := NewType("uint")
|
uintt, _ := NewType("uint256")
|
||||||
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
|
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
|
||||||
exp = "foo(uint256)"
|
exp = "foo(uint256)"
|
||||||
if m.Sig() != exp {
|
if m.Sig() != exp {
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ type SimulatedBackend struct {
|
||||||
// for testing purposes.
|
// for testing purposes.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
||||||
database, _ := ethdb.NewMemDatabase()
|
database, _ := ethdb.NewMemDatabase()
|
||||||
genesis := core.Genesis{Config: params.AllProtocolChanges, Alloc: alloc}
|
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
|
||||||
genesis.MustCommit(database)
|
genesis.MustCommit(database)
|
||||||
blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{})
|
||||||
backend := &SimulatedBackend{database: database, blockchain: blockchain, config: genesis.Config}
|
backend := &SimulatedBackend{database: database, blockchain: blockchain, config: genesis.Config}
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,23 @@ 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.SliceSize), val.Type())
|
return typeErr(formatSliceString(t.Kind, t.Size), val.Type())
|
||||||
}
|
|
||||||
if t.IsArray && val.Len() != t.SliceSize {
|
|
||||||
return typeErr(formatSliceString(t.Elem.Kind, t.SliceSize), formatSliceString(val.Type().Elem().Kind(), val.Len()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Elem.IsSlice {
|
if t.T == ArrayTy && val.Len() != t.Size {
|
||||||
|
return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Elem.T == SliceTy {
|
||||||
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.IsArray {
|
} else if t.Elem.T == ArrayTy {
|
||||||
return sliceTypeCheck(*t.Elem, val.Index(0))
|
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.Kind {
|
||||||
return typeErr(formatSliceString(t.Elem.Kind, t.SliceSize), val.Type())
|
return typeErr(formatSliceString(t.Elem.Kind, t.Size), val.Type())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -62,20 +63,19 @@ func sliceTypeCheck(t Type, val reflect.Value) error {
|
||||||
// typeCheck checks that the given reflection value can be assigned to the reflection
|
// typeCheck checks that the given reflection value can be assigned to the reflection
|
||||||
// type in t.
|
// type in t.
|
||||||
func typeCheck(t Type, value reflect.Value) error {
|
func typeCheck(t Type, value reflect.Value) error {
|
||||||
if t.IsSlice || t.IsArray {
|
if t.T == SliceTy || t.T == ArrayTy {
|
||||||
return sliceTypeCheck(t, value)
|
return sliceTypeCheck(t, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.Kind != value.Kind() {
|
||||||
return typeErr(t.Kind, value.Kind())
|
return typeErr(t.Kind, value.Kind())
|
||||||
}
|
} else if t.T == FixedBytesTy && t.Size != value.Len() {
|
||||||
|
return typeErr(t.Type, value.Type())
|
||||||
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// varErr returns a formatted error.
|
|
||||||
func varErr(expected, got reflect.Kind) error {
|
|
||||||
return typeErr(expected, got)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeErr returns a formatted type casting error.
|
// typeErr returns a formatted type casting error.
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -44,3 +45,93 @@ func (e Event) Id() common.Hash {
|
||||||
}
|
}
|
||||||
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unpacks an event return tuple into a struct of corresponding go types
|
||||||
|
//
|
||||||
|
// Unpacking can be done into a struct or a slice/array.
|
||||||
|
func (e Event) tupleUnpack(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is a pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
value = valueOf.Elem()
|
||||||
|
typ = value.Type()
|
||||||
|
)
|
||||||
|
|
||||||
|
if value.Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(e.Inputs); i++ {
|
||||||
|
input := e.Inputs[i]
|
||||||
|
if input.Indexed {
|
||||||
|
// can't read, continue
|
||||||
|
continue
|
||||||
|
} else if input.Type.T == ArrayTy {
|
||||||
|
// need to move this up because they read sequentially
|
||||||
|
j += input.Type.Size
|
||||||
|
}
|
||||||
|
marshalledValue, err := toGoType((i+j)*32, input.Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reflectValue := reflect.ValueOf(marshalledValue)
|
||||||
|
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
for j := 0; j < typ.NumField(); j++ {
|
||||||
|
field := typ.Field(j)
|
||||||
|
// TODO read tags: `abi:"fieldName"`
|
||||||
|
if field.Name == strings.ToUpper(e.Inputs[i].Name[:1])+e.Inputs[i].Name[1:] {
|
||||||
|
if err := set(value.Field(j), reflectValue, e.Inputs[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if value.Len() < i {
|
||||||
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(e.Inputs), value.Len())
|
||||||
|
}
|
||||||
|
v := value.Index(i)
|
||||||
|
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
|
||||||
|
}
|
||||||
|
reflectValue := reflect.ValueOf(marshalledValue)
|
||||||
|
if err := set(v.Elem(), reflectValue, e.Inputs[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) isTupleReturn() bool { return len(e.Inputs) > 1 }
|
||||||
|
|
||||||
|
func (e Event) singleUnpack(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is a pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Inputs[0].Indexed {
|
||||||
|
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
||||||
|
}
|
||||||
|
|
||||||
|
value := valueOf.Elem()
|
||||||
|
|
||||||
|
marshalledValue, err := toGoType(0, e.Inputs[0].Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := set(value, reflect.ValueOf(marshalledValue), e.Inputs[0]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func TestEventId(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
definition: `[
|
definition: `[
|
||||||
{ "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint" }] },
|
{ "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
{ "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
{ "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
||||||
]`,
|
]`,
|
||||||
expectations: map[string]common.Hash{
|
expectations: map[string]common.Hash{
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,85 @@ func (method Method) pack(args ...interface{}) ([]byte, error) {
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unpacks a method return tuple into a struct of corresponding go types
|
||||||
|
//
|
||||||
|
// Unpacking can be done into a struct or a slice/array.
|
||||||
|
func (method Method) tupleUnpack(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is a pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
value = valueOf.Elem()
|
||||||
|
typ = value.Type()
|
||||||
|
)
|
||||||
|
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(method.Outputs); i++ {
|
||||||
|
toUnpack := method.Outputs[i]
|
||||||
|
if toUnpack.Type.T == ArrayTy {
|
||||||
|
// need to move this up because they read sequentially
|
||||||
|
j += toUnpack.Type.Size
|
||||||
|
}
|
||||||
|
marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reflectValue := reflect.ValueOf(marshalledValue)
|
||||||
|
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
for j := 0; j < typ.NumField(); j++ {
|
||||||
|
field := typ.Field(j)
|
||||||
|
// TODO read tags: `abi:"fieldName"`
|
||||||
|
if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
|
||||||
|
if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if value.Len() < i {
|
||||||
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(method.Outputs), value.Len())
|
||||||
|
}
|
||||||
|
v := value.Index(i)
|
||||||
|
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
|
||||||
|
}
|
||||||
|
reflectValue := reflect.ValueOf(marshalledValue)
|
||||||
|
if err := set(v.Elem(), reflectValue, method.Outputs[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 }
|
||||||
|
|
||||||
|
func (method Method) singleUnpack(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is a pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := valueOf.Elem()
|
||||||
|
|
||||||
|
marshalledValue, err := toGoType(0, method.Outputs[0].Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Sig returns the methods string signature according to the ABI spec.
|
// Sig returns the methods string signature according to the ABI spec.
|
||||||
//
|
//
|
||||||
// Example
|
// Example
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
big_t = reflect.TypeOf(big.Int{})
|
big_t = reflect.TypeOf(&big.Int{})
|
||||||
ubig_t = reflect.TypeOf(big.Int{})
|
derefbig_t = reflect.TypeOf(big.Int{})
|
||||||
byte_t = reflect.TypeOf(byte(0))
|
|
||||||
byte_ts = reflect.TypeOf([]byte(nil))
|
|
||||||
uint_t = reflect.TypeOf(uint(0))
|
|
||||||
uint8_t = reflect.TypeOf(uint8(0))
|
uint8_t = reflect.TypeOf(uint8(0))
|
||||||
uint16_t = reflect.TypeOf(uint16(0))
|
uint16_t = reflect.TypeOf(uint16(0))
|
||||||
uint32_t = reflect.TypeOf(uint32(0))
|
uint32_t = reflect.TypeOf(uint32(0))
|
||||||
|
|
@ -39,22 +36,12 @@ var (
|
||||||
int16_t = reflect.TypeOf(int16(0))
|
int16_t = reflect.TypeOf(int16(0))
|
||||||
int32_t = reflect.TypeOf(int32(0))
|
int32_t = reflect.TypeOf(int32(0))
|
||||||
int64_t = reflect.TypeOf(int64(0))
|
int64_t = reflect.TypeOf(int64(0))
|
||||||
hash_t = reflect.TypeOf(common.Hash{})
|
|
||||||
address_t = reflect.TypeOf(common.Address{})
|
address_t = reflect.TypeOf(common.Address{})
|
||||||
|
|
||||||
uint_ts = reflect.TypeOf([]uint(nil))
|
|
||||||
uint8_ts = reflect.TypeOf([]uint8(nil))
|
|
||||||
uint16_ts = reflect.TypeOf([]uint16(nil))
|
|
||||||
uint32_ts = reflect.TypeOf([]uint32(nil))
|
|
||||||
uint64_ts = reflect.TypeOf([]uint64(nil))
|
|
||||||
ubig_ts = reflect.TypeOf([]*big.Int(nil))
|
|
||||||
|
|
||||||
int_ts = reflect.TypeOf([]int(nil))
|
int_ts = reflect.TypeOf([]int(nil))
|
||||||
int8_ts = reflect.TypeOf([]int8(nil))
|
int8_ts = reflect.TypeOf([]int8(nil))
|
||||||
int16_ts = reflect.TypeOf([]int16(nil))
|
int16_ts = reflect.TypeOf([]int16(nil))
|
||||||
int32_ts = reflect.TypeOf([]int32(nil))
|
int32_ts = reflect.TypeOf([]int32(nil))
|
||||||
int64_ts = reflect.TypeOf([]int64(nil))
|
int64_ts = reflect.TypeOf([]int64(nil))
|
||||||
big_ts = reflect.TypeOf([]*big.Int(nil))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// U256 converts a big Int into a 256bit EVM number.
|
// U256 converts a big Int into a 256bit EVM number.
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
||||||
}
|
default:
|
||||||
panic("abi: fatal error")
|
panic("abi: fatal error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
|
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
|
||||||
|
|
@ -74,6 +75,8 @@ func packNum(value reflect.Value) []byte {
|
||||||
return U256(big.NewInt(value.Int()))
|
return U256(big.NewInt(value.Int()))
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
return U256(value.Interface().(*big.Int))
|
return U256(value.Interface().(*big.Int))
|
||||||
|
default:
|
||||||
|
panic("abi: fatal error")
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -322,12 +322,12 @@ func TestPack(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
typ, err := NewType(test.typ)
|
typ, err := NewType(test.typ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected parse error:", err)
|
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := typ.pack(reflect.ValueOf(test.input))
|
output, err := typ.pack(reflect.ValueOf(test.input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unexpected pack error:", err)
|
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(output, test.output) {
|
if !bytes.Equal(output, test.output) {
|
||||||
|
|
@ -435,7 +435,4 @@ func TestPackNumber(t *testing.T) {
|
||||||
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if packed := packNum(reflect.ValueOf("string")); packed != nil {
|
|
||||||
t.Errorf("expected 'string' to pack to nil. got %x instead", packed)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,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() != big_t {
|
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t {
|
||||||
return indirect(v.Elem())
|
return indirect(v.Elem())
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
|
@ -73,15 +73,9 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
||||||
func set(dst, src reflect.Value, output Argument) error {
|
func set(dst, src reflect.Value, output Argument) error {
|
||||||
dstType := dst.Type()
|
dstType := dst.Type()
|
||||||
srcType := src.Type()
|
srcType := src.Type()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case dstType.AssignableTo(src.Type()):
|
case dstType.AssignableTo(srcType):
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice:
|
|
||||||
if dst.Len() < output.Type.SliceSize {
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.SliceSize, dst.Len())
|
|
||||||
}
|
|
||||||
reflect.Copy(dst, src)
|
|
||||||
case dstType.Kind() == reflect.Interface:
|
case dstType.Kind() == reflect.Interface:
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
case dstType.Kind() == reflect.Ptr:
|
case dstType.Kind() == reflect.Ptr:
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -29,6 +30,7 @@ const (
|
||||||
BoolTy
|
BoolTy
|
||||||
StringTy
|
StringTy
|
||||||
SliceTy
|
SliceTy
|
||||||
|
ArrayTy
|
||||||
AddressTy
|
AddressTy
|
||||||
FixedBytesTy
|
FixedBytesTy
|
||||||
BytesTy
|
BytesTy
|
||||||
|
|
@ -39,9 +41,6 @@ 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 {
|
||||||
IsSlice, IsArray bool
|
|
||||||
SliceSize int
|
|
||||||
|
|
||||||
Elem *Type
|
Elem *Type
|
||||||
|
|
||||||
Kind reflect.Kind
|
Kind reflect.Kind
|
||||||
|
|
@ -53,54 +52,57 @@ type Type struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// fullTypeRegex parses the abi types
|
|
||||||
//
|
|
||||||
// Types can be in the format of:
|
|
||||||
//
|
|
||||||
// Input = Type [ "[" [ Number ] "]" ] Name .
|
|
||||||
// Type = [ "u" ] "int" [ Number ] [ x ] [ Number ].
|
|
||||||
//
|
|
||||||
// Examples:
|
|
||||||
//
|
|
||||||
// string int uint fixed
|
|
||||||
// string32 int8 uint8 uint[]
|
|
||||||
// address int256 uint256 fixed128x128[2]
|
|
||||||
fullTypeRegex = regexp.MustCompile(`([a-zA-Z0-9]+)(\[([0-9]*)\])?`)
|
|
||||||
// typeRegex parses the abi sub types
|
// typeRegex parses the abi sub types
|
||||||
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewType creates a new reflection type of abi type given in t.
|
// NewType creates a new reflection type of abi type given in t.
|
||||||
func NewType(t string) (typ Type, err error) {
|
func NewType(t string) (typ Type, err error) {
|
||||||
res := fullTypeRegex.FindAllStringSubmatch(t, -1)[0]
|
// check that array brackets are equal if they exist
|
||||||
// check if type is slice and parse type.
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
switch {
|
return Type{}, fmt.Errorf("invalid arg type in abi")
|
||||||
case res[3] != "":
|
|
||||||
// err is ignored. Already checked for number through the regexp
|
|
||||||
typ.SliceSize, _ = strconv.Atoi(res[3])
|
|
||||||
typ.IsArray = true
|
|
||||||
case res[2] != "":
|
|
||||||
typ.IsSlice, typ.SliceSize = true, -1
|
|
||||||
case res[0] == "":
|
|
||||||
return Type{}, fmt.Errorf("abi: type parse error: %s", t)
|
|
||||||
}
|
}
|
||||||
if typ.IsArray || typ.IsSlice {
|
|
||||||
sliceType, err := NewType(res[1])
|
typ.stringKind = t
|
||||||
|
|
||||||
|
// if there are brackets, get ready to go into slice/array mode and
|
||||||
|
// recursively create the type
|
||||||
|
if strings.Count(t, "[") != 0 {
|
||||||
|
i := strings.LastIndex(t, "[")
|
||||||
|
// recursively embed the type
|
||||||
|
embeddedType, err := NewType(t[:i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Type{}, err
|
return Type{}, err
|
||||||
}
|
}
|
||||||
typ.Elem = &sliceType
|
// grab the last cell and create a type from there
|
||||||
typ.stringKind = sliceType.stringKind + t[len(res[1]):]
|
sliced := t[i:]
|
||||||
// Although we know that this is an array, we cannot return
|
// grab the slice size with regexp
|
||||||
// as we don't know the type of the element, however, if it
|
re := regexp.MustCompile("[0-9]+")
|
||||||
// is still an array, then don't determine the type.
|
intz := re.FindAllString(sliced, -1)
|
||||||
if typ.Elem.IsArray || typ.Elem.IsSlice {
|
|
||||||
return typ, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if len(intz) == 0 {
|
||||||
|
// is a slice
|
||||||
|
typ.T = SliceTy
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Elem = &embeddedType
|
||||||
|
typ.Type = reflect.SliceOf(embeddedType.Type)
|
||||||
|
} else if len(intz) == 1 {
|
||||||
|
// is a array
|
||||||
|
typ.T = ArrayTy
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Elem = &embeddedType
|
||||||
|
typ.Size, err = strconv.Atoi(intz[0])
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
|
}
|
||||||
|
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
||||||
|
} else {
|
||||||
|
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||||
|
}
|
||||||
|
return typ, err
|
||||||
|
} else {
|
||||||
// parse the type and size of the abi-type.
|
// parse the type and size of the abi-type.
|
||||||
parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0]
|
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||||
// varSize is the size of the variable
|
// varSize is the size of the variable
|
||||||
var varSize int
|
var varSize int
|
||||||
if len(parsedType[3]) > 0 {
|
if len(parsedType[3]) > 0 {
|
||||||
|
|
@ -109,20 +111,15 @@ func NewType(t string) (typ Type, err error) {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||||
|
// this should fail because it means that there's something wrong with
|
||||||
|
// the abi type (the compiler should always format it to the size...always)
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// varType is the parsed abi type
|
// varType is the parsed abi type
|
||||||
varType := parsedType[1]
|
varType := parsedType[1]
|
||||||
// substitute canonical integer
|
|
||||||
if varSize == 0 && (varType == "int" || varType == "uint") {
|
|
||||||
varSize = 256
|
|
||||||
t += "256"
|
|
||||||
}
|
|
||||||
|
|
||||||
// only set stringKind if not array or slice, as for those,
|
|
||||||
// the correct string type has been set
|
|
||||||
if !(typ.IsArray || typ.IsSlice) {
|
|
||||||
typ.stringKind = t
|
|
||||||
}
|
|
||||||
|
|
||||||
switch varType {
|
switch varType {
|
||||||
case "int":
|
case "int":
|
||||||
|
|
@ -136,6 +133,7 @@ func NewType(t string) (typ Type, err error) {
|
||||||
case "bool":
|
case "bool":
|
||||||
typ.Kind = reflect.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.Kind = reflect.Array
|
||||||
typ.Type = address_t
|
typ.Type = address_t
|
||||||
|
|
@ -143,29 +141,28 @@ func NewType(t string) (typ Type, err error) {
|
||||||
typ.T = AddressTy
|
typ.T = AddressTy
|
||||||
case "string":
|
case "string":
|
||||||
typ.Kind = reflect.String
|
typ.Kind = reflect.String
|
||||||
typ.Size = -1
|
typ.Type = reflect.TypeOf("")
|
||||||
typ.T = StringTy
|
typ.T = StringTy
|
||||||
case "bytes":
|
case "bytes":
|
||||||
sliceType, _ := NewType("uint8")
|
|
||||||
typ.Elem = &sliceType
|
|
||||||
if varSize == 0 {
|
if varSize == 0 {
|
||||||
typ.IsSlice = true
|
|
||||||
typ.T = BytesTy
|
typ.T = BytesTy
|
||||||
typ.SliceSize = -1
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||||
} else {
|
} else {
|
||||||
typ.IsArray = true
|
|
||||||
typ.T = FixedBytesTy
|
typ.T = FixedBytesTy
|
||||||
typ.SliceSize = varSize
|
typ.Kind = reflect.Array
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||||
}
|
}
|
||||||
case "function":
|
case "function":
|
||||||
sliceType, _ := NewType("uint8")
|
typ.Kind = reflect.Array
|
||||||
typ.Elem = &sliceType
|
|
||||||
typ.IsArray = true
|
|
||||||
typ.T = FunctionTy
|
typ.T = FunctionTy
|
||||||
typ.SliceSize = 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)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +180,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.IsSlice || t.IsArray) && t.T != BytesTy && t.T != FixedBytesTy && t.T != FunctionTy {
|
if t.T == SliceTy || t.T == ArrayTy {
|
||||||
var packed []byte
|
var packed []byte
|
||||||
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
|
@ -193,18 +190,17 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||||
}
|
}
|
||||||
packed = append(packed, val...)
|
packed = append(packed, val...)
|
||||||
}
|
}
|
||||||
if t.IsSlice {
|
if t.T == SliceTy {
|
||||||
return packBytesSlice(packed, v.Len()), nil
|
return packBytesSlice(packed, v.Len()), nil
|
||||||
} else if t.IsArray {
|
} else if t.T == ArrayTy {
|
||||||
return packed, nil
|
return packed, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return packElement(t, v), nil
|
return packElement(t, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireLengthPrefix returns whether the type requires any sort of length
|
// requireLengthPrefix returns whether the type requires any sort of length
|
||||||
// prefixing.
|
// prefixing.
|
||||||
func (t Type) requiresLengthPrefix() bool {
|
func (t Type) requiresLengthPrefix() bool {
|
||||||
return t.T != FixedBytesTy && (t.T == StringTy || t.T == BytesTy || t.IsSlice)
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,51 +35,58 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
blob string
|
blob string
|
||||||
kind Type
|
kind Type
|
||||||
}{
|
}{
|
||||||
{"bool", Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}},
|
{"bool", Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
|
||||||
{"bool[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Bool, T: BoolTy, Elem: &Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}},
|
{"bool[]", 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[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Bool, T: BoolTy, Elem: &Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}},
|
{"bool[2]", 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][]", 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[][]", 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[][2]", 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][2]", 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]", 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][2]", 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[][][]", 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[][2][]", 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][]"}},
|
||||||
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
||||||
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
|
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
|
||||||
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
|
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
|
||||||
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
|
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
|
||||||
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
||||||
{"int8[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
{"int8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
||||||
{"int8[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
{"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
||||||
{"int16[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
{"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
||||||
{"int16[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
{"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
||||||
{"int32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
{"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
||||||
{"int32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
{"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
||||||
{"int64[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
{"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
||||||
{"int64[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
{"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
||||||
{"int256[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
{"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
||||||
{"int256[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
{"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
||||||
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||||
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
|
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||||
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
|
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||||
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
|
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||||
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
||||||
{"uint8[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
{"uint8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
||||||
{"uint8[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
{"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
||||||
{"uint16[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
{"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
||||||
{"uint16[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
{"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
||||||
{"uint32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
{"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
||||||
{"uint32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
{"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
||||||
{"uint64[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
{"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
||||||
{"uint64[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
{"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
||||||
{"uint256[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
{"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
||||||
{"uint256[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
{"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
||||||
{"bytes32", Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}},
|
{"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
||||||
{"bytes[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
{"bytes[]", 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[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
{"bytes[2]", 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]"}},
|
||||||
{"bytes32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
{"bytes32[]", 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[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
{"bytes32[2]", 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]"}},
|
||||||
{"string", Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}},
|
{"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
||||||
{"string[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[]"}},
|
{"string[]", 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[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[2]"}},
|
{"string[2]", 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]"}},
|
||||||
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
|
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
|
||||||
{"address[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Array, Type: address_t, T: AddressTy, Size: 20, Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
{"address[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
||||||
{"address[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Array, Type: address_t, T: AddressTy, Size: 20, Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
{"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
||||||
|
|
||||||
// TODO when fixed types are implemented properly
|
// TODO when fixed types are implemented properly
|
||||||
// {"fixed", Type{}},
|
// {"fixed", Type{}},
|
||||||
// {"fixed128x128", Type{}},
|
// {"fixed128x128", Type{}},
|
||||||
|
|
@ -87,13 +95,14 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
// {"fixed128x128[]", Type{}},
|
// {"fixed128x128[]", Type{}},
|
||||||
// {"fixed128x128[2]", Type{}},
|
// {"fixed128x128[2]", Type{}},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
|
||||||
|
for _, tt := range tests {
|
||||||
typ, err := NewType(tt.blob)
|
typ, err := NewType(tt.blob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("type %d: failed to parse type string: %v", i, err)
|
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(typ, tt.kind) {
|
if !reflect.DeepEqual(typ, tt.kind) {
|
||||||
t.Errorf("type %d: parsed type mismatch:\n have %+v\n want %+v", i, typeWithoutStringer(typ), typeWithoutStringer(tt.kind))
|
t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,15 +113,90 @@ func TestTypeCheck(t *testing.T) {
|
||||||
input interface{}
|
input interface{}
|
||||||
err string
|
err string
|
||||||
}{
|
}{
|
||||||
{"uint", big.NewInt(1), ""},
|
{"uint", big.NewInt(1), "unsupported arg type: uint"},
|
||||||
{"int", big.NewInt(1), ""},
|
{"int", big.NewInt(1), "unsupported arg type: int"},
|
||||||
{"uint30", big.NewInt(1), ""},
|
{"uint256", big.NewInt(1), ""},
|
||||||
|
{"uint256[][3][]", [][3][]*big.Int{{{}}}, ""},
|
||||||
|
{"uint256[][][3]", [3][][]*big.Int{{{}}}, ""},
|
||||||
|
{"uint256[3][][]", [][][3]*big.Int{{{}}}, ""},
|
||||||
|
{"uint256[3][3][3]", [3][3][3]*big.Int{{{}}}, ""},
|
||||||
|
{"uint8[][]", [][]uint8{}, ""},
|
||||||
|
{"int256", big.NewInt(1), ""},
|
||||||
|
{"uint8", uint8(1), ""},
|
||||||
|
{"uint16", uint16(1), ""},
|
||||||
|
{"uint32", uint32(1), ""},
|
||||||
|
{"uint64", uint64(1), ""},
|
||||||
|
{"int8", int8(1), ""},
|
||||||
|
{"int16", int16(1), ""},
|
||||||
|
{"int32", int32(1), ""},
|
||||||
|
{"int64", int64(1), ""},
|
||||||
|
{"uint24", big.NewInt(1), ""},
|
||||||
|
{"uint40", big.NewInt(1), ""},
|
||||||
|
{"uint48", big.NewInt(1), ""},
|
||||||
|
{"uint56", big.NewInt(1), ""},
|
||||||
|
{"uint72", big.NewInt(1), ""},
|
||||||
|
{"uint80", big.NewInt(1), ""},
|
||||||
|
{"uint88", big.NewInt(1), ""},
|
||||||
|
{"uint96", big.NewInt(1), ""},
|
||||||
|
{"uint104", big.NewInt(1), ""},
|
||||||
|
{"uint112", big.NewInt(1), ""},
|
||||||
|
{"uint120", big.NewInt(1), ""},
|
||||||
|
{"uint128", big.NewInt(1), ""},
|
||||||
|
{"uint136", big.NewInt(1), ""},
|
||||||
|
{"uint144", big.NewInt(1), ""},
|
||||||
|
{"uint152", big.NewInt(1), ""},
|
||||||
|
{"uint160", big.NewInt(1), ""},
|
||||||
|
{"uint168", big.NewInt(1), ""},
|
||||||
|
{"uint176", big.NewInt(1), ""},
|
||||||
|
{"uint184", big.NewInt(1), ""},
|
||||||
|
{"uint192", big.NewInt(1), ""},
|
||||||
|
{"uint200", big.NewInt(1), ""},
|
||||||
|
{"uint208", big.NewInt(1), ""},
|
||||||
|
{"uint216", big.NewInt(1), ""},
|
||||||
|
{"uint224", big.NewInt(1), ""},
|
||||||
|
{"uint232", big.NewInt(1), ""},
|
||||||
|
{"uint240", big.NewInt(1), ""},
|
||||||
|
{"uint248", big.NewInt(1), ""},
|
||||||
|
{"int24", big.NewInt(1), ""},
|
||||||
|
{"int40", big.NewInt(1), ""},
|
||||||
|
{"int48", big.NewInt(1), ""},
|
||||||
|
{"int56", big.NewInt(1), ""},
|
||||||
|
{"int72", big.NewInt(1), ""},
|
||||||
|
{"int80", big.NewInt(1), ""},
|
||||||
|
{"int88", big.NewInt(1), ""},
|
||||||
|
{"int96", big.NewInt(1), ""},
|
||||||
|
{"int104", big.NewInt(1), ""},
|
||||||
|
{"int112", big.NewInt(1), ""},
|
||||||
|
{"int120", big.NewInt(1), ""},
|
||||||
|
{"int128", big.NewInt(1), ""},
|
||||||
|
{"int136", big.NewInt(1), ""},
|
||||||
|
{"int144", big.NewInt(1), ""},
|
||||||
|
{"int152", big.NewInt(1), ""},
|
||||||
|
{"int160", big.NewInt(1), ""},
|
||||||
|
{"int168", big.NewInt(1), ""},
|
||||||
|
{"int176", big.NewInt(1), ""},
|
||||||
|
{"int184", big.NewInt(1), ""},
|
||||||
|
{"int192", big.NewInt(1), ""},
|
||||||
|
{"int200", big.NewInt(1), ""},
|
||||||
|
{"int208", big.NewInt(1), ""},
|
||||||
|
{"int216", big.NewInt(1), ""},
|
||||||
|
{"int224", big.NewInt(1), ""},
|
||||||
|
{"int232", big.NewInt(1), ""},
|
||||||
|
{"int240", big.NewInt(1), ""},
|
||||||
|
{"int248", big.NewInt(1), ""},
|
||||||
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||||
|
{"uint8", uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
||||||
|
{"uint8", uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
||||||
|
{"uint8", uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
||||||
|
{"uint8", int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
||||||
|
{"uint8", int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
||||||
|
{"uint8", int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
||||||
|
{"uint8", int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
||||||
{"uint16", uint16(1), ""},
|
{"uint16", uint16(1), ""},
|
||||||
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||||
{"uint16[]", []uint16{1, 2, 3}, ""},
|
{"uint16[]", []uint16{1, 2, 3}, ""},
|
||||||
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
||||||
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type []uint16 as argument"},
|
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
|
||||||
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
||||||
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
||||||
|
|
@ -122,20 +206,61 @@ func TestTypeCheck(t *testing.T) {
|
||||||
{"address[1]", [1]common.Address{{1}}, ""},
|
{"address[1]", [1]common.Address{{1}}, ""},
|
||||||
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||||
{"bytes32", [32]byte{}, ""},
|
{"bytes32", [32]byte{}, ""},
|
||||||
|
{"bytes31", [31]byte{}, ""},
|
||||||
|
{"bytes30", [30]byte{}, ""},
|
||||||
|
{"bytes29", [29]byte{}, ""},
|
||||||
|
{"bytes28", [28]byte{}, ""},
|
||||||
|
{"bytes27", [27]byte{}, ""},
|
||||||
|
{"bytes26", [26]byte{}, ""},
|
||||||
|
{"bytes25", [25]byte{}, ""},
|
||||||
|
{"bytes24", [24]byte{}, ""},
|
||||||
|
{"bytes23", [23]byte{}, ""},
|
||||||
|
{"bytes22", [22]byte{}, ""},
|
||||||
|
{"bytes21", [21]byte{}, ""},
|
||||||
|
{"bytes20", [20]byte{}, ""},
|
||||||
|
{"bytes19", [19]byte{}, ""},
|
||||||
|
{"bytes18", [18]byte{}, ""},
|
||||||
|
{"bytes17", [17]byte{}, ""},
|
||||||
|
{"bytes16", [16]byte{}, ""},
|
||||||
|
{"bytes15", [15]byte{}, ""},
|
||||||
|
{"bytes14", [14]byte{}, ""},
|
||||||
|
{"bytes13", [13]byte{}, ""},
|
||||||
|
{"bytes12", [12]byte{}, ""},
|
||||||
|
{"bytes11", [11]byte{}, ""},
|
||||||
|
{"bytes10", [10]byte{}, ""},
|
||||||
|
{"bytes9", [9]byte{}, ""},
|
||||||
|
{"bytes8", [8]byte{}, ""},
|
||||||
|
{"bytes7", [7]byte{}, ""},
|
||||||
|
{"bytes6", [6]byte{}, ""},
|
||||||
|
{"bytes5", [5]byte{}, ""},
|
||||||
|
{"bytes4", [4]byte{}, ""},
|
||||||
|
{"bytes3", [3]byte{}, ""},
|
||||||
|
{"bytes2", [2]byte{}, ""},
|
||||||
|
{"bytes1", [1]byte{}, ""},
|
||||||
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||||
{"bytes32", common.Hash{1}, ""},
|
{"bytes32", common.Hash{1}, ""},
|
||||||
{"bytes31", [31]byte{}, ""},
|
{"bytes31", common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
|
||||||
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||||
{"bytes", []byte{0, 1}, ""},
|
{"bytes", []byte{0, 1}, ""},
|
||||||
{"bytes", [2]byte{0, 1}, ""},
|
{"bytes", [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
|
||||||
{"bytes", common.Hash{1}, ""},
|
{"bytes", common.Hash{1}, "abi: cannot use array as type slice as argument"},
|
||||||
{"string", "hello world", ""},
|
{"string", "hello world", ""},
|
||||||
|
{"string", string(""), ""},
|
||||||
|
{"string", []byte{}, "abi: cannot use slice as type string as argument"},
|
||||||
{"bytes32[]", [][32]byte{{}}, ""},
|
{"bytes32[]", [][32]byte{{}}, ""},
|
||||||
{"function", [24]byte{}, ""},
|
{"function", [24]byte{}, ""},
|
||||||
|
{"bytes20", common.Address{}, ""},
|
||||||
|
{"address", [20]byte{}, ""},
|
||||||
|
{"address", common.Address{}, ""},
|
||||||
} {
|
} {
|
||||||
typ, err := NewType(test.typ)
|
typ, err := NewType(test.typ)
|
||||||
if err != nil {
|
if err != nil && len(test.err) == 0 {
|
||||||
t.Fatal("unexpected parse error:", err)
|
t.Fatal("unexpected parse error:", err)
|
||||||
|
} else if err != nil && len(test.err) != 0 {
|
||||||
|
if err.Error() != test.err {
|
||||||
|
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = typeCheck(typ, reflect.ValueOf(test.input))
|
err = typeCheck(typ, reflect.ValueOf(test.input))
|
||||||
|
|
|
||||||
|
|
@ -25,122 +25,20 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// toGoSliceType parses the input and casts it to the proper slice defined by the ABI
|
// unpacker is a utility interface that enables us to have
|
||||||
// argument in T.
|
// abstraction between events and methods and also to properly
|
||||||
func toGoSlice(i int, t Argument, output []byte) (interface{}, error) {
|
// "unpack" them; e.g. events use Inputs, methods use Outputs.
|
||||||
index := i * 32
|
type unpacker interface {
|
||||||
// The slice must, at very least be large enough for the index+32 which is exactly the size required
|
tupleUnpack(v interface{}, output []byte) error
|
||||||
// for the [offset in output, size of offset].
|
singleUnpack(v interface{}, output []byte) error
|
||||||
if index+32 > len(output) {
|
isTupleReturn() bool
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32)
|
|
||||||
}
|
|
||||||
elem := t.Type.Elem
|
|
||||||
|
|
||||||
// first we need to create a slice of the type
|
|
||||||
var refSlice reflect.Value
|
|
||||||
switch elem.T {
|
|
||||||
case IntTy, UintTy, BoolTy:
|
|
||||||
// create a new reference slice matching the element type
|
|
||||||
switch t.Type.Kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
refSlice = reflect.ValueOf([]bool(nil))
|
|
||||||
case reflect.Uint8:
|
|
||||||
refSlice = reflect.ValueOf([]uint8(nil))
|
|
||||||
case reflect.Uint16:
|
|
||||||
refSlice = reflect.ValueOf([]uint16(nil))
|
|
||||||
case reflect.Uint32:
|
|
||||||
refSlice = reflect.ValueOf([]uint32(nil))
|
|
||||||
case reflect.Uint64:
|
|
||||||
refSlice = reflect.ValueOf([]uint64(nil))
|
|
||||||
case reflect.Int8:
|
|
||||||
refSlice = reflect.ValueOf([]int8(nil))
|
|
||||||
case reflect.Int16:
|
|
||||||
refSlice = reflect.ValueOf([]int16(nil))
|
|
||||||
case reflect.Int32:
|
|
||||||
refSlice = reflect.ValueOf([]int32(nil))
|
|
||||||
case reflect.Int64:
|
|
||||||
refSlice = reflect.ValueOf([]int64(nil))
|
|
||||||
default:
|
|
||||||
refSlice = reflect.ValueOf([]*big.Int(nil))
|
|
||||||
}
|
|
||||||
case AddressTy: // address must be of slice Address
|
|
||||||
refSlice = reflect.ValueOf([]common.Address(nil))
|
|
||||||
case HashTy: // hash must be of slice hash
|
|
||||||
refSlice = reflect.ValueOf([]common.Hash(nil))
|
|
||||||
case FixedBytesTy:
|
|
||||||
refSlice = reflect.ValueOf([][]byte(nil))
|
|
||||||
default: // no other types are supported
|
|
||||||
return nil, fmt.Errorf("abi: unsupported slice type %v", elem.T)
|
|
||||||
}
|
|
||||||
|
|
||||||
var slice []byte
|
|
||||||
var size int
|
|
||||||
var offset int
|
|
||||||
if t.Type.IsSlice {
|
|
||||||
// get the offset which determines the start of this array ...
|
|
||||||
offset = int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
|
||||||
if offset+32 > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32)
|
|
||||||
}
|
|
||||||
|
|
||||||
slice = output[offset:]
|
|
||||||
// ... starting with the size of the array in elements ...
|
|
||||||
size = int(binary.BigEndian.Uint64(slice[24:32]))
|
|
||||||
slice = slice[32:]
|
|
||||||
// ... and make sure that we've at the very least the amount of bytes
|
|
||||||
// available in the buffer.
|
|
||||||
if size*32 > len(slice) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), offset+32+size*32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reslice to match the required size
|
|
||||||
slice = slice[:size*32]
|
|
||||||
} else if t.Type.IsArray {
|
|
||||||
//get the number of elements in the array
|
|
||||||
size = t.Type.SliceSize
|
|
||||||
|
|
||||||
//check to make sure array size matches up
|
|
||||||
if index+32*size > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), index+32*size)
|
|
||||||
}
|
|
||||||
//slice is there for a fixed amount of times
|
|
||||||
slice = output[index : index+size*32]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
var (
|
|
||||||
inter interface{} // interface type
|
|
||||||
returnOutput = slice[i*32 : i*32+32] // the return output
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
// set inter to the correct type (cast)
|
|
||||||
switch elem.T {
|
|
||||||
case IntTy, UintTy:
|
|
||||||
inter = readInteger(t.Type.Kind, returnOutput)
|
|
||||||
case BoolTy:
|
|
||||||
inter, err = readBool(returnOutput)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case AddressTy:
|
|
||||||
inter = common.BytesToAddress(returnOutput)
|
|
||||||
case HashTy:
|
|
||||||
inter = common.BytesToHash(returnOutput)
|
|
||||||
case FixedBytesTy:
|
|
||||||
inter = returnOutput
|
|
||||||
}
|
|
||||||
// append the item to our reflect slice
|
|
||||||
refSlice = reflect.Append(refSlice, reflect.ValueOf(inter))
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the interface
|
|
||||||
return refSlice.Interface(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reads the integer based on its kind
|
||||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Uint8:
|
case reflect.Uint8:
|
||||||
return uint8(b[len(b)-1])
|
return b[len(b)-1]
|
||||||
case reflect.Uint16:
|
case reflect.Uint16:
|
||||||
return binary.BigEndian.Uint16(b[len(b)-2:])
|
return binary.BigEndian.Uint16(b[len(b)-2:])
|
||||||
case reflect.Uint32:
|
case reflect.Uint32:
|
||||||
|
|
@ -160,13 +58,10 @@ func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reads a bool
|
||||||
func readBool(word []byte) (bool, error) {
|
func readBool(word []byte) (bool, error) {
|
||||||
if len(word) != 32 {
|
for _, b := range word[:31] {
|
||||||
return false, fmt.Errorf("abi: fatal error: incorrect word length")
|
if b != 0 {
|
||||||
}
|
|
||||||
|
|
||||||
for i, b := range word {
|
|
||||||
if b != 0 && i != 31 {
|
|
||||||
return false, errBadBool
|
return false, errBadBool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -178,58 +73,144 @@ func readBool(word []byte) (bool, error) {
|
||||||
default:
|
default:
|
||||||
return false, errBadBool
|
return false, errBadBool
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A function type is simply the address with the function selection signature at the end.
|
||||||
|
// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
||||||
|
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
|
if t.T != FunctionTy {
|
||||||
|
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array.")
|
||||||
|
}
|
||||||
|
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||||
|
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||||
|
} else {
|
||||||
|
copy(funcTy[:], word[0:24])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// through reflection, creates a fixed array to be read from
|
||||||
|
func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||||
|
if t.T != FixedBytesTy {
|
||||||
|
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array.")
|
||||||
|
}
|
||||||
|
// convert
|
||||||
|
array := reflect.New(t.Type).Elem()
|
||||||
|
|
||||||
|
reflect.Copy(array, reflect.ValueOf(word[0:t.Size]))
|
||||||
|
return array.Interface(), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// toGoType parses the input and casts it to the proper type defined by the ABI
|
// iteratively unpack elements
|
||||||
// argument in T.
|
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
|
||||||
func toGoType(i int, t Argument, output []byte) (interface{}, error) {
|
if start+32*size > len(output) {
|
||||||
// we need to treat slices differently
|
return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
|
||||||
if (t.Type.IsSlice || t.Type.IsArray) && t.Type.T != BytesTy && t.Type.T != StringTy && t.Type.T != FixedBytesTy && t.Type.T != FunctionTy {
|
|
||||||
return toGoSlice(i, t, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
index := i * 32
|
// this value will become our slice or our array, depending on the type
|
||||||
|
var refSlice reflect.Value
|
||||||
|
slice := output[start : start+size*32]
|
||||||
|
|
||||||
|
if t.T == SliceTy {
|
||||||
|
// declare our slice
|
||||||
|
refSlice = reflect.MakeSlice(t.Type, size, size)
|
||||||
|
} else if t.T == ArrayTy {
|
||||||
|
// declare our array
|
||||||
|
refSlice = reflect.New(t.Type).Elem()
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, j := start, 0; j*32 < len(slice); i, j = i+32, j+1 {
|
||||||
|
// this corrects the arrangement so that we get all the underlying array values
|
||||||
|
if t.Elem.T == ArrayTy && j != 0 {
|
||||||
|
i = start + t.Elem.Size*32*j
|
||||||
|
}
|
||||||
|
inter, err := toGoType(i, *t.Elem, output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// append the item to our reflect slice
|
||||||
|
refSlice.Index(j).Set(reflect.ValueOf(inter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the interface
|
||||||
|
return refSlice.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toGoType parses the output bytes and recursively assigns the value of these bytes
|
||||||
|
// into a go type with accordance with the ABI spec.
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the given index output and check whether we need to read
|
var (
|
||||||
// a different offset and length based on the type (i.e. string, bytes)
|
returnOutput []byte
|
||||||
var returnOutput []byte
|
begin, end int
|
||||||
switch t.Type.T {
|
err error
|
||||||
case StringTy, BytesTy: // variable arrays are written at the end of the return bytes
|
)
|
||||||
// parse offset from which we should start reading
|
|
||||||
offset := int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
|
||||||
if offset+32 > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32)
|
|
||||||
}
|
|
||||||
// parse the size up until we should be reading
|
|
||||||
size := int(binary.BigEndian.Uint64(output[offset+24 : offset+32]))
|
|
||||||
if offset+32+size > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the bytes for this return value
|
// if we require a length prefix, find the beginning word and size returned.
|
||||||
returnOutput = output[offset+32 : offset+32+size]
|
if t.requiresLengthPrefix() {
|
||||||
default:
|
begin, end, err = lengthPrefixPointsTo(index, output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
returnOutput = output[index : index+32]
|
returnOutput = output[index : index+32]
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert the bytes to whatever is specified by the ABI.
|
switch t.T {
|
||||||
switch t.Type.T {
|
case SliceTy:
|
||||||
|
return forEachUnpack(t, output, begin, end)
|
||||||
|
case ArrayTy:
|
||||||
|
return forEachUnpack(t, output, index, t.Size)
|
||||||
|
case StringTy: // variable arrays are written at the end of the return bytes
|
||||||
|
return string(output[begin : begin+end]), nil
|
||||||
case IntTy, UintTy:
|
case IntTy, UintTy:
|
||||||
return readInteger(t.Type.Kind, returnOutput), nil
|
return readInteger(t.Kind, returnOutput), nil
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
return readBool(returnOutput)
|
return readBool(returnOutput)
|
||||||
case AddressTy:
|
case AddressTy:
|
||||||
return common.BytesToAddress(returnOutput), nil
|
return common.BytesToAddress(returnOutput), nil
|
||||||
case HashTy:
|
case HashTy:
|
||||||
return common.BytesToHash(returnOutput), nil
|
return common.BytesToHash(returnOutput), nil
|
||||||
case BytesTy, FixedBytesTy, FunctionTy:
|
case BytesTy:
|
||||||
return returnOutput, nil
|
return output[begin : begin+end], nil
|
||||||
case StringTy:
|
case FixedBytesTy:
|
||||||
return string(returnOutput), nil
|
return readFixedBytes(t, returnOutput)
|
||||||
|
case FunctionTy:
|
||||||
|
return readFunctionType(t, returnOutput)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("abi: unknown type %v", t.T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type.
|
||||||
|
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
|
||||||
|
offset := int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
||||||
|
if offset+32 > len(output) {
|
||||||
|
return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32)
|
||||||
|
}
|
||||||
|
length = int(binary.BigEndian.Uint64(output[offset+24 : offset+32]))
|
||||||
|
if offset+32+length > len(output) {
|
||||||
|
return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+length)
|
||||||
|
}
|
||||||
|
start = offset + 32
|
||||||
|
|
||||||
|
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks for proper formatting of byte output
|
||||||
|
func bytesAreProper(output []byte) error {
|
||||||
|
if len(output) == 0 {
|
||||||
|
return fmt.Errorf("abi: unmarshalling empty output")
|
||||||
|
} else if len(output)%32 != 0 {
|
||||||
|
return fmt.Errorf("abi: improperly formatted output")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("abi: unknown type %v", t.Type.T)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -27,260 +28,258 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimpleMethodUnpack(t *testing.T) {
|
type unpackTest struct {
|
||||||
for i, test := range []struct {
|
def string // ABI definition JSON
|
||||||
def string // definition of the **output** ABI params
|
enc string // evm return data
|
||||||
marshalledOutput []byte // evm return data
|
want interface{} // the expected output
|
||||||
expectedOut interface{} // the expected output
|
|
||||||
outVar string // the output variable (e.g. uint32, *big.Int, etc)
|
|
||||||
err string // empty or error if expected
|
err string // empty or error if expected
|
||||||
}{
|
|
||||||
{
|
|
||||||
`[ { "type": "bool" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
bool(true),
|
|
||||||
"bool",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint32" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
uint32(1),
|
|
||||||
"uint32",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint32" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
nil,
|
|
||||||
"uint16",
|
|
||||||
"abi: cannot unmarshal uint32 in to uint16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint17" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
nil,
|
|
||||||
"uint16",
|
|
||||||
"abi: cannot unmarshal *big.Int in to uint16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint17" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
big.NewInt(1),
|
|
||||||
"*big.Int",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
`[ { "type": "int32" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
int32(1),
|
|
||||||
"int32",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "int32" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
nil,
|
|
||||||
"int16",
|
|
||||||
"abi: cannot unmarshal int32 in to int16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "int17" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
nil,
|
|
||||||
"int16",
|
|
||||||
"abi: cannot unmarshal *big.Int in to int16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "int17" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
||||||
big.NewInt(1),
|
|
||||||
"*big.Int",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
`[ { "type": "address" } ]`,
|
|
||||||
common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"),
|
|
||||||
common.Address{1},
|
|
||||||
"address",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "bytes32" } ]`,
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
"bytes",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "bytes32" } ]`,
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
"hash",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "bytes32" } ]`,
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
"interface",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "function" } ]`,
|
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
[24]byte{1},
|
|
||||||
"function",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
|
||||||
abi, err := JSON(strings.NewReader(abiDefinition))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%d failed. %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var outvar interface{}
|
|
||||||
switch test.outVar {
|
|
||||||
case "bool":
|
|
||||||
var v bool
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint8":
|
|
||||||
var v uint8
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint16":
|
|
||||||
var v uint16
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint32":
|
|
||||||
var v uint32
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint64":
|
|
||||||
var v uint64
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int8":
|
|
||||||
var v int8
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int16":
|
|
||||||
var v int16
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int32":
|
|
||||||
var v int32
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int64":
|
|
||||||
var v int64
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "*big.Int":
|
|
||||||
var v *big.Int
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "address":
|
|
||||||
var v common.Address
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "bytes":
|
|
||||||
var v []byte
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "hash":
|
|
||||||
var v common.Hash
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v.Bytes()[:]
|
|
||||||
case "function":
|
|
||||||
var v [24]byte
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "interface":
|
|
||||||
err = abi.Unpack(&outvar, "method", test.marshalledOutput)
|
|
||||||
default:
|
|
||||||
t.Errorf("unsupported type '%v' please add it to the switch statement in this test", test.outVar)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && len(test.err) == 0 {
|
|
||||||
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err == nil && len(test.err) != 0 {
|
|
||||||
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
|
||||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
if !reflect.DeepEqual(test.expectedOut, outvar) {
|
|
||||||
t.Errorf("%d failed. Output error: expected %v, got %v", i, test.expectedOut, outvar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackSetInterfaceSlice(t *testing.T) {
|
func (test unpackTest) checkError(err error) error {
|
||||||
var (
|
|
||||||
var1 = new(uint8)
|
|
||||||
var2 = new(uint8)
|
|
||||||
)
|
|
||||||
out := []interface{}{var1, var2}
|
|
||||||
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint8"}, {"type":"uint8"}]}]`))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
if len(test.err) == 0 {
|
||||||
|
return fmt.Errorf("expected no err but got: %v", err)
|
||||||
|
} else if err.Error() != test.err {
|
||||||
|
return fmt.Errorf("expected err: '%v' got err: %q", test.err, err)
|
||||||
}
|
}
|
||||||
marshalledReturn := append(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")...)
|
} else if len(test.err) > 0 {
|
||||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
return fmt.Errorf("expected err: %v but got none", test.err)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if *var1 != 1 {
|
|
||||||
t.Error("expected var1 to be 1, got", *var1)
|
|
||||||
}
|
|
||||||
if *var2 != 2 {
|
|
||||||
t.Error("expected var2 to be 2, got", *var2)
|
|
||||||
}
|
|
||||||
|
|
||||||
out = []interface{}{var1}
|
|
||||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
|
||||||
|
|
||||||
expErr := "abi: cannot marshal in to slices of unequal size (require: 2, got: 1)"
|
|
||||||
if err == nil || err.Error() != expErr {
|
|
||||||
t.Error("expected err:", expErr, "Got:", err)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackSetInterfaceArrayOutput(t *testing.T) {
|
var unpackTests = []unpackTest{
|
||||||
var (
|
{
|
||||||
var1 = new([1]uint32)
|
def: `[{ "type": "bool" }]`,
|
||||||
var2 = new([1]uint32)
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
)
|
want: true,
|
||||||
out := []interface{}{var1, var2}
|
},
|
||||||
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint32[1]"}, {"type":"uint32[1]"}]}]`))
|
{
|
||||||
if err != nil {
|
def: `[{"type": "uint32"}]`,
|
||||||
t.Fatal(err)
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
}
|
want: uint32(1),
|
||||||
marshalledReturn := append(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")...)
|
},
|
||||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
{
|
||||||
if err != nil {
|
def: `[{"type": "uint32"}]`,
|
||||||
t.Fatal(err)
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
}
|
want: uint16(0),
|
||||||
|
err: "abi: cannot unmarshal uint32 in to uint16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint17"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
want: uint16(0),
|
||||||
|
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"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
want: int16(0),
|
||||||
|
err: "abi: cannot unmarshal int32 in to int16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int17"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
want: int16(0),
|
||||||
|
err: "abi: cannot unmarshal *big.Int in to int16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int17"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
want: big.NewInt(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "address"}]`,
|
||||||
|
enc: "0000000000000000000000000100000000000000000000000000000000000000",
|
||||||
|
want: common.Address{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"}]`,
|
||||||
|
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
want: [32]byte{},
|
||||||
|
err: "abi: cannot unmarshal []uint8 in to [32]uint8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32"}]`,
|
||||||
|
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
want: []byte(nil),
|
||||||
|
err: "abi: cannot unmarshal [32]uint8 in to []uint8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes32"}]`,
|
||||||
|
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
want: common.HexToHash("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "function"}]`,
|
||||||
|
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
want: [24]byte{1},
|
||||||
|
},
|
||||||
|
// slices
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: []uint8{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000E0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: [][]uint8{{1, 2}, {1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][2]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: [2][2]uint8{{1, 2}, {1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][2]"}]`,
|
||||||
|
enc: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
want: [2][]uint8{{1}, {1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: [][2]uint8{{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": "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": "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)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if *var1 != [1]uint32{1} {
|
func TestUnpack(t *testing.T) {
|
||||||
t.Error("expected var1 to be [1], got", *var1)
|
for i, test := range unpackTests {
|
||||||
|
def := fmt.Sprintf(`[{ "name" : "method", "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.enc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid hex: %s" + test.enc)
|
||||||
|
}
|
||||||
|
outptr := reflect.New(reflect.TypeOf(test.want))
|
||||||
|
err = abi.Unpack(outptr.Interface(), "method", encb)
|
||||||
|
if err := test.checkError(err); err != nil {
|
||||||
|
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out := outptr.Elem().Interface()
|
||||||
|
if !reflect.DeepEqual(test.want, out) {
|
||||||
|
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
|
||||||
}
|
}
|
||||||
if *var2 != [1]uint32{2} {
|
|
||||||
t.Error("expected var2 to be [2], got", *var2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,101 +336,6 @@ func TestMultiReturnWithStruct(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithSlice(t *testing.T) {
|
|
||||||
const definition = `[
|
|
||||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// using buff to make the code readable
|
|
||||||
buff := new(bytes.Buffer)
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
|
||||||
stringOut := "hello"
|
|
||||||
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
|
||||||
|
|
||||||
var inter []interface{}
|
|
||||||
err = abi.Unpack(&inter, "multi", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(inter) != 2 {
|
|
||||||
t.Fatal("expected 2 results got", len(inter))
|
|
||||||
}
|
|
||||||
|
|
||||||
if num, ok := inter[0].(*big.Int); !ok || num.Cmp(big.NewInt(1)) != 0 {
|
|
||||||
t.Error("expected index 0 to be 1 got", num)
|
|
||||||
}
|
|
||||||
|
|
||||||
if str, ok := inter[1].(string); !ok || str != stringOut {
|
|
||||||
t.Error("expected index 1 to be", stringOut, "got", str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarshalArrays(t *testing.T) {
|
|
||||||
const definition = `[
|
|
||||||
{ "name" : "bytes32", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
|
||||||
{ "name" : "bytes10", "constant" : false, "outputs": [ { "type": "bytes10" } ] }
|
|
||||||
]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output := common.LeftPadBytes([]byte{1}, 32)
|
|
||||||
|
|
||||||
var bytes10 [10]byte
|
|
||||||
err = abi.Unpack(&bytes10, "bytes32", output)
|
|
||||||
if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
|
|
||||||
t.Error("expected error or bytes32 not be assignable to bytes10:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes32 [32]byte
|
|
||||||
err = abi.Unpack(&bytes32, "bytes32", output)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(bytes32[:], output) {
|
|
||||||
t.Error("expected bytes32[31] to be 1 got", bytes32[31])
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
B10 [10]byte
|
|
||||||
B32 [32]byte
|
|
||||||
)
|
|
||||||
|
|
||||||
var b10 B10
|
|
||||||
err = abi.Unpack(&b10, "bytes32", output)
|
|
||||||
if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
|
|
||||||
t.Error("expected error or bytes32 not be assignable to bytes10:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b32 B32
|
|
||||||
err = abi.Unpack(&b32, "bytes32", output)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(b32[:], output) {
|
|
||||||
t.Error("expected bytes32[31] to be 1 got", bytes32[31])
|
|
||||||
}
|
|
||||||
|
|
||||||
output[10] = 1
|
|
||||||
var shortAssignLong [32]byte
|
|
||||||
err = abi.Unpack(&shortAssignLong, "bytes10", output)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(output, shortAssignLong[:]) {
|
|
||||||
t.Errorf("expected %x to be %x", shortAssignLong, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
func TestUnmarshal(t *testing.T) {
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
||||||
|
|
@ -450,6 +354,29 @@ func TestUnmarshal(t *testing.T) {
|
||||||
}
|
}
|
||||||
buff := new(bytes.Buffer)
|
buff := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// marshall mixed bytes (mixedBytes)
|
||||||
|
p0, p0Exp := []byte{}, common.Hex2Bytes("01020000000000000000")
|
||||||
|
p1, p1Exp := [32]byte{}, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")
|
||||||
|
mixedBytes := []interface{}{&p0, &p1}
|
||||||
|
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff"))
|
||||||
|
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a"))
|
||||||
|
buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000"))
|
||||||
|
|
||||||
|
err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
if bytes.Compare(p0, p0Exp) != 0 {
|
||||||
|
t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Compare(p1[:], p1Exp) != 0 {
|
||||||
|
t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// marshal int
|
// marshal int
|
||||||
var Int *big.Int
|
var Int *big.Int
|
||||||
err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
|
|
@ -473,6 +400,7 @@ func TestUnmarshal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshal dynamic bytes max length 32
|
// marshal dynamic bytes max length 32
|
||||||
|
buff.Reset()
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
bytesOut := common.RightPadBytes([]byte("hello"), 32)
|
bytesOut := common.RightPadBytes([]byte("hello"), 32)
|
||||||
|
|
@ -504,11 +432,11 @@ func TestUnmarshal(t *testing.T) {
|
||||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshall dynamic bytes max length 63
|
// marshall dynamic bytes max length 64
|
||||||
buff.Reset()
|
buff.Reset()
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f"))
|
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f"))
|
||||||
bytesOut = common.RightPadBytes([]byte("hello"), 63)
|
bytesOut = common.RightPadBytes([]byte("hello"), 64)
|
||||||
buff.Write(bytesOut)
|
buff.Write(bytesOut)
|
||||||
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||||
|
|
@ -516,8 +444,8 @@ func TestUnmarshal(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(Bytes, bytesOut) {
|
if !bytes.Equal(Bytes, bytesOut[:len(bytesOut)-1]) {
|
||||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshal dynamic bytes output empty
|
// marshal dynamic bytes output empty
|
||||||
|
|
@ -569,29 +497,6 @@ func TestUnmarshal(t *testing.T) {
|
||||||
t.Error("expected error")
|
t.Error("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshal mixed bytes
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
|
||||||
fixed := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")
|
|
||||||
buff.Write(fixed)
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
bytesOut = common.RightPadBytes([]byte("hello"), 32)
|
|
||||||
buff.Write(bytesOut)
|
|
||||||
|
|
||||||
var out []interface{}
|
|
||||||
err = abi.Unpack(&out, "mixedBytes", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(bytesOut, out[0].([]byte)) {
|
|
||||||
t.Errorf("expected %x, got %x", bytesOut, out[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fixed, out[1].([]byte)) {
|
|
||||||
t.Errorf("expected %x, got %x", fixed, out[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
buff.Reset()
|
buff.Reset()
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"))
|
||||||
|
|
|
||||||
53
build/ci.go
53
build/ci.go
|
|
@ -24,7 +24,8 @@ Usage: go run ci.go <command> <command flags/arguments>
|
||||||
Available commands are:
|
Available commands are:
|
||||||
|
|
||||||
install [ -arch architecture ] [ packages... ] -- builds packages and executables
|
install [ -arch architecture ] [ packages... ] -- builds packages and executables
|
||||||
test [ -coverage ] [ -misspell ] [ packages... ] -- runs the tests
|
test [ -coverage ] [ packages... ] -- runs the tests
|
||||||
|
lint -- runs certain pre-selected linters
|
||||||
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts
|
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts
|
||||||
importkeys -- imports signing keys from env
|
importkeys -- imports signing keys from env
|
||||||
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
|
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
|
||||||
|
|
@ -120,7 +121,7 @@ var (
|
||||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||||
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
||||||
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
||||||
debDistros = []string{"trusty", "xenial", "zesty"}
|
debDistros = []string{"trusty", "xenial", "zesty", "artful"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
@ -146,6 +147,8 @@ func main() {
|
||||||
doInstall(os.Args[2:])
|
doInstall(os.Args[2:])
|
||||||
case "test":
|
case "test":
|
||||||
doTest(os.Args[2:])
|
doTest(os.Args[2:])
|
||||||
|
case "lint":
|
||||||
|
doLint(os.Args[2:])
|
||||||
case "archive":
|
case "archive":
|
||||||
doArchive(os.Args[2:])
|
doArchive(os.Args[2:])
|
||||||
case "debsrc":
|
case "debsrc":
|
||||||
|
|
@ -280,7 +283,6 @@ func goToolArch(arch string, subcmd string, args ...string) *exec.Cmd {
|
||||||
|
|
||||||
func doTest(cmdline []string) {
|
func doTest(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
misspell = flag.Bool("misspell", false, "Whether to run the spell checker")
|
|
||||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
|
|
@ -294,10 +296,7 @@ func doTest(cmdline []string) {
|
||||||
|
|
||||||
// Run analysis tools before the tests.
|
// Run analysis tools before the tests.
|
||||||
build.MustRun(goTool("vet", packages...))
|
build.MustRun(goTool("vet", packages...))
|
||||||
if *misspell {
|
|
||||||
// TODO(karalabe): Reenable after false detection is fixed: https://github.com/client9/misspell/issues/105
|
|
||||||
// spellcheck(packages)
|
|
||||||
}
|
|
||||||
// Run the actual tests.
|
// Run the actual tests.
|
||||||
gotest := goTool("test", buildFlags(env)...)
|
gotest := goTool("test", buildFlags(env)...)
|
||||||
// Test a single package at a time. CI builders are slow
|
// Test a single package at a time. CI builders are slow
|
||||||
|
|
@ -306,35 +305,31 @@ func doTest(cmdline []string) {
|
||||||
if *coverage {
|
if *coverage {
|
||||||
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
||||||
}
|
}
|
||||||
|
|
||||||
gotest.Args = append(gotest.Args, packages...)
|
gotest.Args = append(gotest.Args, packages...)
|
||||||
build.MustRun(gotest)
|
build.MustRun(gotest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// spellcheck runs the client9/misspell spellchecker package on all Go, Cgo and
|
// runs gometalinter on requested packages
|
||||||
// test files in the requested packages.
|
func doLint(cmdline []string) {
|
||||||
func spellcheck(packages []string) {
|
flag.CommandLine.Parse(cmdline)
|
||||||
// Ensure the spellchecker is available
|
|
||||||
build.MustRun(goTool("get", "github.com/client9/misspell/cmd/misspell"))
|
|
||||||
|
|
||||||
// Windows chokes on long argument lists, check packages individually
|
packages := []string{"./..."}
|
||||||
for _, pkg := range packages {
|
if len(flag.CommandLine.Args()) > 0 {
|
||||||
// The spell checker doesn't work on packages, gather all .go files for it
|
packages = flag.CommandLine.Args()
|
||||||
out, err := goTool("list", "-f", "{{.Dir}}{{range .GoFiles}}\n{{.}}{{end}}{{range .CgoFiles}}\n{{.}}{{end}}{{range .TestGoFiles}}\n{{.}}{{end}}", pkg).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("source file listing failed: %v\n%s", err, string(out))
|
|
||||||
}
|
}
|
||||||
// Retrieve the folder and assemble the source list
|
// Get metalinter and install all supported linters
|
||||||
lines := strings.Split(string(out), "\n")
|
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v1"))
|
||||||
root := lines[0]
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), "--install")
|
||||||
|
|
||||||
sources := make([]string, 0, len(lines)-1)
|
// Run fast linters batched together
|
||||||
for _, line := range lines[1:] {
|
configs := []string{"--vendor", "--disable-all", "--enable=vet", "--enable=gofmt", "--enable=misspell"}
|
||||||
if line = strings.TrimSpace(line); line != "" {
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
|
||||||
sources = append(sources, filepath.Join(root, line))
|
|
||||||
}
|
// Run slow linters one by one
|
||||||
}
|
for _, linter := range []string{"unconvert"} {
|
||||||
// Run the spell checker for this particular package
|
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
||||||
build.MustRunCommand(filepath.Join(GOBIN, "misspell"), append([]string{"-error"}, sources...)...)
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,8 @@ 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}
|
||||||
if state, err := test.Run(st, cfg); err != nil {
|
state, err := test.Run(st, cfg)
|
||||||
|
if err != nil {
|
||||||
// Test failed, mark as so and dump any state to aid debugging
|
// Test failed, mark as so and dump any state to aid debugging
|
||||||
result.Pass, result.Error = false, err.Error()
|
result.Pass, result.Error = false, err.Error()
|
||||||
if ctx.GlobalBool(DumpFlag.Name) && state != nil {
|
if ctx.GlobalBool(DumpFlag.Name) && state != nil {
|
||||||
|
|
@ -102,6 +103,11 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||||
result.State = &dump
|
result.State = &dump
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// print state root for evmlab tracing (already committed above, so no need to delete objects again
|
||||||
|
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
|
||||||
|
}
|
||||||
|
|
||||||
results = append(results, *result)
|
results = append(results, *result)
|
||||||
|
|
||||||
// Print any structured logs collected
|
// Print any structured logs collected
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
@ -33,6 +35,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -181,9 +184,9 @@ func main() {
|
||||||
|
|
||||||
// request represents an accepted funding request.
|
// request represents an accepted funding request.
|
||||||
type request struct {
|
type request struct {
|
||||||
Username string `json:"username"` // GitHub user for displaying an avatar
|
Avatar string `json:"avatar"` // Avatar URL to make the UI nicer
|
||||||
Account common.Address `json:"account"` // Ethereum address being funded
|
Account common.Address `json:"account"` // Ethereum address being funded
|
||||||
Time time.Time `json:"time"` // Timestamp when te request was accepted
|
Time time.Time `json:"time"` // Timestamp when the request was accepted
|
||||||
Tx *types.Transaction `json:"tx"` // Transaction funding the account
|
Tx *types.Transaction `json:"tx"` // Transaction funding the account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,6 +302,8 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// apiHandler handles requests for Ether grants and transaction statuses.
|
// apiHandler handles requests for Ether grants and transaction statuses.
|
||||||
func (f *faucet) apiHandler(conn *websocket.Conn) {
|
func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
// Start tracking the connection and drop at the end
|
// Start tracking the connection and drop at the end
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
f.conns = append(f.conns, conn)
|
f.conns = append(f.conns, conn)
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
|
@ -313,25 +318,50 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
}
|
}
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
}()
|
}()
|
||||||
// Send a few initial stats to the client
|
// Gather the initial stats from the network to report
|
||||||
balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
|
var (
|
||||||
nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
|
head *types.Header
|
||||||
|
balance *big.Int
|
||||||
|
nonce uint64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// Attempt to retrieve the stats, may error on no faucet connectivity
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
head, err = f.client.HeaderByNumber(ctx, nil)
|
||||||
|
if err == nil {
|
||||||
|
balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number)
|
||||||
|
if err == nil {
|
||||||
|
nonce, err = f.client.NonceAt(ctx, f.account.Address, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
|
||||||
websocket.JSON.Send(conn, map[string]interface{}{
|
// If stats retrieval failed, wait a bit and retry
|
||||||
|
if err != nil {
|
||||||
|
if err = sendError(conn, errors.New("Faucet offline: "+err.Error())); err != nil {
|
||||||
|
log.Warn("Failed to send faucet error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Initial stats reported successfully, proceed with user interaction
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Send over the initial stats and the latest header
|
||||||
|
if err = send(conn, map[string]interface{}{
|
||||||
"funds": balance.Div(balance, ether),
|
"funds": balance.Div(balance, ether),
|
||||||
"funded": nonce,
|
"funded": nonce,
|
||||||
"peers": f.stack.Server().PeerCount(),
|
"peers": f.stack.Server().PeerCount(),
|
||||||
"requests": f.reqs,
|
"requests": f.reqs,
|
||||||
})
|
}, 3*time.Second); err != nil {
|
||||||
// Send the initial block to the client
|
log.Warn("Failed to send initial stats to client", "err", err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
return
|
||||||
header, err := f.client.HeaderByNumber(ctx, nil)
|
}
|
||||||
cancel()
|
if err = send(conn, head, 3*time.Second); err != nil {
|
||||||
|
log.Warn("Failed to send initial header to client", "err", err)
|
||||||
if err != nil {
|
return
|
||||||
log.Error("Failed to retrieve latest header", "err", err)
|
|
||||||
} else {
|
|
||||||
websocket.JSON.Send(conn, header)
|
|
||||||
}
|
}
|
||||||
// Keep reading requests from the websocket until the connection breaks
|
// Keep reading requests from the websocket until the connection breaks
|
||||||
for {
|
for {
|
||||||
|
|
@ -341,18 +371,25 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
Tier uint `json:"tier"`
|
Tier uint `json:"tier"`
|
||||||
Captcha string `json:"captcha"`
|
Captcha string `json:"captcha"`
|
||||||
}
|
}
|
||||||
if err := websocket.JSON.Receive(conn, &msg); err != nil {
|
if err = websocket.JSON.Receive(conn, &msg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
|
||||||
|
!strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
|
||||||
|
if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil {
|
||||||
|
log.Warn("Failed to send URL error to client", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(msg.URL, "https://gist.github.com/") {
|
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"})
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if msg.Tier >= uint(*tiersFlag) {
|
if msg.Tier >= uint(*tiersFlag) {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "Invalid funding tier requested"})
|
if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil {
|
||||||
|
log.Warn("Failed to send tier error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Info("Faucet funds requested", "gist", msg.URL, "tier", msg.Tier)
|
log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier)
|
||||||
|
|
||||||
// If captcha verifications are enabled, make sure we're not dealing with a robot
|
// If captcha verifications are enabled, make sure we're not dealing with a robot
|
||||||
if *captchaToken != "" {
|
if *captchaToken != "" {
|
||||||
|
|
@ -362,7 +399,10 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
|
|
||||||
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
|
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send captcha post error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var result struct {
|
var result struct {
|
||||||
|
|
@ -372,74 +412,55 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
err = json.NewDecoder(res.Body).Decode(&result)
|
err = json.NewDecoder(res.Body).Decode(&result)
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send captcha decode error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !result.Success {
|
if !result.Success {
|
||||||
log.Warn("Captcha verification failed", "err", string(result.Errors))
|
log.Warn("Captcha verification failed", "err", string(result.Errors))
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "Beep-bop, you're a robot!"})
|
if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil {
|
||||||
|
log.Warn("Failed to send captcha failure to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Retrieve the gist from the GitHub Gist APIs
|
// Retrieve the Ethereum address to fund, the requesting user and a profile picture
|
||||||
parts := strings.Split(msg.URL, "/")
|
var (
|
||||||
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
|
username string
|
||||||
if *githubUser != "" {
|
avatar string
|
||||||
req.SetBasicAuth(*githubUser, *githubToken)
|
address common.Address
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(msg.URL, "https://gist.github.com/"):
|
||||||
|
username, avatar, address, err = authGitHub(msg.URL)
|
||||||
|
case strings.HasPrefix(msg.URL, "https://twitter.com/"):
|
||||||
|
username, avatar, address, err = authTwitter(msg.URL)
|
||||||
|
case strings.HasPrefix(msg.URL, "https://plus.google.com/"):
|
||||||
|
username, avatar, address, err = authGooglePlus(msg.URL)
|
||||||
|
case strings.HasPrefix(msg.URL, "https://www.facebook.com/"):
|
||||||
|
username, avatar, address, err = authFacebook(msg.URL)
|
||||||
|
default:
|
||||||
|
err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
|
||||||
}
|
}
|
||||||
res, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send prefix error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var gist struct {
|
log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address)
|
||||||
Owner struct {
|
|
||||||
Login string `json:"login"`
|
|
||||||
} `json:"owner"`
|
|
||||||
Files map[string]struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
} `json:"files"`
|
|
||||||
}
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&gist)
|
|
||||||
res.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if gist.Owner.Login == "" {
|
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "Anonymous Gists not allowed"})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Iterate over all the files and look for Ethereum addresses
|
|
||||||
var address common.Address
|
|
||||||
for _, file := range gist.Files {
|
|
||||||
content := strings.TrimSpace(file.Content)
|
|
||||||
if len(content) == 2+common.AddressLength*2 {
|
|
||||||
address = common.HexToAddress(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if address == (common.Address{}) {
|
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "No Ethereum address found to fund"})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Validate the user's existence since the API is unhelpful here
|
|
||||||
if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
|
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "Invalid user... boom!"})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ensure the user didn't request funds too recently
|
// Ensure the user didn't request funds too recently
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
var (
|
var (
|
||||||
fund bool
|
fund bool
|
||||||
timeout time.Time
|
timeout time.Time
|
||||||
)
|
)
|
||||||
if timeout = f.timeouts[gist.Owner.Login]; time.Now().After(timeout) {
|
if timeout = f.timeouts[username]; time.Now().After(timeout) {
|
||||||
// User wasn't funded recently, create the funding transaction
|
// User wasn't funded recently, create the funding transaction
|
||||||
amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether)
|
amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether)
|
||||||
amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil))
|
amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil))
|
||||||
|
|
@ -448,33 +469,45 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, big.NewInt(21000), f.price, nil)
|
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, big.NewInt(21000), f.price, nil)
|
||||||
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send transaction creation error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Submit the transaction and mark as funded if successful
|
// Submit the transaction and mark as funded if successful
|
||||||
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
|
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send transaction transmission error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.reqs = append(f.reqs, &request{
|
f.reqs = append(f.reqs, &request{
|
||||||
Username: gist.Owner.Login,
|
Avatar: avatar,
|
||||||
Account: address,
|
Account: address,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Tx: signed,
|
Tx: signed,
|
||||||
})
|
})
|
||||||
f.timeouts[gist.Owner.Login] = time.Now().Add(time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute)
|
f.timeouts[username] = time.Now().Add(time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute)
|
||||||
fund = true
|
fund = true
|
||||||
}
|
}
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
|
||||||
// Send an error if too frequent funding, othewise a success
|
// Send an error if too frequent funding, othewise a success
|
||||||
if !fund {
|
if !fund {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))})
|
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil {
|
||||||
|
log.Warn("Failed to send funding error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", gist.Owner.Login, address.Hex())})
|
if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil {
|
||||||
|
log.Warn("Failed to send funding success to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case f.update <- struct{}{}:
|
case f.update <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
|
|
@ -497,11 +530,31 @@ func (f *faucet) loop() {
|
||||||
select {
|
select {
|
||||||
case head := <-heads:
|
case head := <-heads:
|
||||||
// New chain head arrived, query the current stats and stream to clients
|
// New chain head arrived, query the current stats and stream to clients
|
||||||
balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
|
var (
|
||||||
balance = new(big.Int).Div(balance, ether)
|
balance *big.Int
|
||||||
|
nonce uint64
|
||||||
|
price *big.Int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number)
|
||||||
|
if err == nil {
|
||||||
|
nonce, err = f.client.NonceAt(ctx, f.account.Address, nil)
|
||||||
|
if err == nil {
|
||||||
|
price, err = f.client.SuggestGasPrice(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
|
||||||
price, _ := f.client.SuggestGasPrice(context.Background())
|
// If querying the data failed, try for the next block
|
||||||
nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
|
if err != nil {
|
||||||
|
log.Warn("Failed to update faucet state", "block", head.Number, "hash", head.Hash(), "err", err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Info("Updated faucet state", "block", head.Number, "hash", head.Hash(), "balance", balance, "nonce", nonce, "price", price)
|
||||||
|
}
|
||||||
|
// Faucet state retrieved, update locally and send to clients
|
||||||
|
balance = new(big.Int).Div(balance, ether)
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
f.price, f.nonce = price, nonce
|
f.price, f.nonce = price, nonce
|
||||||
|
|
@ -512,17 +565,17 @@ func (f *faucet) loop() {
|
||||||
|
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
for _, conn := range f.conns {
|
for _, conn := range f.conns {
|
||||||
if err := websocket.JSON.Send(conn, map[string]interface{}{
|
if err := send(conn, map[string]interface{}{
|
||||||
"funds": balance,
|
"funds": balance,
|
||||||
"funded": f.nonce,
|
"funded": f.nonce,
|
||||||
"peers": f.stack.Server().PeerCount(),
|
"peers": f.stack.Server().PeerCount(),
|
||||||
"requests": f.reqs,
|
"requests": f.reqs,
|
||||||
}); err != nil {
|
}, time.Second); err != nil {
|
||||||
log.Warn("Failed to send stats to client", "err", err)
|
log.Warn("Failed to send stats to client", "err", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := websocket.JSON.Send(conn, head); err != nil {
|
if err := send(conn, head, time.Second); err != nil {
|
||||||
log.Warn("Failed to send header to client", "err", err)
|
log.Warn("Failed to send header to client", "err", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
@ -533,7 +586,7 @@ func (f *faucet) loop() {
|
||||||
// Pending requests updated, stream to clients
|
// Pending requests updated, stream to clients
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
for _, conn := range f.conns {
|
for _, conn := range f.conns {
|
||||||
if err := websocket.JSON.Send(conn, map[string]interface{}{"requests": f.reqs}); err != nil {
|
if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil {
|
||||||
log.Warn("Failed to send requests to client", "err", err)
|
log.Warn("Failed to send requests to client", "err", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
@ -542,3 +595,184 @@ func (f *faucet) loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sends transmits a data packet to the remote end of the websocket, but also
|
||||||
|
// setting a write deadline to prevent waiting forever on the node.
|
||||||
|
func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error {
|
||||||
|
if timeout == 0 {
|
||||||
|
timeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||||
|
return websocket.JSON.Send(conn, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendError transmits an error to the remote end of the websocket, also setting
|
||||||
|
// the write deadline to 1 second to prevent waiting forever.
|
||||||
|
func sendError(conn *websocket.Conn, err error) error {
|
||||||
|
return send(conn, map[string]string{"error": err.Error()}, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSuccess transmits a success message to the remote end of the websocket, also
|
||||||
|
// setting the write deadline to 1 second to prevent waiting forever.
|
||||||
|
func sendSuccess(conn *websocket.Conn, msg string) error {
|
||||||
|
return send(conn, map[string]string{"success": msg}, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// authGitHub tries to authenticate a faucet request using GitHub gists, returning
|
||||||
|
// the username, avatar URL and Ethereum address to fund on success.
|
||||||
|
func authGitHub(url string) (string, string, common.Address, error) {
|
||||||
|
// Retrieve the gist from the GitHub Gist APIs
|
||||||
|
parts := strings.Split(url, "/")
|
||||||
|
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
|
||||||
|
if *githubUser != "" {
|
||||||
|
req.SetBasicAuth(*githubUser, *githubToken)
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
var gist struct {
|
||||||
|
Owner struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
} `json:"owner"`
|
||||||
|
Files map[string]struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"files"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&gist)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
if gist.Owner.Login == "" {
|
||||||
|
return "", "", common.Address{}, errors.New("Anonymous Gists not allowed")
|
||||||
|
}
|
||||||
|
// Iterate over all the files and look for Ethereum addresses
|
||||||
|
var address common.Address
|
||||||
|
for _, file := range gist.Files {
|
||||||
|
content := strings.TrimSpace(file.Content)
|
||||||
|
if len(content) == 2+common.AddressLength*2 {
|
||||||
|
address = common.HexToAddress(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if address == (common.Address{}) {
|
||||||
|
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
|
||||||
|
}
|
||||||
|
// Validate the user's existence since the API is unhelpful here
|
||||||
|
if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", "", common.Address{}, errors.New("Invalid user... boom!")
|
||||||
|
}
|
||||||
|
// Everything passed validation, return the gathered infos
|
||||||
|
return gist.Owner.Login + "@github", fmt.Sprintf("https://github.com/%s.png?size=64", gist.Owner.Login), address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authTwitter tries to authenticate a faucet request using Twitter posts, returning
|
||||||
|
// the username, avatar URL and Ethereum address to fund on success.
|
||||||
|
func authTwitter(url string) (string, string, common.Address, error) {
|
||||||
|
// Ensure the user specified a meaningful URL, no fancy nonsense
|
||||||
|
parts := strings.Split(url, "/")
|
||||||
|
if len(parts) < 4 || parts[len(parts)-2] != "status" {
|
||||||
|
return "", "", common.Address{}, errors.New("Invalid Twitter status URL")
|
||||||
|
}
|
||||||
|
username := parts[len(parts)-3]
|
||||||
|
|
||||||
|
// Twitter's API isn't really friendly with direct links. Still, we don't
|
||||||
|
// want to do ask read permissions from users, so just load the public posts and
|
||||||
|
// scrape it for the Ethereum address and profile URL.
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
reader, err := zlib.NewReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
|
||||||
|
if address == (common.Address{}) {
|
||||||
|
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
|
||||||
|
}
|
||||||
|
var avatar string
|
||||||
|
if parts = regexp.MustCompile("src=\"([^\"]+twimg.com/profile_images[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 {
|
||||||
|
avatar = parts[1]
|
||||||
|
}
|
||||||
|
return username + "@twitter", avatar, address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authGooglePlus tries to authenticate a faucet request using GooglePlus posts,
|
||||||
|
// returning the username, avatar URL and Ethereum address to fund on success.
|
||||||
|
func authGooglePlus(url string) (string, string, common.Address, error) {
|
||||||
|
// Ensure the user specified a meaningful URL, no fancy nonsense
|
||||||
|
parts := strings.Split(url, "/")
|
||||||
|
if len(parts) < 4 || parts[len(parts)-2] != "posts" {
|
||||||
|
return "", "", common.Address{}, errors.New("Invalid Google+ post URL")
|
||||||
|
}
|
||||||
|
username := parts[len(parts)-3]
|
||||||
|
|
||||||
|
// Google's API isn't really friendly with direct links. Still, we don't
|
||||||
|
// want to do ask read permissions from users, so just load the public posts and
|
||||||
|
// scrape it for the Ethereum address and profile URL.
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
|
||||||
|
if address == (common.Address{}) {
|
||||||
|
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
|
||||||
|
}
|
||||||
|
var avatar string
|
||||||
|
if parts = regexp.MustCompile("src=\"([^\"]+googleusercontent.com[^\"]+photo.jpg)\"").FindStringSubmatch(string(body)); len(parts) == 2 {
|
||||||
|
avatar = parts[1]
|
||||||
|
}
|
||||||
|
return username + "@google+", avatar, address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authFacebook tries to authenticate a faucet request using Facebook posts,
|
||||||
|
// returning the username, avatar URL and Ethereum address to fund on success.
|
||||||
|
func authFacebook(url string) (string, string, common.Address, error) {
|
||||||
|
// Ensure the user specified a meaningful URL, no fancy nonsense
|
||||||
|
parts := strings.Split(url, "/")
|
||||||
|
if len(parts) < 4 || parts[len(parts)-2] != "posts" {
|
||||||
|
return "", "", common.Address{}, errors.New("Invalid Facebook post URL")
|
||||||
|
}
|
||||||
|
username := parts[len(parts)-3]
|
||||||
|
|
||||||
|
// Facebook's Graph API isn't really friendly with direct links. Still, we don't
|
||||||
|
// want to do ask read permissions from users, so just load the public posts and
|
||||||
|
// scrape it for the Ethereum address and profile URL.
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", common.Address{}, err
|
||||||
|
}
|
||||||
|
address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
|
||||||
|
if address == (common.Address{}) {
|
||||||
|
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
|
||||||
|
}
|
||||||
|
var avatar string
|
||||||
|
if parts = regexp.MustCompile("src=\"([^\"]+fbcdn.net[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 {
|
||||||
|
avatar = parts[1]
|
||||||
|
}
|
||||||
|
return username + "@facebook", avatar, address, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>{{.Network}}: GitHub Faucet</title>
|
<title>{{.Network}}: Authenticated Faucet</title>
|
||||||
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||||
|
|
@ -43,13 +43,13 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row" style="margin-bottom: 16px;">
|
<div class="row" style="margin-bottom: 16px;">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h1 style="text-align: center;"><i class="fa fa-bath" aria-hidden="true"></i> {{.Network}} GitHub Authenticated Faucet <i class="fa fa-github-alt" aria-hidden="true"></i></h1>
|
<h1 style="text-align: center;"><i class="fa fa-bath" aria-hidden="true"></i> {{.Network}} Authenticated Faucet</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8 col-lg-offset-2">
|
<div class="col-lg-8 col-lg-offset-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address...">
|
<input id="url" name="url" type="text" class="form-control" placeholder="Social network URL containing your Ethereum address...">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Give me Ether <i class="fa fa-caret-down" aria-hidden="true"></i></button>
|
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Give me Ether <i class="fa fa-caret-down" aria-hidden="true"></i></button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">{{range $idx, $amount := .Amounts}}
|
<ul class="dropdown-menu dropdown-menu-right">{{range $idx, $amount := .Amounts}}
|
||||||
|
|
@ -80,8 +80,21 @@
|
||||||
<div class="row" style="margin-top: 32px;">
|
<div class="row" style="margin-top: 32px;">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h3>How does this work?</h3>
|
<h3>How does this work?</h3>
|
||||||
<p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to GitHub accounts. Anyone having a GitHub account may request funds within the permitted limits.</p>
|
<p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to certain common 3rd party accounts. Anyone having a GitHub, Twitter, Google+ or Facebook account may request funds within the permitted limits.</p>
|
||||||
<p>To request funds, simply create a <a href="https://gist.github.com/" target="_about:blank">GitHub Gist</a> with your Ethereum address pasted into the contents (the file name doesn't matter), copy paste the gists URL into the above input box and fire away! You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
|
<dl class="dl-horizontal">
|
||||||
|
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-github-alt" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||||
|
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via GitHub, create a <a href="https://gist.github.com/" target="_about:blank">gist</a> with your Ethereum address embedded into the content (the file name doesn't matter).<br/>Copy-paste the gists URL into the above input box and fire away!</dd>
|
||||||
|
|
||||||
|
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-twitter" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||||
|
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Twitter, make a <a href="https://twitter.com/intent/tweet?text=Requesting%20faucet%20funds%20into%200x0000000000000000000000000000000000000000%20on%20the%20%23{{.Network}}%20%23Ethereum%20test%20network." target="_about:blank">tweet</a> with your Ethereum address pasted into the contents (surrounding text doesn't matter).<br/>Copy-paste the <a href="https://support.twitter.com/articles/80586" target="_about:blank">tweets URL</a> into the above input box and fire away!</dd>
|
||||||
|
|
||||||
|
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-google-plus-official" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||||
|
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Google Plus, publish a new <strong>public</strong> post with your Ethereum address embedded into the content (surrounding text doesn't matter).<br/>Copy-paste the posts URL into the above input box and fire away!</dd>
|
||||||
|
|
||||||
|
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-facebook" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||||
|
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Facebook, publish a new <strong>public</strong> post with your Ethereum address embedded into the content (surrounding text doesn't matter).<br/>Copy-paste the <a href="https://www.facebook.com/help/community/question/?id=282662498552845" target="_about:blank">posts URL</a> into the above input box and fire away!</dd>
|
||||||
|
</dl>
|
||||||
|
<p>You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
|
||||||
{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
|
{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -93,10 +106,22 @@
|
||||||
var attempt = 0;
|
var attempt = 0;
|
||||||
var server;
|
var server;
|
||||||
var tier = 0;
|
var tier = 0;
|
||||||
|
var requests = [];
|
||||||
|
|
||||||
|
// Define a function that creates closures to drop old requests
|
||||||
|
var dropper = function(hash) {
|
||||||
|
return function() {
|
||||||
|
for (var i=0; i<requests.length; i++) {
|
||||||
|
if (requests[i].tx.hash == hash) {
|
||||||
|
requests.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
// Define the function that submits a gist url to the server
|
// Define the function that submits a gist url to the server
|
||||||
var submit = function({{if .Recaptcha}}captcha{{end}}) {
|
var submit = function({{if .Recaptcha}}captcha{{end}}) {
|
||||||
server.send(JSON.stringify({url: $("#gist")[0].value, tier: tier{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
|
server.send(JSON.stringify({url: $("#url")[0].value, tier: tier{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
|
||||||
grecaptcha.reset();{{end}}
|
grecaptcha.reset();{{end}}
|
||||||
};
|
};
|
||||||
// Define a method to reconnect upon server loss
|
// Define a method to reconnect upon server loss
|
||||||
|
|
@ -127,21 +152,85 @@
|
||||||
$("#block").text(parseInt(msg.number, 16));
|
$("#block").text(parseInt(msg.number, 16));
|
||||||
}
|
}
|
||||||
if (msg.error !== undefined) {
|
if (msg.error !== undefined) {
|
||||||
noty({layout: 'topCenter', text: msg.error, type: 'error'});
|
noty({layout: 'topCenter', text: msg.error, type: 'error', timeout: 5000, progressBar: true});
|
||||||
}
|
}
|
||||||
if (msg.success !== undefined) {
|
if (msg.success !== undefined) {
|
||||||
noty({layout: 'topCenter', text: msg.success, type: 'success'});
|
noty({layout: 'topCenter', text: msg.success, type: 'success', timeout: 5000, progressBar: true});
|
||||||
}
|
}
|
||||||
if (msg.requests !== undefined && msg.requests !== null) {
|
if (msg.requests !== undefined && msg.requests !== null) {
|
||||||
var content = "";
|
// Mark all previous requests missing as done
|
||||||
|
for (var i=0; i<requests.length; i++) {
|
||||||
|
if (msg.requests.length > 0 && msg.requests[0].tx.hash == requests[i].tx.hash) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (requests[i].time != "") {
|
||||||
|
requests[i].time = "";
|
||||||
|
setTimeout(dropper(requests[i].tx.hash), 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Append any new requests into our local collection
|
||||||
|
var common = -1;
|
||||||
|
if (requests.length > 0) {
|
||||||
for (var i=0; i<msg.requests.length; i++) {
|
for (var i=0; i<msg.requests.length; i++) {
|
||||||
content += "<tr><td><div style=\"background: url('https://github.com/" + msg.requests[i].username + ".png?size=64'); background-size: cover; width:32px; height: 32px; border-radius: 4px;\"></div></td><td><pre>" + msg.requests[i].account + "</pre></td><td style=\"width: 100%; text-align: center; vertical-align: middle;\">" + moment.duration(moment(msg.requests[i].time).unix()-moment().unix(), 'seconds').humanize(true) + "</td></tr>";
|
if (requests[requests.length-1].tx.hash == msg.requests[i].tx.hash) {
|
||||||
|
common = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i=common+1; i<msg.requests.length; i++) {
|
||||||
|
requests.push(msg.requests[i]);
|
||||||
|
}
|
||||||
|
// Iterate over our entire local collection and re-render the funding table
|
||||||
|
var content = "";
|
||||||
|
for (var i=0; i<requests.length; i++) {
|
||||||
|
var done = requests[i].time == "";
|
||||||
|
var elapsed = moment().unix()-moment(requests[i].time).unix();
|
||||||
|
|
||||||
|
content += "<tr id='" + requests[i].tx.hash + "'>";
|
||||||
|
content += " <td><div style=\"background: url('" + requests[i].avatar + "'); background-size: cover; width:32px; height: 32px; border-radius: 4px;\"></div></td>";
|
||||||
|
content += " <td><pre>" + requests[i].account + "</pre></td>";
|
||||||
|
content += " <td style=\"width: 100%; text-align: center; vertical-align: middle;\">";
|
||||||
|
if (done) {
|
||||||
|
content += " funded";
|
||||||
|
} else {
|
||||||
|
content += " <span id='time-" + i + "' class='timer'>" + moment.duration(-elapsed, 'seconds').humanize(true) + "</span>";
|
||||||
|
}
|
||||||
|
content += " <div class='progress' style='height: 4px; margin: 0;'>";
|
||||||
|
if (done) {
|
||||||
|
content += " <div class='progress-bar progress-bar-success' role='progressbar' aria-valuenow='30' style='width:100%;'></div>";
|
||||||
|
} else if (elapsed > 30) {
|
||||||
|
content += " <div class='progress-bar progress-bar-danger progress-bar-striped active' role='progressbar' aria-valuenow='30' style='width:100%;'></div>";
|
||||||
|
} else {
|
||||||
|
content += " <div class='progress-bar progress-bar-striped active' role='progressbar' aria-valuenow='" + elapsed + "' style='width:" + (elapsed * 100 / 30) + "%;'></div>";
|
||||||
|
}
|
||||||
|
content += " </div>";
|
||||||
|
content += " </td>";
|
||||||
|
content += "</tr>";
|
||||||
}
|
}
|
||||||
$("#requests").html("<tbody>" + content + "</tbody>");
|
$("#requests").html("<tbody>" + content + "</tbody>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.onclose = function() { setTimeout(reconnect, 3000); };
|
server.onclose = function() { setTimeout(reconnect, 3000); };
|
||||||
}
|
}
|
||||||
|
// Start a UI updater to push the progress bars forward until they are done
|
||||||
|
setInterval(function() {
|
||||||
|
$('.progress-bar').each(function() {
|
||||||
|
var progress = Number($(this).attr('aria-valuenow')) + 1;
|
||||||
|
if (progress < 30) {
|
||||||
|
$(this).attr('aria-valuenow', progress);
|
||||||
|
$(this).css('width', (progress * 100 / 30) + '%');
|
||||||
|
} else if (progress == 30) {
|
||||||
|
$(this).css('width', '100%');
|
||||||
|
$(this).addClass("progress-bar-danger");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$('.timer').each(function() {
|
||||||
|
var index = Number($(this).attr('id').substring(5));
|
||||||
|
$(this).html(moment.duration(moment(requests[index].time).unix()-moment().unix(), 'seconds').humanize(true));
|
||||||
|
})
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
// Establish a websocket connection to the API server
|
// Establish a websocket connection to the API server
|
||||||
reconnect();
|
reconnect();
|
||||||
</script>{{if .Recaptcha}}
|
</script>{{if .Recaptcha}}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -134,7 +134,7 @@ Fatal: could not decrypt key with given passphrase
|
||||||
func TestUnlockFlag(t *testing.T) {
|
func TestUnlockFlag(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
geth.Expect(`
|
geth.Expect(`
|
||||||
|
|
@ -158,7 +158,7 @@ Passphrase: {{.InputLine "foobar"}}
|
||||||
func TestUnlockFlagWrongPassword(t *testing.T) {
|
func TestUnlockFlagWrongPassword(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
||||||
defer geth.ExpectExit()
|
defer geth.ExpectExit()
|
||||||
geth.Expect(`
|
geth.Expect(`
|
||||||
|
|
@ -177,7 +177,7 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could
|
||||||
func TestUnlockFlagMultiIndex(t *testing.T) {
|
func TestUnlockFlagMultiIndex(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--unlock", "0,2",
|
"--unlock", "0,2",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
geth.Expect(`
|
geth.Expect(`
|
||||||
|
|
@ -204,7 +204,7 @@ Passphrase: {{.InputLine "foobar"}}
|
||||||
func TestUnlockFlagPasswordFile(t *testing.T) {
|
func TestUnlockFlagPasswordFile(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--password", "testdata/passwords.txt", "--unlock", "0,2",
|
"--password", "testdata/passwords.txt", "--unlock", "0,2",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
geth.ExpectExit()
|
geth.ExpectExit()
|
||||||
|
|
@ -224,7 +224,7 @@ func TestUnlockFlagPasswordFile(t *testing.T) {
|
||||||
func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
|
func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
|
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
|
||||||
defer geth.ExpectExit()
|
defer geth.ExpectExit()
|
||||||
geth.Expect(`
|
geth.Expect(`
|
||||||
|
|
@ -235,7 +235,7 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
|
||||||
func TestUnlockFlagAmbiguous(t *testing.T) {
|
func TestUnlockFlagAmbiguous(t *testing.T) {
|
||||||
store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
|
store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
|
"--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
defer geth.ExpectExit()
|
defer geth.ExpectExit()
|
||||||
|
|
@ -273,7 +273,7 @@ In order to avoid this warning, you need to remove the following duplicate key f
|
||||||
func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
|
func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
|
||||||
store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
|
store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
|
"--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
||||||
defer geth.ExpectExit()
|
defer geth.ExpectExit()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
|
|
||||||
// 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
|
||||||
shhEnabled := enableWhisper(ctx)
|
shhEnabled := enableWhisper(ctx)
|
||||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
|
||||||
if shhEnabled || shhAutoEnabled {
|
if shhEnabled || shhAutoEnabled {
|
||||||
if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
|
if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
|
||||||
cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
|
cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,8 @@ var (
|
||||||
utils.NetrestrictFlag,
|
utils.NetrestrictFlag,
|
||||||
utils.NodeKeyFileFlag,
|
utils.NodeKeyFileFlag,
|
||||||
utils.NodeKeyHexFlag,
|
utils.NodeKeyHexFlag,
|
||||||
utils.DevModeFlag,
|
utils.DeveloperFlag,
|
||||||
|
utils.DeveloperPeriodFlag,
|
||||||
utils.TestnetFlag,
|
utils.TestnetFlag,
|
||||||
utils.RinkebyFlag,
|
utils.RinkebyFlag,
|
||||||
utils.VMEnableDebugFlag,
|
utils.VMEnableDebugFlag,
|
||||||
|
|
@ -270,7 +271,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Start auxiliary services if enabled
|
// Start auxiliary services if enabled
|
||||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
|
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
||||||
// Mining only makes sense if a full Ethereum node is running
|
// Mining only makes sense if a full Ethereum node is running
|
||||||
var ethereum *eth.Ethereum
|
var ethereum *eth.Ethereum
|
||||||
if err := stack.Service(ðereum); err != nil {
|
if err := stack.Service(ðereum); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.TestnetFlag,
|
utils.TestnetFlag,
|
||||||
utils.RinkebyFlag,
|
utils.RinkebyFlag,
|
||||||
utils.DevModeFlag,
|
|
||||||
utils.SyncModeFlag,
|
utils.SyncModeFlag,
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
utils.IdentityFlag,
|
utils.IdentityFlag,
|
||||||
|
|
@ -81,6 +80,12 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{Name: "DEVELOPER CHAIN",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DeveloperFlag,
|
||||||
|
utils.DeveloperPeriodFlag,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "ETHASH",
|
Name: "ETHASH",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
||||||
})
|
})
|
||||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
files[filepath.Join(workdir, "genesis.json")] = []byte(config.node.genesis)
|
files[filepath.Join(workdir, "genesis.json")] = config.node.genesis
|
||||||
files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON)
|
files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON)
|
||||||
files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass)
|
files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ func deployNode(client *sshClient, network string, bootv4, bootv5 []string, conf
|
||||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
//genesisfile, _ := json.MarshalIndent(config.genesis, "", " ")
|
//genesisfile, _ := json.MarshalIndent(config.genesis, "", " ")
|
||||||
files[filepath.Join(workdir, "genesis.json")] = []byte(config.genesis)
|
files[filepath.Join(workdir, "genesis.json")] = config.genesis
|
||||||
|
|
||||||
if config.keyJSON != "" {
|
if config.keyJSON != "" {
|
||||||
files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON)
|
files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
@ -85,7 +84,7 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||||
}
|
}
|
||||||
auths = append(auths, ssh.PasswordCallback(func() (string, error) {
|
auths = append(auths, ssh.PasswordCallback(func() (string, error) {
|
||||||
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
|
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
|
||||||
blob, err := terminal.ReadPassword(int(syscall.Stdin))
|
blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return string(blob), err
|
return string(blob), err
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -231,7 +230,7 @@ func (w *wizard) readDefaultFloat(def float64) float64 {
|
||||||
// line and returns it. The input will not be echoed.
|
// line and returns it. The input will not be echoed.
|
||||||
func (w *wizard) readPassword() string {
|
func (w *wizard) readPassword() string {
|
||||||
fmt.Printf("> ")
|
fmt.Printf("> ")
|
||||||
text, err := terminal.ReadPassword(int(syscall.Stdin))
|
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Failed to read password", "err", err)
|
log.Crit("Failed to read password", "err", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func main() {
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
switch {
|
switch {
|
||||||
case *hexMode != "":
|
case *hexMode != "":
|
||||||
data, err := hex.DecodeString(*hexMode)
|
data, err := hex.DecodeString(strings.TrimPrefix(*hexMode, "0x"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
die(err)
|
die(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,13 @@ var (
|
||||||
Name: "rinkeby",
|
Name: "rinkeby",
|
||||||
Usage: "Rinkeby network: pre-configured proof-of-authority test network",
|
Usage: "Rinkeby network: pre-configured proof-of-authority test network",
|
||||||
}
|
}
|
||||||
DevModeFlag = cli.BoolFlag{
|
DeveloperFlag = cli.BoolFlag{
|
||||||
Name: "dev",
|
Name: "dev",
|
||||||
Usage: "Developer mode: pre-configured private network with several debugging flags",
|
Usage: "Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled",
|
||||||
|
}
|
||||||
|
DeveloperPeriodFlag = cli.IntFlag{
|
||||||
|
Name: "dev.period",
|
||||||
|
Usage: "Block period to use in developer mode (0 = mine only if transaction pending)",
|
||||||
}
|
}
|
||||||
IdentityFlag = cli.StringFlag{
|
IdentityFlag = cli.StringFlag{
|
||||||
Name: "identity",
|
Name: "identity",
|
||||||
|
|
@ -796,7 +800,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
cfg.NetRestrict = list
|
cfg.NetRestrict = list
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.GlobalBool(DevModeFlag.Name) {
|
if ctx.GlobalBool(DeveloperFlag.Name) {
|
||||||
// --dev mode can't use p2p networking.
|
// --dev mode can't use p2p networking.
|
||||||
cfg.MaxPeers = 0
|
cfg.MaxPeers = 0
|
||||||
cfg.ListenAddr = ":0"
|
cfg.ListenAddr = ":0"
|
||||||
|
|
@ -817,8 +821,8 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||||
switch {
|
switch {
|
||||||
case ctx.GlobalIsSet(DataDirFlag.Name):
|
case ctx.GlobalIsSet(DataDirFlag.Name):
|
||||||
cfg.DataDir = ctx.GlobalString(DataDirFlag.Name)
|
cfg.DataDir = ctx.GlobalString(DataDirFlag.Name)
|
||||||
case ctx.GlobalBool(DevModeFlag.Name):
|
case ctx.GlobalBool(DeveloperFlag.Name):
|
||||||
cfg.DataDir = filepath.Join(os.TempDir(), "ethereum_dev_mode")
|
cfg.DataDir = "" // unless explicitly requested, use memory databases
|
||||||
case ctx.GlobalBool(TestnetFlag.Name):
|
case ctx.GlobalBool(TestnetFlag.Name):
|
||||||
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet")
|
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet")
|
||||||
case ctx.GlobalBool(RinkebyFlag.Name):
|
case ctx.GlobalBool(RinkebyFlag.Name):
|
||||||
|
|
@ -924,7 +928,7 @@ 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, DevModeFlag, TestnetFlag, RinkebyFlag)
|
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
||||||
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
|
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
@ -985,14 +989,30 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
cfg.NetworkId = 4
|
cfg.NetworkId = 4
|
||||||
}
|
}
|
||||||
cfg.Genesis = core.DefaultRinkebyGenesisBlock()
|
cfg.Genesis = core.DefaultRinkebyGenesisBlock()
|
||||||
case ctx.GlobalBool(DevModeFlag.Name):
|
case ctx.GlobalBool(DeveloperFlag.Name):
|
||||||
cfg.Genesis = core.DevGenesisBlock()
|
// Create new developer account or reuse existing one
|
||||||
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
|
var (
|
||||||
cfg.GasPrice = new(big.Int)
|
developer accounts.Account
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accs := ks.Accounts(); len(accs) > 0 {
|
||||||
|
developer = ks.Accounts()[0]
|
||||||
|
} else {
|
||||||
|
developer, err = ks.NewAccount("")
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("Failed to create developer account: %v", err)
|
||||||
}
|
}
|
||||||
cfg.PowTest = true
|
|
||||||
}
|
}
|
||||||
|
if err := ks.Unlock(developer, ""); err != nil {
|
||||||
|
Fatalf("Failed to unlock developer account: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("Using developer account", "address", developer.Address)
|
||||||
|
|
||||||
|
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||||
|
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
|
||||||
|
cfg.GasPrice = big.NewInt(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO(fjl): move trie cache generations into config
|
// TODO(fjl): move trie cache generations into config
|
||||||
if gen := ctx.GlobalInt(TrieCacheGenFlag.Name); gen > 0 {
|
if gen := ctx.GlobalInt(TrieCacheGenFlag.Name); gen > 0 {
|
||||||
state.MaxTrieCacheGen = uint16(gen)
|
state.MaxTrieCacheGen = uint16(gen)
|
||||||
|
|
@ -1077,8 +1097,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis {
|
||||||
genesis = core.DefaultTestnetGenesisBlock()
|
genesis = core.DefaultTestnetGenesisBlock()
|
||||||
case ctx.GlobalBool(RinkebyFlag.Name):
|
case ctx.GlobalBool(RinkebyFlag.Name):
|
||||||
genesis = core.DefaultRinkebyGenesisBlock()
|
genesis = core.DefaultRinkebyGenesisBlock()
|
||||||
case ctx.GlobalBool(DevModeFlag.Name):
|
case ctx.GlobalBool(DeveloperFlag.Name):
|
||||||
genesis = core.DevGenesisBlock()
|
Fatalf("Developer chains are ephemeral")
|
||||||
}
|
}
|
||||||
return genesis
|
return genesis
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,7 @@ var (
|
||||||
|
|
||||||
type decError struct{ msg string }
|
type decError struct{ msg string }
|
||||||
|
|
||||||
func (err decError) Error() string {
|
func (err decError) Error() string { return err.msg }
|
||||||
return string(err.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode decodes a hex string with 0x prefix.
|
// Decode decodes a hex string with 0x prefix.
|
||||||
func Decode(input string) ([]byte, error) {
|
func Decode(input string) ([]byte, error) {
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ func (b *Uint64) UnmarshalText(input []byte) error {
|
||||||
return ErrSyntax
|
return ErrSyntax
|
||||||
}
|
}
|
||||||
dec *= 16
|
dec *= 16
|
||||||
dec += uint64(nib)
|
dec += nib
|
||||||
}
|
}
|
||||||
*b = Uint64(dec)
|
*b = Uint64(dec)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,11 @@ var (
|
||||||
|
|
||||||
// errUnauthorized is returned if a header is signed by a non-authorized entity.
|
// errUnauthorized is returned if a header is signed by a non-authorized entity.
|
||||||
errUnauthorized = errors.New("unauthorized")
|
errUnauthorized = errors.New("unauthorized")
|
||||||
|
|
||||||
|
// errWaitTransactions is returned if an empty block is attempted to be sealed
|
||||||
|
// on an instant chain (0 second period). It's important to refuse these as the
|
||||||
|
// block reward is zero, so an empty block just bloats the chain... fast.
|
||||||
|
errWaitTransactions = errors.New("waiting for transactions")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignerFn is a signer callback function to request a hash to be signed by a
|
// SignerFn is a signer callback function to request a hash to be signed by a
|
||||||
|
|
@ -211,9 +216,6 @@ func New(config *params.CliqueConfig, db ethdb.Database) *Clique {
|
||||||
if conf.Epoch == 0 {
|
if conf.Epoch == 0 {
|
||||||
conf.Epoch = epochLength
|
conf.Epoch = epochLength
|
||||||
}
|
}
|
||||||
if conf.Period == 0 {
|
|
||||||
conf.Period = blockPeriod
|
|
||||||
}
|
|
||||||
// Allocate the snapshot caches and create the engine
|
// Allocate the snapshot caches and create the engine
|
||||||
recents, _ := lru.NewARC(inmemorySnapshots)
|
recents, _ := lru.NewARC(inmemorySnapshots)
|
||||||
signatures, _ := lru.NewARC(inmemorySignatures)
|
signatures, _ := lru.NewARC(inmemorySignatures)
|
||||||
|
|
@ -599,6 +601,10 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
|
||||||
if number == 0 {
|
if number == 0 {
|
||||||
return nil, errUnknownBlock
|
return nil, errUnknownBlock
|
||||||
}
|
}
|
||||||
|
// For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
|
||||||
|
if c.config.Period == 0 && len(block.Transactions()) == 0 {
|
||||||
|
return nil, errWaitTransactions
|
||||||
|
}
|
||||||
// Don't hold the signer fields for the entire sealing procedure
|
// Don't hold the signer fields for the entire sealing procedure
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
signer, signFn := c.signer, c.signFn
|
signer, signFn := c.signer, c.signFn
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ type testerChainReader struct {
|
||||||
db ethdb.Database
|
db ethdb.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *testerChainReader) Config() *params.ChainConfig { return params.AllProtocolChanges }
|
func (r *testerChainReader) Config() *params.ChainConfig { return params.AllCliqueProtocolChanges }
|
||||||
func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") }
|
func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") }
|
||||||
func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") }
|
func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") }
|
||||||
func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") }
|
func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") }
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func cacheSize(block uint64) uint64 {
|
||||||
return cacheSizes[epoch]
|
return cacheSizes[epoch]
|
||||||
}
|
}
|
||||||
// No known cache size, calculate manually (sanity branch only)
|
// No known cache size, calculate manually (sanity branch only)
|
||||||
size := uint64(cacheInitBytes + cacheGrowthBytes*uint64(epoch) - hashBytes)
|
size := cacheInitBytes + cacheGrowthBytes*uint64(epoch) - hashBytes
|
||||||
for !new(big.Int).SetUint64(size / hashBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
|
for !new(big.Int).SetUint64(size / hashBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
|
||||||
size -= 2 * hashBytes
|
size -= 2 * hashBytes
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ func datasetSize(block uint64) uint64 {
|
||||||
return datasetSizes[epoch]
|
return datasetSizes[epoch]
|
||||||
}
|
}
|
||||||
// No known dataset size, calculate manually (sanity branch only)
|
// No known dataset size, calculate manually (sanity branch only)
|
||||||
size := uint64(datasetInitBytes + datasetGrowthBytes*uint64(epoch) - mixBytes)
|
size := datasetInitBytes + datasetGrowthBytes*uint64(epoch) - mixBytes
|
||||||
for !new(big.Int).SetUint64(size / mixBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
|
for !new(big.Int).SetUint64(size / mixBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
|
||||||
size -= 2 * mixBytes
|
size -= 2 * mixBytes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
|
||||||
t.Fatalf("failed to create node: %v", err)
|
t.Fatalf("failed to create node: %v", err)
|
||||||
}
|
}
|
||||||
ethConf := ð.Config{
|
ethConf := ð.Config{
|
||||||
Genesis: core.DevGenesisBlock(),
|
Genesis: core.DeveloperGenesisBlock(15, common.Address{}),
|
||||||
Etherbase: common.HexToAddress(testAddress),
|
Etherbase: common.HexToAddress(testAddress),
|
||||||
PowTest: true,
|
PowTest: true,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import "mortal";
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
import "https://github.com/ethereum/solidity/std/mortal.sol";
|
||||||
|
|
||||||
/// @title Chequebook for Ethereum micropayments
|
/// @title Chequebook for Ethereum micropayments
|
||||||
/// @author Daniel A. Nagy <daniel@ethdev.com>
|
/// @author Daniel A. Nagy <daniel@ethereum.org>
|
||||||
contract chequebook is mortal {
|
contract chequebook is mortal {
|
||||||
// Cumulative paid amount in wei to each beneficiary
|
// Cumulative paid amount in wei to each beneficiary
|
||||||
mapping (address => uint256) public sent;
|
mapping (address => uint256) public sent;
|
||||||
|
|
@ -21,26 +23,23 @@ contract chequebook is mortal {
|
||||||
uint8 sig_v, bytes32 sig_r, bytes32 sig_s) {
|
uint8 sig_v, bytes32 sig_r, bytes32 sig_s) {
|
||||||
// Check if the cheque is old.
|
// Check if the cheque is old.
|
||||||
// Only cheques that are more recent than the last cashed one are considered.
|
// Only cheques that are more recent than the last cashed one are considered.
|
||||||
if(amount <= sent[beneficiary]) return;
|
require(amount > sent[beneficiary]);
|
||||||
// Check the digital signature of the cheque.
|
// Check the digital signature of the cheque.
|
||||||
bytes32 hash = sha3(address(this), beneficiary, amount);
|
bytes32 hash = keccak256(address(this), beneficiary, amount);
|
||||||
if(owner != ecrecover(hash, sig_v, sig_r, sig_s)) return;
|
require(owner == ecrecover(hash, sig_v, sig_r, sig_s));
|
||||||
// Attempt sending the difference between the cumulative amount on the cheque
|
// Attempt sending the difference between the cumulative amount on the cheque
|
||||||
// and the cumulative amount on the last cashed cheque to beneficiary.
|
// and the cumulative amount on the last cashed cheque to beneficiary.
|
||||||
uint256 diff = amount - sent[beneficiary];
|
uint256 diff = amount - sent[beneficiary];
|
||||||
if (diff <= this.balance) {
|
if (diff <= this.balance) {
|
||||||
// update the cumulative amount before sending
|
// update the cumulative amount before sending
|
||||||
sent[beneficiary] = amount;
|
sent[beneficiary] = amount;
|
||||||
if (!beneficiary.send(diff)) {
|
beneficiary.transfer(diff);
|
||||||
// Upon failure to execute send, revert everything
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Upon failure, punish owner for writing a bounced cheque.
|
// Upon failure, punish owner for writing a bounced cheque.
|
||||||
// owner.sendToDebtorsPrison();
|
// owner.sendToDebtorsPrison();
|
||||||
Overdraft(owner);
|
Overdraft(owner);
|
||||||
// Compensate beneficiary.
|
// Compensate beneficiary.
|
||||||
suicide(beneficiary);
|
selfdestruct(beneficiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package bloombits
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -56,10 +57,16 @@ type partialMatches struct {
|
||||||
// Retrieval represents a request for retrieval task assignments for a given
|
// Retrieval represents a request for retrieval task assignments for a given
|
||||||
// bit with the given number of fetch elements, or a response for such a request.
|
// bit with the given number of fetch elements, or a response for such a request.
|
||||||
// It can also have the actual results set to be used as a delivery data struct.
|
// It can also have the actual results set to be used as a delivery data struct.
|
||||||
|
//
|
||||||
|
// The contest and error fields are used by the light client to terminate matching
|
||||||
|
// early if an error is enountered on some path of the pipeline.
|
||||||
type Retrieval struct {
|
type Retrieval struct {
|
||||||
Bit uint
|
Bit uint
|
||||||
Sections []uint64
|
Sections []uint64
|
||||||
Bitsets [][]byte
|
Bitsets [][]byte
|
||||||
|
|
||||||
|
Context context.Context
|
||||||
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is a pipelined system of schedulers and logic matchers which perform
|
// Matcher is a pipelined system of schedulers and logic matchers which perform
|
||||||
|
|
@ -137,7 +144,7 @@ func (m *Matcher) addScheduler(idx uint) {
|
||||||
// Start starts the matching process and returns a stream of bloom matches in
|
// Start starts the matching process and returns a stream of bloom matches in
|
||||||
// a given range of blocks. If there are no more matches in the range, the result
|
// a given range of blocks. If there are no more matches in the range, the result
|
||||||
// channel is closed.
|
// channel is closed.
|
||||||
func (m *Matcher) Start(begin, end uint64, results chan uint64) (*MatcherSession, error) {
|
func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uint64) (*MatcherSession, error) {
|
||||||
// Make sure we're not creating concurrent sessions
|
// Make sure we're not creating concurrent sessions
|
||||||
if atomic.SwapUint32(&m.running, 1) == 1 {
|
if atomic.SwapUint32(&m.running, 1) == 1 {
|
||||||
return nil, errors.New("matcher already running")
|
return nil, errors.New("matcher already running")
|
||||||
|
|
@ -149,6 +156,7 @@ func (m *Matcher) Start(begin, end uint64, results chan uint64) (*MatcherSession
|
||||||
matcher: m,
|
matcher: m,
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
kill: make(chan struct{}),
|
kill: make(chan struct{}),
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
for _, scheduler := range m.schedulers {
|
for _, scheduler := range m.schedulers {
|
||||||
scheduler.reset()
|
scheduler.reset()
|
||||||
|
|
@ -502,25 +510,34 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
|
||||||
type MatcherSession struct {
|
type MatcherSession struct {
|
||||||
matcher *Matcher
|
matcher *Matcher
|
||||||
|
|
||||||
|
closer sync.Once // Sync object to ensure we only ever close once
|
||||||
quit chan struct{} // Quit channel to request pipeline termination
|
quit chan struct{} // Quit channel to request pipeline termination
|
||||||
kill chan struct{} // Term channel to signal non-graceful forced shutdown
|
kill chan struct{} // Term channel to signal non-graceful forced shutdown
|
||||||
|
|
||||||
|
ctx context.Context // Context used by the light client to abort filtering
|
||||||
|
err atomic.Value // Global error to track retrieval failures deep in the chain
|
||||||
|
|
||||||
pend sync.WaitGroup
|
pend sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close stops the matching process and waits for all subprocesses to terminate
|
// Close stops the matching process and waits for all subprocesses to terminate
|
||||||
// before returning. The timeout may be used for graceful shutdown, allowing the
|
// before returning. The timeout may be used for graceful shutdown, allowing the
|
||||||
// currently running retrievals to complete before this time.
|
// currently running retrievals to complete before this time.
|
||||||
func (s *MatcherSession) Close(timeout time.Duration) {
|
func (s *MatcherSession) Close() {
|
||||||
// Bail out if the matcher is not running
|
s.closer.Do(func() {
|
||||||
select {
|
|
||||||
case <-s.quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
// Signal termination and wait for all goroutines to tear down
|
// Signal termination and wait for all goroutines to tear down
|
||||||
close(s.quit)
|
close(s.quit)
|
||||||
time.AfterFunc(timeout, func() { close(s.kill) })
|
time.AfterFunc(time.Second, func() { close(s.kill) })
|
||||||
s.pend.Wait()
|
s.pend.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure encountered during the matching session.
|
||||||
|
func (s *MatcherSession) Error() error {
|
||||||
|
if err := s.err.Load(); err != nil {
|
||||||
|
return err.(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllocateRetrieval assigns a bloom bit index to a client process that can either
|
// AllocateRetrieval assigns a bloom bit index to a client process that can either
|
||||||
|
|
@ -618,9 +635,13 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
|
||||||
|
|
||||||
case mux <- request:
|
case mux <- request:
|
||||||
// Retrieval accepted, something must arrive before we're aborting
|
// Retrieval accepted, something must arrive before we're aborting
|
||||||
request <- &Retrieval{Bit: bit, Sections: sections}
|
request <- &Retrieval{Bit: bit, Sections: sections, Context: s.ctx}
|
||||||
|
|
||||||
result := <-request
|
result := <-request
|
||||||
|
if result.Error != nil {
|
||||||
|
s.err.Store(result.Error)
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
s.DeliverSections(result.Bit, result.Sections, result.Bitsets)
|
s.DeliverSections(result.Bit, result.Sections, result.Bitsets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package bloombits
|
package bloombits
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -30,13 +31,13 @@ const testSectionSize = 4096
|
||||||
// Tests that wildcard filter rules (nil) can be specified and are handled well.
|
// Tests that wildcard filter rules (nil) can be specified and are handled well.
|
||||||
func TestMatcherWildcards(t *testing.T) {
|
func TestMatcherWildcards(t *testing.T) {
|
||||||
matcher := NewMatcher(testSectionSize, [][][]byte{
|
matcher := NewMatcher(testSectionSize, [][][]byte{
|
||||||
[][]byte{common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard
|
{common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard
|
||||||
[][]byte{common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()}, // Default hash is not a wildcard
|
{common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()}, // Default hash is not a wildcard
|
||||||
[][]byte{common.Hash{0x01}.Bytes()}, // Plain rule, sanity check
|
{common.Hash{0x01}.Bytes()}, // Plain rule, sanity check
|
||||||
[][]byte{common.Hash{0x01}.Bytes(), nil}, // Wildcard suffix, drop rule
|
{common.Hash{0x01}.Bytes(), nil}, // Wildcard suffix, drop rule
|
||||||
[][]byte{nil, common.Hash{0x01}.Bytes()}, // Wildcard prefix, drop rule
|
{nil, common.Hash{0x01}.Bytes()}, // Wildcard prefix, drop rule
|
||||||
[][]byte{nil, nil}, // Wildcard combo, drop rule
|
{nil, nil}, // Wildcard combo, drop rule
|
||||||
[][]byte{}, // Inited wildcard rule, drop rule
|
{}, // Inited wildcard rule, drop rule
|
||||||
nil, // Proper wildcard rule, drop rule
|
nil, // Proper wildcard rule, drop rule
|
||||||
})
|
})
|
||||||
if len(matcher.filters) != 3 {
|
if len(matcher.filters) != 3 {
|
||||||
|
|
@ -85,7 +86,7 @@ func TestWildcardMatcher(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeRandomIndexes generates a random filter system, composed on multiple filter
|
// makeRandomIndexes generates a random filter system, composed on multiple filter
|
||||||
// criteria, each having one bloom list component for the address and arbitrarilly
|
// criteria, each having one bloom list component for the address and arbitrarily
|
||||||
// many topic bloom list components.
|
// many topic bloom list components.
|
||||||
func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes {
|
func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes {
|
||||||
res := make([][]bloomIndexes, len(lengths))
|
res := make([][]bloomIndexes, len(lengths))
|
||||||
|
|
@ -144,7 +145,7 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, blocks uint64, intermitt
|
||||||
quit := make(chan struct{})
|
quit := make(chan struct{})
|
||||||
matches := make(chan uint64, 16)
|
matches := make(chan uint64, 16)
|
||||||
|
|
||||||
session, err := matcher.Start(0, blocks-1, matches)
|
session, err := matcher.Start(context.Background(), 0, blocks-1, matches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to stat matcher session: %v", err)
|
t.Fatalf("failed to stat matcher session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -163,13 +164,13 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, blocks uint64, intermitt
|
||||||
}
|
}
|
||||||
// If we're testing intermittent mode, abort and restart the pipeline
|
// If we're testing intermittent mode, abort and restart the pipeline
|
||||||
if intermittent {
|
if intermittent {
|
||||||
session.Close(time.Second)
|
session.Close()
|
||||||
close(quit)
|
close(quit)
|
||||||
|
|
||||||
quit = make(chan struct{})
|
quit = make(chan struct{})
|
||||||
matches = make(chan uint64, 16)
|
matches = make(chan uint64, 16)
|
||||||
|
|
||||||
session, err = matcher.Start(i+1, blocks-1, matches)
|
session, err = matcher.Start(context.Background(), i+1, blocks-1, matches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to stat matcher session: %v", err)
|
t.Fatalf("failed to stat matcher session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +184,7 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, blocks uint64, intermitt
|
||||||
t.Errorf("filter = %v blocks = %v intermittent = %v: expected closed channel, got #%v", filter, blocks, intermittent, match)
|
t.Errorf("filter = %v blocks = %v intermittent = %v: expected closed channel, got #%v", filter, blocks, intermittent, match)
|
||||||
}
|
}
|
||||||
// Clean up the session and ensure we match the expected retrieval count
|
// Clean up the session and ensure we match the expected retrieval count
|
||||||
session.Close(time.Second)
|
session.Close()
|
||||||
close(quit)
|
close(quit)
|
||||||
|
|
||||||
if retrievals != 0 && requested != retrievals {
|
if retrievals != 0 && requested != retrievals {
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) {
|
||||||
req.section, // Requested data
|
req.section, // Requested data
|
||||||
req.section, // Duplicated data (ensure it doesn't double close anything)
|
req.section, // Duplicated data (ensure it doesn't double close anything)
|
||||||
}, [][]byte{
|
}, [][]byte{
|
||||||
[]byte{},
|
{},
|
||||||
new(big.Int).SetUint64(req.section).Bytes(),
|
new(big.Int).SetUint64(req.section).Bytes(),
|
||||||
new(big.Int).SetUint64(req.section).Bytes(),
|
new(big.Int).SetUint64(req.section).Bytes(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import (
|
||||||
type ChainIndexerBackend interface {
|
type ChainIndexerBackend interface {
|
||||||
// Reset initiates the processing of a new chain segment, potentially terminating
|
// Reset initiates the processing of a new chain segment, potentially terminating
|
||||||
// any partially completed operations (in case of a reorg).
|
// any partially completed operations (in case of a reorg).
|
||||||
Reset(section uint64)
|
Reset(section uint64, prevHead common.Hash) error
|
||||||
|
|
||||||
// Process crunches through the next header in the chain segment. The caller
|
// Process crunches through the next header in the chain segment. The caller
|
||||||
// will ensure a sequential order of headers.
|
// will ensure a sequential order of headers.
|
||||||
|
|
@ -46,6 +46,15 @@ type ChainIndexerBackend interface {
|
||||||
Commit() error
|
Commit() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChainIndexerChain interface is used for connecting the indexer to a blockchain
|
||||||
|
type ChainIndexerChain interface {
|
||||||
|
// CurrentHeader retrieves the latest locally known header.
|
||||||
|
CurrentHeader() *types.Header
|
||||||
|
|
||||||
|
// SubscribeChainEvent subscribes to new head header notifications.
|
||||||
|
SubscribeChainEvent(ch chan<- ChainEvent) event.Subscription
|
||||||
|
}
|
||||||
|
|
||||||
// ChainIndexer does a post-processing job for equally sized sections of the
|
// ChainIndexer does a post-processing job for equally sized sections of the
|
||||||
// canonical chain (like BlooomBits and CHT structures). A ChainIndexer is
|
// canonical chain (like BlooomBits and CHT structures). A ChainIndexer is
|
||||||
// connected to the blockchain through the event system by starting a
|
// connected to the blockchain through the event system by starting a
|
||||||
|
|
@ -100,11 +109,27 @@ func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBacken
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddKnownSectionHead marks a new section head as known/processed if it is newer
|
||||||
|
// than the already known best section head
|
||||||
|
func (c *ChainIndexer) AddKnownSectionHead(section uint64, shead common.Hash) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
if section < c.storedSections {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.setSectionHead(section, shead)
|
||||||
|
c.setValidSections(section + 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Start creates a goroutine to feed chain head events into the indexer for
|
// Start creates a goroutine to feed chain head events into the indexer for
|
||||||
// cascading background processing. Children do not need to be started, they
|
// cascading background processing. Children do not need to be started, they
|
||||||
// are notified about new events by their parents.
|
// are notified about new events by their parents.
|
||||||
func (c *ChainIndexer) Start(currentHeader *types.Header, chainEventer func(ch chan<- ChainEvent) event.Subscription) {
|
func (c *ChainIndexer) Start(chain ChainIndexerChain) {
|
||||||
go c.eventLoop(currentHeader, chainEventer)
|
events := make(chan ChainEvent, 10)
|
||||||
|
sub := chain.SubscribeChainEvent(events)
|
||||||
|
|
||||||
|
go c.eventLoop(chain.CurrentHeader(), events, sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close tears down all goroutines belonging to the indexer and returns any error
|
// Close tears down all goroutines belonging to the indexer and returns any error
|
||||||
|
|
@ -147,12 +172,10 @@ func (c *ChainIndexer) Close() error {
|
||||||
// eventLoop is a secondary - optional - event loop of the indexer which is only
|
// eventLoop is a secondary - optional - event loop of the indexer which is only
|
||||||
// started for the outermost indexer to push chain head events into a processing
|
// started for the outermost indexer to push chain head events into a processing
|
||||||
// queue.
|
// queue.
|
||||||
func (c *ChainIndexer) eventLoop(currentHeader *types.Header, chainEventer func(ch chan<- ChainEvent) event.Subscription) {
|
func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainEvent, sub event.Subscription) {
|
||||||
// Mark the chain indexer as active, requiring an additional teardown
|
// Mark the chain indexer as active, requiring an additional teardown
|
||||||
atomic.StoreUint32(&c.active, 1)
|
atomic.StoreUint32(&c.active, 1)
|
||||||
|
|
||||||
events := make(chan ChainEvent, 10)
|
|
||||||
sub := chainEventer(events)
|
|
||||||
defer sub.Unsubscribe()
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
// Fire the initial new head event to start any outstanding processing
|
// Fire the initial new head event to start any outstanding processing
|
||||||
|
|
@ -178,7 +201,11 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, chainEventer func(
|
||||||
}
|
}
|
||||||
header := ev.Block.Header()
|
header := ev.Block.Header()
|
||||||
if header.ParentHash != prevHash {
|
if header.ParentHash != prevHash {
|
||||||
c.newHead(FindCommonAncestor(c.chainDb, prevHeader, header).Number.Uint64(), true)
|
// Reorg to the common ancestor (might not exist in light sync mode, skip reorg then)
|
||||||
|
// TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly?
|
||||||
|
if h := FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
||||||
|
c.newHead(h.Number.Uint64(), true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.newHead(header.Number.Uint64(), false)
|
c.newHead(header.Number.Uint64(), false)
|
||||||
|
|
||||||
|
|
@ -236,6 +263,7 @@ func (c *ChainIndexer) updateLoop() {
|
||||||
updating bool
|
updating bool
|
||||||
updated time.Time
|
updated time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case errc := <-c.quit:
|
case errc := <-c.quit:
|
||||||
|
|
@ -259,7 +287,7 @@ func (c *ChainIndexer) updateLoop() {
|
||||||
section := c.storedSections
|
section := c.storedSections
|
||||||
var oldHead common.Hash
|
var oldHead common.Hash
|
||||||
if section > 0 {
|
if section > 0 {
|
||||||
oldHead = c.sectionHead(section - 1)
|
oldHead = c.SectionHead(section - 1)
|
||||||
}
|
}
|
||||||
// Process the newly defined section in the background
|
// Process the newly defined section in the background
|
||||||
c.lock.Unlock()
|
c.lock.Unlock()
|
||||||
|
|
@ -270,7 +298,7 @@ func (c *ChainIndexer) updateLoop() {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
|
|
||||||
// If processing succeeded and no reorgs occcurred, mark the section completed
|
// If processing succeeded and no reorgs occcurred, mark the section completed
|
||||||
if err == nil && oldHead == c.sectionHead(section-1) {
|
if err == nil && oldHead == c.SectionHead(section-1) {
|
||||||
c.setSectionHead(section, newHead)
|
c.setSectionHead(section, newHead)
|
||||||
c.setValidSections(section + 1)
|
c.setValidSections(section + 1)
|
||||||
if c.storedSections == c.knownSections && updating {
|
if c.storedSections == c.knownSections && updating {
|
||||||
|
|
@ -311,7 +339,11 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||||
c.log.Trace("Processing new chain section", "section", section)
|
c.log.Trace("Processing new chain section", "section", section)
|
||||||
|
|
||||||
// Reset and partial processing
|
// Reset and partial processing
|
||||||
c.backend.Reset(section)
|
|
||||||
|
if err := c.backend.Reset(section, lastHead); err != nil {
|
||||||
|
c.setValidSections(0)
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ {
|
for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ {
|
||||||
hash := GetCanonicalHash(c.chainDb, number)
|
hash := GetCanonicalHash(c.chainDb, number)
|
||||||
|
|
@ -341,7 +373,7 @@ func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
return c.storedSections, c.storedSections*c.sectionSize - 1, c.sectionHead(c.storedSections - 1)
|
return c.storedSections, c.storedSections*c.sectionSize - 1, c.SectionHead(c.storedSections - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -381,9 +413,9 @@ func (c *ChainIndexer) setValidSections(sections uint64) {
|
||||||
c.storedSections = sections // needed if new > old
|
c.storedSections = sections // needed if new > old
|
||||||
}
|
}
|
||||||
|
|
||||||
// sectionHead retrieves the last block hash of a processed section from the
|
// SectionHead retrieves the last block hash of a processed section from the
|
||||||
// index database.
|
// index database.
|
||||||
func (c *ChainIndexer) sectionHead(section uint64) common.Hash {
|
func (c *ChainIndexer) SectionHead(section uint64) common.Hash {
|
||||||
var data [8]byte
|
var data [8]byte
|
||||||
binary.BigEndian.PutUint64(data[:], section)
|
binary.BigEndian.PutUint64(data[:], section)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
)
|
)
|
||||||
|
|
@ -208,9 +209,10 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 {
|
||||||
return b.stored * b.indexer.sectionSize
|
return b.stored * b.indexer.sectionSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testChainIndexBackend) Reset(section uint64) {
|
func (b *testChainIndexBackend) Reset(section uint64, prevHead common.Hash) error {
|
||||||
b.section = section
|
b.section = section
|
||||||
b.headerCnt = 0
|
b.headerCnt = 0
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testChainIndexBackend) Process(header *types.Header) {
|
func (b *testChainIndexBackend) Process(header *types.Header) {
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ func newCanonical(n int, full bool) (ethdb.Database, *BlockChain, error) {
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
genesis := gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
|
|
||||||
blockchain, _ := NewBlockChain(db, params.AllProtocolChanges, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), vm.Config{})
|
||||||
// Create and inject the requested chain
|
// Create and inject the requested chain
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return db, blockchain, nil
|
return db, blockchain, nil
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,9 @@ var (
|
||||||
preimageHitCounter = metrics.NewCounter("db/preimage/hits")
|
preimageHitCounter = metrics.NewCounter("db/preimage/hits")
|
||||||
)
|
)
|
||||||
|
|
||||||
// txLookupEntry is a positional metadata to help looking up the data content of
|
// TxLookupEntry is a positional metadata to help looking up the data content of
|
||||||
// a transaction or receipt given only its hash.
|
// a transaction or receipt given only its hash.
|
||||||
type txLookupEntry struct {
|
type TxLookupEntry struct {
|
||||||
BlockHash common.Hash
|
BlockHash common.Hash
|
||||||
BlockIndex uint64
|
BlockIndex uint64
|
||||||
Index uint64
|
Index uint64
|
||||||
|
|
@ -260,7 +260,7 @@ func GetTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64,
|
||||||
return common.Hash{}, 0, 0
|
return common.Hash{}, 0, 0
|
||||||
}
|
}
|
||||||
// Parse and return the contents of the lookup entry
|
// Parse and return the contents of the lookup entry
|
||||||
var entry txLookupEntry
|
var entry TxLookupEntry
|
||||||
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
||||||
log.Error("Invalid lookup entry RLP", "hash", hash, "err", err)
|
log.Error("Invalid lookup entry RLP", "hash", hash, "err", err)
|
||||||
return common.Hash{}, 0, 0
|
return common.Hash{}, 0, 0
|
||||||
|
|
@ -296,7 +296,7 @@ func GetTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, co
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil, common.Hash{}, 0, 0
|
return nil, common.Hash{}, 0, 0
|
||||||
}
|
}
|
||||||
var entry txLookupEntry
|
var entry TxLookupEntry
|
||||||
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
||||||
return nil, common.Hash{}, 0, 0
|
return nil, common.Hash{}, 0, 0
|
||||||
}
|
}
|
||||||
|
|
@ -332,14 +332,13 @@ func GetReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Has
|
||||||
|
|
||||||
// GetBloomBits retrieves the compressed bloom bit vector belonging to the given
|
// GetBloomBits retrieves the compressed bloom bit vector belonging to the given
|
||||||
// section and bit index from the.
|
// section and bit index from the.
|
||||||
func GetBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) []byte {
|
func GetBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) ([]byte, error) {
|
||||||
key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...)
|
key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(key[1:], uint16(bit))
|
binary.BigEndian.PutUint16(key[1:], uint16(bit))
|
||||||
binary.BigEndian.PutUint64(key[3:], section)
|
binary.BigEndian.PutUint64(key[3:], section)
|
||||||
|
|
||||||
bits, _ := db.Get(key)
|
return db.Get(key)
|
||||||
return bits
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteCanonicalHash stores the canonical hash for the given block number.
|
// WriteCanonicalHash stores the canonical hash for the given block number.
|
||||||
|
|
@ -465,7 +464,7 @@ func WriteBlockReceipts(db ethdb.Putter, hash common.Hash, number uint64, receip
|
||||||
func WriteTxLookupEntries(db ethdb.Putter, block *types.Block) error {
|
func WriteTxLookupEntries(db ethdb.Putter, block *types.Block) error {
|
||||||
// Iterate over each transaction and encode its metadata
|
// Iterate over each transaction and encode its metadata
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
entry := txLookupEntry{
|
entry := TxLookupEntry{
|
||||||
BlockHash: block.Hash(),
|
BlockHash: block.Hash(),
|
||||||
BlockIndex: block.NumberU64(),
|
BlockIndex: block.NumberU64(),
|
||||||
Index: uint64(i),
|
Index: uint64(i),
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ 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) {
|
||||||
if genesis != nil && genesis.Config == nil {
|
if genesis != nil && genesis.Config == nil {
|
||||||
return params.AllProtocolChanges, common.Hash{}, errGenesisNoConfig
|
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just commit the new block if there is no stored genesis block.
|
// Just commit the new block if there is no stored genesis block.
|
||||||
|
|
@ -216,7 +216,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||||
case ghash == params.TestnetGenesisHash:
|
case ghash == params.TestnetGenesisHash:
|
||||||
return params.TestnetChainConfig
|
return params.TestnetChainConfig
|
||||||
default:
|
default:
|
||||||
return params.AllProtocolChanges
|
return params.AllEthashProtocolChanges
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
||||||
}
|
}
|
||||||
config := g.Config
|
config := g.Config
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = params.AllProtocolChanges
|
config = params.AllEthashProtocolChanges
|
||||||
}
|
}
|
||||||
return block, WriteChainConfig(db, block.Hash(), config)
|
return block, WriteChainConfig(db, block.Hash(), config)
|
||||||
}
|
}
|
||||||
|
|
@ -342,14 +342,30 @@ func DefaultRinkebyGenesisBlock() *Genesis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevGenesisBlock returns the 'geth --dev' genesis block.
|
// DeveloperGenesisBlock returns the 'geth --dev' genesis block. Note, this must
|
||||||
func DevGenesisBlock() *Genesis {
|
// be seeded with the
|
||||||
|
func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
|
||||||
|
// Override the default period to the user requested one
|
||||||
|
config := *params.AllCliqueProtocolChanges
|
||||||
|
config.Clique.Period = period
|
||||||
|
|
||||||
|
// Assemble and return the genesis with the precompiles and faucet pre-funded
|
||||||
return &Genesis{
|
return &Genesis{
|
||||||
Config: params.AllProtocolChanges,
|
Config: &config,
|
||||||
Nonce: 42,
|
ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, 65)...),
|
||||||
GasLimit: 4712388,
|
GasLimit: 6283185,
|
||||||
Difficulty: big.NewInt(131072),
|
Difficulty: big.NewInt(1),
|
||||||
Alloc: decodePrealloc(devAllocData),
|
Alloc: map[common.Address]GenesisAccount{
|
||||||
|
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
|
||||||
|
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
|
||||||
|
common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD
|
||||||
|
common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity
|
||||||
|
common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp
|
||||||
|
common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
|
||||||
|
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||||
|
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
||||||
|
faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -65,7 +65,7 @@ func TestSetupGenesis(t *testing.T) {
|
||||||
return SetupGenesisBlock(db, new(Genesis))
|
return SetupGenesisBlock(db, new(Genesis))
|
||||||
},
|
},
|
||||||
wantErr: errGenesisNoConfig,
|
wantErr: errGenesisNoConfig,
|
||||||
wantConfig: params.AllProtocolChanges,
|
wantConfig: params.AllEthashProtocolChanges,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no block in DB, genesis == nil",
|
name: "no block in DB, genesis == nil",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,15 @@ import (
|
||||||
// into the journal, but no such file is currently open.
|
// into the journal, but no such file is currently open.
|
||||||
var errNoActiveJournal = errors.New("no active journal")
|
var errNoActiveJournal = errors.New("no active journal")
|
||||||
|
|
||||||
|
// devNull is a WriteCloser that just discards anything written into it. Its
|
||||||
|
// goal is to allow the transaction journal to write into a fake journal when
|
||||||
|
// loading transactions on startup without printing warnings due to no file
|
||||||
|
// being readt for write.
|
||||||
|
type devNull struct{}
|
||||||
|
|
||||||
|
func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil }
|
||||||
|
func (*devNull) Close() error { return nil }
|
||||||
|
|
||||||
// txJournal is a rotating log of transactions with the aim of storing locally
|
// txJournal is a rotating log of transactions with the aim of storing locally
|
||||||
// created transactions to allow non-executed ones to survive node restarts.
|
// created transactions to allow non-executed ones to survive node restarts.
|
||||||
type txJournal struct {
|
type txJournal struct {
|
||||||
|
|
@ -59,6 +68,10 @@ func (journal *txJournal) load(add func(*types.Transaction) error) error {
|
||||||
}
|
}
|
||||||
defer input.Close()
|
defer input.Close()
|
||||||
|
|
||||||
|
// Temporarily discard any journal additions (don't double add on load)
|
||||||
|
journal.writer = new(devNull)
|
||||||
|
defer func() { journal.writer = nil }()
|
||||||
|
|
||||||
// Inject all transactions from the journal into the pool
|
// Inject all transactions from the journal into the pool
|
||||||
stream := rlp.NewStream(input, 0)
|
stream := rlp.NewStream(input, 0)
|
||||||
total, dropped := 0, 0
|
total, dropped := 0, 0
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,10 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
|
||||||
old := l.txs.Get(tx.Nonce())
|
old := l.txs.Get(tx.Nonce())
|
||||||
if old != nil {
|
if old != nil {
|
||||||
threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
|
threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
|
||||||
if threshold.Cmp(tx.GasPrice()) >= 0 {
|
// Have to ensure that the new gas price is higher than the old gas
|
||||||
|
// price as well as checking the percentage threshold to ensure that
|
||||||
|
// this is accurate for low (Wei-level) gas price replacements
|
||||||
|
if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,16 @@ var (
|
||||||
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TxStatus is the current status of a transaction as seen py the pool.
|
||||||
|
type TxStatus uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
TxStatusUnknown TxStatus = iota
|
||||||
|
TxStatusQueued
|
||||||
|
TxStatusPending
|
||||||
|
TxStatusIncluded
|
||||||
|
)
|
||||||
|
|
||||||
// blockChain provides the state of blockchain and current gas limit to do
|
// blockChain provides the state of blockchain and current gas limit to do
|
||||||
// some pre checks in tx pool and event subscribers.
|
// some pre checks in tx pool and event subscribers.
|
||||||
type blockChain interface {
|
type blockChain interface {
|
||||||
|
|
@ -640,6 +650,10 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||||
pool.journalTx(from, tx)
|
pool.journalTx(from, tx)
|
||||||
|
|
||||||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
|
|
||||||
|
// We've directly injected a replacement transaction, notify subsystems
|
||||||
|
go pool.txFeed.Send(TxPreEvent{tx})
|
||||||
|
|
||||||
return old != nil, nil
|
return old != nil, nil
|
||||||
}
|
}
|
||||||
// New transaction isn't replacing a pending one, push into queue
|
// New transaction isn't replacing a pending one, push into queue
|
||||||
|
|
@ -729,6 +743,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
|
||||||
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
||||||
pool.beats[addr] = time.Now()
|
pool.beats[addr] = time.Now()
|
||||||
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
|
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
|
||||||
|
|
||||||
go pool.txFeed.Send(TxPreEvent{tx})
|
go pool.txFeed.Send(TxPreEvent{tx})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -749,14 +764,14 @@ func (pool *TxPool) AddRemote(tx *types.Transaction) error {
|
||||||
// AddLocals enqueues a batch of transactions into the pool if they are valid,
|
// AddLocals enqueues a batch of transactions into the pool if they are valid,
|
||||||
// marking the senders as a local ones in the mean time, ensuring they go around
|
// marking the senders as a local ones in the mean time, ensuring they go around
|
||||||
// the local pricing constraints.
|
// the local pricing constraints.
|
||||||
func (pool *TxPool) AddLocals(txs []*types.Transaction) error {
|
func (pool *TxPool) AddLocals(txs []*types.Transaction) []error {
|
||||||
return pool.addTxs(txs, !pool.config.NoLocals)
|
return pool.addTxs(txs, !pool.config.NoLocals)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRemotes enqueues a batch of transactions into the pool if they are valid.
|
// AddRemotes enqueues a batch of transactions into the pool if they are valid.
|
||||||
// If the senders are not among the locally tracked ones, full pricing constraints
|
// If the senders are not among the locally tracked ones, full pricing constraints
|
||||||
// will apply.
|
// will apply.
|
||||||
func (pool *TxPool) AddRemotes(txs []*types.Transaction) error {
|
func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||||
return pool.addTxs(txs, false)
|
return pool.addTxs(txs, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -779,7 +794,7 @@ func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTxs attempts to queue a batch of transactions if they are valid.
|
// addTxs attempts to queue a batch of transactions if they are valid.
|
||||||
func (pool *TxPool) addTxs(txs []*types.Transaction, local bool) error {
|
func (pool *TxPool) addTxs(txs []*types.Transaction, local bool) []error {
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
defer pool.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -788,11 +803,14 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local bool) error {
|
||||||
|
|
||||||
// addTxsLocked attempts to queue a batch of transactions if they are valid,
|
// addTxsLocked attempts to queue a batch of transactions if they are valid,
|
||||||
// whilst assuming the transaction pool lock is already held.
|
// whilst assuming the transaction pool lock is already held.
|
||||||
func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) error {
|
func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) []error {
|
||||||
// Add the batch of transaction, tracking the accepted ones
|
// Add the batch of transaction, tracking the accepted ones
|
||||||
dirty := make(map[common.Address]struct{})
|
dirty := make(map[common.Address]struct{})
|
||||||
for _, tx := range txs {
|
errs := make([]error, len(txs))
|
||||||
if replace, err := pool.add(tx, local); err == nil {
|
|
||||||
|
for i, tx := range txs {
|
||||||
|
var replace bool
|
||||||
|
if replace, errs[i] = pool.add(tx, local); errs[i] == nil {
|
||||||
if !replace {
|
if !replace {
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
dirty[from] = struct{}{}
|
dirty[from] = struct{}{}
|
||||||
|
|
@ -802,12 +820,32 @@ func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) error {
|
||||||
// Only reprocess the internal state if something was actually added
|
// Only reprocess the internal state if something was actually added
|
||||||
if len(dirty) > 0 {
|
if len(dirty) > 0 {
|
||||||
addrs := make([]common.Address, 0, len(dirty))
|
addrs := make([]common.Address, 0, len(dirty))
|
||||||
for addr, _ := range dirty {
|
for addr := range dirty {
|
||||||
addrs = append(addrs, addr)
|
addrs = append(addrs, addr)
|
||||||
}
|
}
|
||||||
pool.promoteExecutables(addrs)
|
pool.promoteExecutables(addrs)
|
||||||
}
|
}
|
||||||
return nil
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status returns the status (unknown/pending/queued) of a batch of transactions
|
||||||
|
// identified by their hashes.
|
||||||
|
func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||||
|
pool.mu.RLock()
|
||||||
|
defer pool.mu.RUnlock()
|
||||||
|
|
||||||
|
status := make([]TxStatus, len(hashes))
|
||||||
|
for i, hash := range hashes {
|
||||||
|
if tx := pool.all[hash]; tx != nil {
|
||||||
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
|
if pool.pending[from].txs.items[tx.Nonce()] != nil {
|
||||||
|
status[i] = TxStatusPending
|
||||||
|
} else {
|
||||||
|
status[i] = TxStatusQueued
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a transaction if it is contained in the pool
|
// Get returns a transaction if it is contained in the pool
|
||||||
|
|
@ -869,7 +907,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
// Gather all the accounts potentially needing updates
|
// Gather all the accounts potentially needing updates
|
||||||
if accounts == nil {
|
if accounts == nil {
|
||||||
accounts = make([]common.Address, 0, len(pool.queue))
|
accounts = make([]common.Address, 0, len(pool.queue))
|
||||||
for addr, _ := range pool.queue {
|
for addr := range pool.queue {
|
||||||
accounts = append(accounts, addr)
|
accounts = append(accounts, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||||
for addr, txs := range pool.pending {
|
for addr, txs := range pool.pending {
|
||||||
// Find the last transaction
|
// Find the last transaction
|
||||||
var last uint64
|
var last uint64
|
||||||
for nonce, _ := range txs.txs.items {
|
for nonce := range txs.txs.items {
|
||||||
if last < nonce {
|
if last < nonce {
|
||||||
last = nonce
|
last = nonce
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +117,28 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateEvents checks that the correct number of transaction addition events
|
||||||
|
// were fired on the pool's event feed.
|
||||||
|
func validateEvents(events chan TxPreEvent, count int) error {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
select {
|
||||||
|
case <-events:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
return fmt.Errorf("event #%d not fired", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case tx := <-events:
|
||||||
|
return fmt.Errorf("more than %d events fired: %v", count, tx.Tx)
|
||||||
|
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
// This branch should be "default", but it's a data race between goroutines,
|
||||||
|
// reading the event channel and pushng into it, so better wait a bit ensuring
|
||||||
|
// really nothing gets injected.
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func deriveSender(tx *types.Transaction) (common.Address, error) {
|
func deriveSender(tx *types.Transaction) (common.Address, error) {
|
||||||
return types.Sender(types.HomesteadSigner{}, tx)
|
return types.Sender(types.HomesteadSigner{}, tx)
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +171,9 @@ func (c *testChain) State() (*state.StateDB, error) {
|
||||||
// This test simulates a scenario where a new block is imported during a
|
// This test simulates a scenario where a new block is imported during a
|
||||||
// state reset and tests whether the pending state is in sync with the
|
// state reset and tests whether the pending state is in sync with the
|
||||||
// block head event that initiated the resetState().
|
// block head event that initiated the resetState().
|
||||||
func TestStateChangeDuringPoolReset(t *testing.T) {
|
func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
db, _ = ethdb.NewMemDatabase()
|
db, _ = ethdb.NewMemDatabase()
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
|
|
@ -201,6 +225,8 @@ func TestStateChangeDuringPoolReset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidTransactions(t *testing.T) {
|
func TestInvalidTransactions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
|
@ -236,6 +262,8 @@ func TestInvalidTransactions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactionQueue(t *testing.T) {
|
func TestTransactionQueue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
|
@ -287,7 +315,9 @@ func TestTransactionQueue(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNegativeValue(t *testing.T) {
|
func TestTransactionNegativeValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
|
@ -300,6 +330,8 @@ func TestNegativeValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactionChainFork(t *testing.T) {
|
func TestTransactionChainFork(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
|
@ -328,6 +360,8 @@ func TestTransactionChainFork(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactionDoubleNonce(t *testing.T) {
|
func TestTransactionDoubleNonce(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
|
@ -376,7 +410,9 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingNonce(t *testing.T) {
|
func TestTransactionMissingNonce(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
|
@ -398,6 +434,8 @@ func TestMissingNonce(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactionNonceRecovery(t *testing.T) {
|
func TestTransactionNonceRecovery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
const n = 10
|
const n = 10
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
@ -422,6 +460,8 @@ func TestTransactionNonceRecovery(t *testing.T) {
|
||||||
// Tests that if an account runs out of funds, any pending and queued transactions
|
// Tests that if an account runs out of funds, any pending and queued transactions
|
||||||
// are dropped.
|
// are dropped.
|
||||||
func TestTransactionDropping(t *testing.T) {
|
func TestTransactionDropping(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create a test account and fund it
|
// Create a test account and fund it
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
@ -515,6 +555,8 @@ func TestTransactionDropping(t *testing.T) {
|
||||||
// of fund), all consecutive (still valid, but not executable) transactions are
|
// of fund), all consecutive (still valid, but not executable) transactions are
|
||||||
// postponed back into the future queue to prevent broadcasting them.
|
// postponed back into the future queue to prevent broadcasting them.
|
||||||
func TestTransactionPostponing(t *testing.T) {
|
func TestTransactionPostponing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create a test account and fund it
|
// Create a test account and fund it
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
@ -586,9 +628,68 @@ func TestTransactionPostponing(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that if the transaction pool has both executable and non-executable
|
||||||
|
// transactions from an origin account, filling the nonce gap moves all queued
|
||||||
|
// ones into the pending pool.
|
||||||
|
func TestTransactionGapFilling(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create a test account and fund it
|
||||||
|
pool, key := setupTxPool()
|
||||||
|
defer pool.Stop()
|
||||||
|
|
||||||
|
account, _ := deriveSender(transaction(0, big.NewInt(0), key))
|
||||||
|
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||||
|
|
||||||
|
// Keep track of transaction events to ensure all executables get announced
|
||||||
|
events := make(chan TxPreEvent, testTxPoolConfig.AccountQueue+5)
|
||||||
|
sub := pool.txFeed.Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
// Create a pending and a queued transaction with a nonce-gap in between
|
||||||
|
if err := pool.AddRemote(transaction(0, big.NewInt(100000), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add pending transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.AddRemote(transaction(2, big.NewInt(100000), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add queued transaction: %v", err)
|
||||||
|
}
|
||||||
|
pending, queued := pool.Stats()
|
||||||
|
if pending != 1 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
|
||||||
|
}
|
||||||
|
if queued != 1 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||||
|
}
|
||||||
|
if err := validateEvents(events, 1); err != nil {
|
||||||
|
t.Fatalf("original event firing failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
|
}
|
||||||
|
// Fill the nonce gap and ensure all transactions become pending
|
||||||
|
if err := pool.AddRemote(transaction(1, big.NewInt(100000), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add gapped transaction: %v", err)
|
||||||
|
}
|
||||||
|
pending, queued = pool.Stats()
|
||||||
|
if pending != 3 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||||
|
}
|
||||||
|
if queued != 0 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||||
|
}
|
||||||
|
if err := validateEvents(events, 2); err != nil {
|
||||||
|
t.Fatalf("gap-filling event firing failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that if the transaction count belonging to a single account goes above
|
// Tests that if the transaction count belonging to a single account goes above
|
||||||
// some threshold, the higher transactions are dropped to prevent DOS attacks.
|
// some threshold, the higher transactions are dropped to prevent DOS attacks.
|
||||||
func TestTransactionQueueAccountLimiting(t *testing.T) {
|
func TestTransactionQueueAccountLimiting(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create a test account and fund it
|
// Create a test account and fund it
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
@ -632,6 +733,8 @@ func TestTransactionQueueGlobalLimitingNoLocals(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -782,6 +885,8 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||||
// above some threshold, as long as the transactions are executable, they are
|
// above some threshold, as long as the transactions are executable, they are
|
||||||
// accepted.
|
// accepted.
|
||||||
func TestTransactionPendingLimiting(t *testing.T) {
|
func TestTransactionPendingLimiting(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create a test account and fund it
|
// Create a test account and fund it
|
||||||
pool, key := setupTxPool()
|
pool, key := setupTxPool()
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
@ -789,6 +894,11 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||||
account, _ := deriveSender(transaction(0, big.NewInt(0), key))
|
account, _ := deriveSender(transaction(0, big.NewInt(0), key))
|
||||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||||
|
|
||||||
|
// Keep track of transaction events to ensure all executables get announced
|
||||||
|
events := make(chan TxPreEvent, testTxPoolConfig.AccountQueue+5)
|
||||||
|
sub := pool.txFeed.Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
// Keep queuing up transactions and make sure all above a limit are dropped
|
// Keep queuing up transactions and make sure all above a limit are dropped
|
||||||
for i := uint64(0); i < testTxPoolConfig.AccountQueue+5; i++ {
|
for i := uint64(0); i < testTxPoolConfig.AccountQueue+5; i++ {
|
||||||
if err := pool.AddRemote(transaction(i, big.NewInt(100000), key)); err != nil {
|
if err := pool.AddRemote(transaction(i, big.NewInt(100000), key)); err != nil {
|
||||||
|
|
@ -804,6 +914,12 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||||
if len(pool.all) != int(testTxPoolConfig.AccountQueue+5) {
|
if len(pool.all) != int(testTxPoolConfig.AccountQueue+5) {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue+5)
|
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue+5)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil {
|
||||||
|
t.Fatalf("event firing failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the transaction limits are enforced the same way irrelevant whether
|
// Tests that the transaction limits are enforced the same way irrelevant whether
|
||||||
|
|
@ -812,6 +928,8 @@ func TestTransactionQueueLimitingEquivalency(t *testing.T) { testTransactionLi
|
||||||
func TestTransactionPendingLimitingEquivalency(t *testing.T) { testTransactionLimitingEquivalency(t, 0) }
|
func TestTransactionPendingLimitingEquivalency(t *testing.T) { testTransactionLimitingEquivalency(t, 0) }
|
||||||
|
|
||||||
func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
|
func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Add a batch of transactions to a pool one by one
|
// Add a batch of transactions to a pool one by one
|
||||||
pool1, key1 := setupTxPool()
|
pool1, key1 := setupTxPool()
|
||||||
defer pool1.Stop()
|
defer pool1.Stop()
|
||||||
|
|
@ -859,6 +977,8 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
|
||||||
// some hard threshold, the higher transactions are dropped to prevent DOS
|
// some hard threshold, the higher transactions are dropped to prevent DOS
|
||||||
// attacks.
|
// attacks.
|
||||||
func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -904,6 +1024,8 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||||
|
|
||||||
// Tests that if transactions start being capped, transactions are also removed from 'all'
|
// Tests that if transactions start being capped, transactions are also removed from 'all'
|
||||||
func TestTransactionCapClearsFromAll(t *testing.T) {
|
func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -937,6 +1059,8 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||||
// some hard threshold, if they are under the minimum guaranteed slot count then
|
// some hard threshold, if they are under the minimum guaranteed slot count then
|
||||||
// the transactions are still kept.
|
// the transactions are still kept.
|
||||||
func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -984,6 +1108,8 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||||
//
|
//
|
||||||
// Note, local transactions are never allowed to be dropped.
|
// Note, local transactions are never allowed to be dropped.
|
||||||
func TestTransactionPoolRepricing(t *testing.T) {
|
func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the pricing enforcement with
|
// Create the pool to test the pricing enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -992,6 +1118,11 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
// Keep track of transaction events to ensure all executables get announced
|
||||||
|
events := make(chan TxPreEvent, 32)
|
||||||
|
sub := pool.txFeed.Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
// Create a number of test accounts and fund them
|
// Create a number of test accounts and fund them
|
||||||
keys := make([]*ecdsa.PrivateKey, 3)
|
keys := make([]*ecdsa.PrivateKey, 3)
|
||||||
for i := 0; i < len(keys); i++ {
|
for i := 0; i < len(keys); i++ {
|
||||||
|
|
@ -1022,6 +1153,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
if queued != 3 {
|
if queued != 3 {
|
||||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 4); err != nil {
|
||||||
|
t.Fatalf("original event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1035,6 +1169,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
if queued != 3 {
|
if queued != 3 {
|
||||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 0); err != nil {
|
||||||
|
t.Fatalf("reprice event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1045,6 +1182,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1])); err != ErrUnderpriced {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1])); err != ErrUnderpriced {
|
||||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 0); err != nil {
|
||||||
|
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1056,6 +1196,9 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
if pending, _ = pool.Stats(); pending != 3 {
|
if pending, _ = pool.Stats(); pending != 3 {
|
||||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 1); err != nil {
|
||||||
|
t.Fatalf("post-reprice local event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1064,6 +1207,8 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
// Tests that setting the transaction pool gas price to a higher value does not
|
// Tests that setting the transaction pool gas price to a higher value does not
|
||||||
// remove local transactions.
|
// remove local transactions.
|
||||||
func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the pricing enforcement with
|
// Create the pool to test the pricing enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -1125,6 +1270,8 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||||
//
|
//
|
||||||
// Note, local transactions are never allowed to be dropped.
|
// Note, local transactions are never allowed to be dropped.
|
||||||
func TestTransactionPoolUnderpricing(t *testing.T) {
|
func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the pricing enforcement with
|
// Create the pool to test the pricing enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -1137,6 +1284,11 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
pool := NewTxPool(config, params.TestChainConfig, blockchain)
|
pool := NewTxPool(config, params.TestChainConfig, blockchain)
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
// Keep track of transaction events to ensure all executables get announced
|
||||||
|
events := make(chan TxPreEvent, 32)
|
||||||
|
sub := pool.txFeed.Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
// Create a number of test accounts and fund them
|
// Create a number of test accounts and fund them
|
||||||
keys := make([]*ecdsa.PrivateKey, 3)
|
keys := make([]*ecdsa.PrivateKey, 3)
|
||||||
for i := 0; i < len(keys); i++ {
|
for i := 0; i < len(keys); i++ {
|
||||||
|
|
@ -1164,6 +1316,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
if queued != 1 {
|
if queued != 1 {
|
||||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 3); err != nil {
|
||||||
|
t.Fatalf("original event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1188,6 +1343,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
if queued != 2 {
|
if queued != 2 {
|
||||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 2); err != nil {
|
||||||
|
t.Fatalf("additional event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1203,6 +1361,9 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
if queued != 2 {
|
if queued != 2 {
|
||||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 1); err != nil {
|
||||||
|
t.Fatalf("local event firing failed: %v", err)
|
||||||
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1211,6 +1372,8 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
// Tests that the pool rejects replacement transactions that don't meet the minimum
|
// Tests that the pool rejects replacement transactions that don't meet the minimum
|
||||||
// price bump required.
|
// price bump required.
|
||||||
func TestTransactionReplacement(t *testing.T) {
|
func TestTransactionReplacement(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create the pool to test the pricing enforcement with
|
// Create the pool to test the pricing enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
|
@ -1219,6 +1382,11 @@ func TestTransactionReplacement(t *testing.T) {
|
||||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||||
defer pool.Stop()
|
defer pool.Stop()
|
||||||
|
|
||||||
|
// Keep track of transaction events to ensure all executables get announced
|
||||||
|
events := make(chan TxPreEvent, 32)
|
||||||
|
sub := pool.txFeed.Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
// Create a test account to add transactions with
|
// Create a test account to add transactions with
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||||
|
|
@ -1236,35 +1404,45 @@ func TestTransactionReplacement(t *testing.T) {
|
||||||
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(2), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(2), key)); err != nil {
|
||||||
t.Fatalf("failed to replace original cheap pending transaction: %v", err)
|
t.Fatalf("failed to replace original cheap pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 2); err != nil {
|
||||||
|
t.Fatalf("cheap replacement event firing failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(price), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(threshold), key)); err != ErrReplaceUnderpriced {
|
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100001), big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced {
|
||||||
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(threshold+1), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(0, big.NewInt(100000), big.NewInt(threshold), key)); err != nil {
|
||||||
t.Fatalf("failed to replace original proper pending transaction: %v", err)
|
t.Fatalf("failed to replace original proper pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := validateEvents(events, 2); err != nil {
|
||||||
|
t.Fatalf("proper replacement event firing failed: %v", err)
|
||||||
|
}
|
||||||
// Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
// Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original queued transaction: %v", err)
|
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100001), big.NewInt(1), key)); err != ErrReplaceUnderpriced {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100001), big.NewInt(1), key)); err != ErrReplaceUnderpriced {
|
||||||
t.Fatalf("original queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(2), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(2), key)); err != nil {
|
||||||
t.Fatalf("failed to replace original queued transaction: %v", err)
|
t.Fatalf("failed to replace original cheap queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(price), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original queued transaction: %v", err)
|
t.Fatalf("failed to add original proper queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100001), big.NewInt(threshold), key)); err != ErrReplaceUnderpriced {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100001), big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced {
|
||||||
t.Fatalf("original queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(threshold+1), key)); err != nil {
|
if err := pool.AddRemote(pricedTransaction(2, big.NewInt(100000), big.NewInt(threshold), key)); err != nil {
|
||||||
t.Fatalf("failed to replace original queued transaction: %v", err)
|
t.Fatalf("failed to replace original proper queued transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateEvents(events, 0); err != nil {
|
||||||
|
t.Fatalf("queued replacement event firing failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := validateTxPoolInternals(pool); err != nil {
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
|
|
@ -1277,6 +1455,8 @@ func TestTransactionJournaling(t *testing.T) { testTransactionJournaling
|
||||||
func TestTransactionJournalingNoLocals(t *testing.T) { testTransactionJournaling(t, true) }
|
func TestTransactionJournalingNoLocals(t *testing.T) { testTransactionJournaling(t, true) }
|
||||||
|
|
||||||
func testTransactionJournaling(t *testing.T, nolocals bool) {
|
func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Create a temporary file for the journal
|
// Create a temporary file for the journal
|
||||||
file, err := ioutil.TempFile("", "")
|
file, err := ioutil.TempFile("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1335,6 +1515,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
pool.Stop()
|
pool.Stop()
|
||||||
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
|
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
|
||||||
blockchain = &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
|
blockchain = &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
|
||||||
|
|
||||||
pool = NewTxPool(config, params.TestChainConfig, blockchain)
|
pool = NewTxPool(config, params.TestChainConfig, blockchain)
|
||||||
|
|
||||||
pending, queued = pool.Stats()
|
pending, queued = pool.Stats()
|
||||||
|
|
@ -1358,6 +1539,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
pool.lockedReset(nil, nil)
|
pool.lockedReset(nil, nil)
|
||||||
time.Sleep(2 * config.Rejournal)
|
time.Sleep(2 * config.Rejournal)
|
||||||
pool.Stop()
|
pool.Stop()
|
||||||
|
|
||||||
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
|
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
|
||||||
blockchain = &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
|
blockchain = &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
|
||||||
pool = NewTxPool(config, params.TestChainConfig, blockchain)
|
pool = NewTxPool(config, params.TestChainConfig, blockchain)
|
||||||
|
|
|
||||||
|
|
@ -161,30 +161,27 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
|
||||||
|
|
||||||
if in.cfg.Debug {
|
if in.cfg.Debug {
|
||||||
logged = false
|
logged = false
|
||||||
pcCopy = uint64(pc)
|
pcCopy = pc
|
||||||
gasCopy = uint64(contract.Gas)
|
gasCopy = contract.Gas
|
||||||
stackCopy = newstack()
|
stackCopy = newstack()
|
||||||
for _, val := range stack.data {
|
for _, val := range stack.data {
|
||||||
stackCopy.push(val)
|
stackCopy.push(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the operation from the jump table matching the opcode
|
// Get the operation from the jump table matching the opcode and validate the
|
||||||
|
// stack and make sure there enough stack items available to perform the operation
|
||||||
operation := in.cfg.JumpTable[op]
|
operation := in.cfg.JumpTable[op]
|
||||||
if err := in.enforceRestrictions(op, operation, stack); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the op is invalid abort the process and return an error
|
|
||||||
if !operation.valid {
|
if !operation.valid {
|
||||||
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
|
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the stack and make sure there enough stack items available
|
|
||||||
// to perform the operation
|
|
||||||
if err := operation.validateStack(stack); err != nil {
|
if err := operation.validateStack(stack); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// If the operation is valid, enforce and write restrictions
|
||||||
|
if err := in.enforceRestrictions(op, operation, stack); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var memorySize uint64
|
var memorySize uint64
|
||||||
// calculate the new memory size and expand the memory to fit
|
// calculate the new memory size and expand the memory to fit
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type LesServer interface {
|
||||||
Start(srvr *p2p.Server)
|
Start(srvr *p2p.Server)
|
||||||
Stop()
|
Stop()
|
||||||
Protocols() []p2p.Protocol
|
Protocols() []p2p.Protocol
|
||||||
|
SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ethereum implements the Ethereum full node service.
|
// Ethereum implements the Ethereum full node service.
|
||||||
|
|
@ -95,6 +96,7 @@ type Ethereum struct {
|
||||||
|
|
||||||
func (s *Ethereum) AddLesServer(ls LesServer) {
|
func (s *Ethereum) AddLesServer(ls LesServer) {
|
||||||
s.lesServer = ls
|
s.lesServer = ls
|
||||||
|
ls.SetBloomBitsIndexer(s.bloomIndexer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Ethereum object (including the
|
// New creates a new Ethereum object (including the
|
||||||
|
|
@ -154,7 +156,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||||
eth.blockchain.SetHead(compat.RewindTo)
|
eth.blockchain.SetHead(compat.RewindTo)
|
||||||
core.WriteChainConfig(chainDb, genesisHash, chainConfig)
|
core.WriteChainConfig(chainDb, genesisHash, chainConfig)
|
||||||
}
|
}
|
||||||
eth.bloomIndexer.Start(eth.blockchain.CurrentHeader(), eth.blockchain.SubscribeChainEvent)
|
eth.bloomIndexer.Start(eth.blockchain)
|
||||||
|
|
||||||
if config.TxPool.Journal != "" {
|
if config.TxPool.Journal != "" {
|
||||||
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
|
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
|
||||||
|
|
|
||||||
|
|
@ -58,15 +58,18 @@ func (eth *Ethereum) startBloomHandlers() {
|
||||||
|
|
||||||
case request := <-eth.bloomRequests:
|
case request := <-eth.bloomRequests:
|
||||||
task := <-request
|
task := <-request
|
||||||
|
|
||||||
task.Bitsets = make([][]byte, len(task.Sections))
|
task.Bitsets = make([][]byte, len(task.Sections))
|
||||||
for i, section := range task.Sections {
|
for i, section := range task.Sections {
|
||||||
head := core.GetCanonicalHash(eth.chainDb, (section+1)*params.BloomBitsBlocks-1)
|
head := core.GetCanonicalHash(eth.chainDb, (section+1)*params.BloomBitsBlocks-1)
|
||||||
blob, err := bitutil.DecompressBytes(core.GetBloomBits(eth.chainDb, task.Bit, section, head), int(params.BloomBitsBlocks)/8)
|
if compVector, err := core.GetBloomBits(eth.chainDb, task.Bit, section, head); err == nil {
|
||||||
if err != nil {
|
if blob, err := bitutil.DecompressBytes(compVector, int(params.BloomBitsBlocks)/8); err == nil {
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
task.Bitsets[i] = blob
|
task.Bitsets[i] = blob
|
||||||
|
} else {
|
||||||
|
task.Error = err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
task.Error = err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
request <- task
|
request <- task
|
||||||
}
|
}
|
||||||
|
|
@ -111,12 +114,10 @@ func NewBloomIndexer(db ethdb.Database, size uint64) *core.ChainIndexer {
|
||||||
|
|
||||||
// Reset implements core.ChainIndexerBackend, starting a new bloombits index
|
// Reset implements core.ChainIndexerBackend, starting a new bloombits index
|
||||||
// section.
|
// section.
|
||||||
func (b *BloomIndexer) Reset(section uint64) {
|
func (b *BloomIndexer) Reset(section uint64, lastSectionHead common.Hash) error {
|
||||||
gen, err := bloombits.NewGenerator(uint(b.size))
|
gen, err := bloombits.NewGenerator(uint(b.size))
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
b.gen, b.section, b.head = gen, section, common.Hash{}
|
b.gen, b.section, b.head = gen, section, common.Hash{}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements core.ChainIndexerBackend, adding a new header's bloom into
|
// Process implements core.ChainIndexerBackend, adding a new header's bloom into
|
||||||
|
|
|
||||||
|
|
@ -708,7 +708,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
||||||
ttl := d.requestTTL()
|
ttl := d.requestTTL()
|
||||||
timeout := time.After(ttl)
|
timeout := time.After(ttl)
|
||||||
|
|
||||||
go p.peer.RequestHeadersByNumber(uint64(check), 1, 0, false)
|
go p.peer.RequestHeadersByNumber(check, 1, 0, false)
|
||||||
|
|
||||||
// Wait until a reply arrives to this request
|
// Wait until a reply arrives to this request
|
||||||
for arrived := false; !arrived; {
|
for arrived := false; !arrived; {
|
||||||
|
|
@ -1518,7 +1518,7 @@ func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, i
|
||||||
func (d *Downloader) qosTuner() {
|
func (d *Downloader) qosTuner() {
|
||||||
for {
|
for {
|
||||||
// Retrieve the current median RTT and integrate into the previoust target RTT
|
// Retrieve the current median RTT and integrate into the previoust target RTT
|
||||||
rtt := time.Duration(float64(1-qosTuningImpact)*float64(atomic.LoadUint64(&d.rttEstimate)) + qosTuningImpact*float64(d.peers.medianRTT()))
|
rtt := time.Duration((1-qosTuningImpact)*float64(atomic.LoadUint64(&d.rttEstimate)) + qosTuningImpact*float64(d.peers.medianRTT()))
|
||||||
atomic.StoreUint64(&d.rttEstimate, uint64(rtt))
|
atomic.StoreUint64(&d.rttEstimate, uint64(rtt))
|
||||||
|
|
||||||
// A new RTT cycle passed, increase our confidence in the estimated RTT
|
// A new RTT cycle passed, increase our confidence in the estimated RTT
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func (p *FakePeer) RequestHeadersByHash(hash common.Hash, amount int, skip int,
|
||||||
number := origin.Number.Uint64()
|
number := origin.Number.Uint64()
|
||||||
headers = append(headers, origin)
|
headers = append(headers, origin)
|
||||||
if reverse {
|
if reverse {
|
||||||
for i := 0; i < int(skip)+1; i++ {
|
for i := 0; i <= skip; i++ {
|
||||||
if header := p.hc.GetHeader(hash, number); header != nil {
|
if header := p.hc.GetHeader(hash, number); header != nil {
|
||||||
hash = header.ParentHash
|
hash = header.ParentHash
|
||||||
number--
|
number--
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ func BenchmarkNoBloomBits(b *testing.B) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
mux := new(event.TypeMux)
|
mux := new(event.TypeMux)
|
||||||
backend := &testBackend{mux, db, 0, new(event.Feed), new(event.Feed), new(event.Feed), new(event.Feed)}
|
backend := &testBackend{mux, db, 0, new(event.Feed), new(event.Feed), new(event.Feed), new(event.Feed)}
|
||||||
filter := New(backend, 0, int64(headNum), []common.Address{common.Address{}}, nil)
|
filter := New(backend, 0, int64(headNum), []common.Address{{}}, nil)
|
||||||
filter.Logs(context.Background())
|
filter.Logs(context.Background())
|
||||||
d := time.Since(start)
|
d := time.Since(start)
|
||||||
fmt.Println("Finished running filter benchmarks")
|
fmt.Println("Finished running filter benchmarks")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package filters
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -136,11 +135,11 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err
|
||||||
// Create a matcher session and request servicing from the backend
|
// Create a matcher session and request servicing from the backend
|
||||||
matches := make(chan uint64, 64)
|
matches := make(chan uint64, 64)
|
||||||
|
|
||||||
session, err := f.matcher.Start(uint64(f.begin), end, matches)
|
session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer session.Close(time.Second)
|
defer session.Close()
|
||||||
|
|
||||||
f.backend.ServiceFilter(ctx, session)
|
f.backend.ServiceFilter(ctx, session)
|
||||||
|
|
||||||
|
|
@ -152,9 +151,13 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err
|
||||||
case number, ok := <-matches:
|
case number, ok := <-matches:
|
||||||
// Abort if all matches have been fulfilled
|
// Abort if all matches have been fulfilled
|
||||||
if !ok {
|
if !ok {
|
||||||
|
err := session.Error()
|
||||||
|
if err == nil {
|
||||||
f.begin = int64(end) + 1
|
f.begin = int64(end) + 1
|
||||||
return logs, nil
|
|
||||||
}
|
}
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
f.begin = int64(number) + 1
|
||||||
// Retrieve the suggested block and pull any truly matching logs
|
// Retrieve the suggested block and pull any truly matching logs
|
||||||
header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
|
header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
|
||||||
if header == nil || err != nil {
|
if header == nil || err != nil {
|
||||||
|
|
@ -203,7 +206,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs [
|
||||||
}
|
}
|
||||||
var unfiltered []*types.Log
|
var unfiltered []*types.Log
|
||||||
for _, receipt := range receipts {
|
for _, receipt := range receipts {
|
||||||
unfiltered = append(unfiltered, ([]*types.Log)(receipt.Logs)...)
|
unfiltered = append(unfiltered, receipt.Logs...)
|
||||||
}
|
}
|
||||||
logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
|
logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
|
||||||
if len(logs) > 0 {
|
if len(logs) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc
|
||||||
for i, section := range task.Sections {
|
for i, section := range task.Sections {
|
||||||
if rand.Int()%4 != 0 { // Handle occasional missing deliveries
|
if rand.Int()%4 != 0 { // Handle occasional missing deliveries
|
||||||
head := core.GetCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1)
|
head := core.GetCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1)
|
||||||
task.Bitsets[i] = core.GetBloomBits(b.db, task.Bit, section, head)
|
task.Bitsets[i], _ = core.GetBloomBits(b.db, task.Bit, section, head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request <- task
|
request <- task
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ type testTxPool struct {
|
||||||
|
|
||||||
// AddRemotes appends a batch of transactions to the pool, and notifies any
|
// AddRemotes appends a batch of transactions to the pool, and notifies any
|
||||||
// listeners if the addition channel is non nil
|
// listeners if the addition channel is non nil
|
||||||
func (p *testTxPool) AddRemotes(txs []*types.Transaction) error {
|
func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -105,8 +105,7 @@ func (p *testTxPool) AddRemotes(txs []*types.Transaction) error {
|
||||||
if p.added != nil {
|
if p.added != nil {
|
||||||
p.added <- txs
|
p.added <- txs
|
||||||
}
|
}
|
||||||
|
return make([]error, len(txs))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pending returns all the transactions known to the pool
|
// Pending returns all the transactions known to the pool
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ var errorToString = map[int]string{
|
||||||
|
|
||||||
type txPool interface {
|
type txPool interface {
|
||||||
// AddRemotes should add the given transactions to the pool.
|
// AddRemotes should add the given transactions to the pool.
|
||||||
AddRemotes([]*types.Transaction) error
|
AddRemotes([]*types.Transaction) []error
|
||||||
|
|
||||||
// Pending should return pending transactions.
|
// Pending should return pending transactions.
|
||||||
// The slice should be modifiable by the caller.
|
// The slice should be modifiable by the caller.
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ func (s *Service) login(conn *websocket.Conn) error {
|
||||||
protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
|
protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
|
||||||
} else {
|
} else {
|
||||||
network = fmt.Sprintf("%d", infos.Protocols["les"].(*eth.EthNodeInfo).Network)
|
network = fmt.Sprintf("%d", infos.Protocols["les"].(*eth.EthNodeInfo).Network)
|
||||||
protocol = fmt.Sprintf("les/%d", les.ProtocolVersions[0])
|
protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0])
|
||||||
}
|
}
|
||||||
auth := &authMsg{
|
auth := &authMsg{
|
||||||
Id: s.node,
|
Id: s.node,
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,8 @@ func (f *Feed) remove(sub *feedSub) {
|
||||||
// Send delivers to all subscribed channels simultaneously.
|
// Send delivers to all subscribed channels simultaneously.
|
||||||
// It returns the number of subscribers that the value was sent to.
|
// It returns the number of subscribers that the value was sent to.
|
||||||
func (f *Feed) Send(value interface{}) (nsent int) {
|
func (f *Feed) Send(value interface{}) (nsent int) {
|
||||||
|
rvalue := reflect.ValueOf(value)
|
||||||
|
|
||||||
f.once.Do(f.init)
|
f.once.Do(f.init)
|
||||||
<-f.sendLock
|
<-f.sendLock
|
||||||
|
|
||||||
|
|
@ -134,14 +136,14 @@ func (f *Feed) Send(value interface{}) (nsent int) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
f.sendCases = append(f.sendCases, f.inbox...)
|
f.sendCases = append(f.sendCases, f.inbox...)
|
||||||
f.inbox = nil
|
f.inbox = nil
|
||||||
f.mu.Unlock()
|
|
||||||
|
|
||||||
// Set the sent value on all channels.
|
|
||||||
rvalue := reflect.ValueOf(value)
|
|
||||||
if !f.typecheck(rvalue.Type()) {
|
if !f.typecheck(rvalue.Type()) {
|
||||||
f.sendLock <- struct{}{}
|
f.sendLock <- struct{}{}
|
||||||
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
||||||
}
|
}
|
||||||
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
// Set the sent value on all channels.
|
||||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
||||||
f.sendCases[i].Send = rvalue
|
f.sendCases[i].Send = rvalue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,8 +174,15 @@ func (b *LesApiBackend) AccountManager() *accounts.Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
|
func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
|
||||||
return params.BloomBitsBlocks, 0
|
if b.eth.bloomIndexer == nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
sections, _, _ := b.eth.bloomIndexer.Sections()
|
||||||
|
return light.BloomTrieFrequency, sections
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
|
func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
|
||||||
|
for i := 0; i < bloomFilterThreads; i++ {
|
||||||
|
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
|
|
@ -61,6 +62,9 @@ type LightEthereum struct {
|
||||||
// DB interfaces
|
// DB interfaces
|
||||||
chainDb ethdb.Database // Block chain database
|
chainDb ethdb.Database // Block chain database
|
||||||
|
|
||||||
|
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
||||||
|
bloomIndexer, chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
||||||
|
|
||||||
ApiBackend *LesApiBackend
|
ApiBackend *LesApiBackend
|
||||||
|
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
|
|
@ -87,7 +91,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
||||||
peers := newPeerSet()
|
peers := newPeerSet()
|
||||||
quitSync := make(chan struct{})
|
quitSync := make(chan struct{})
|
||||||
|
|
||||||
eth := &LightEthereum{
|
leth := &LightEthereum{
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
chainDb: chainDb,
|
chainDb: chainDb,
|
||||||
eventMux: ctx.EventMux,
|
eventMux: ctx.EventMux,
|
||||||
|
|
@ -97,37 +101,51 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
||||||
engine: eth.CreateConsensusEngine(ctx, config, chainConfig, chainDb),
|
engine: eth.CreateConsensusEngine(ctx, config, chainConfig, chainDb),
|
||||||
shutdownChan: make(chan bool),
|
shutdownChan: make(chan bool),
|
||||||
networkId: config.NetworkId,
|
networkId: config.NetworkId,
|
||||||
|
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||||
|
bloomIndexer: eth.NewBloomIndexer(chainDb, light.BloomTrieFrequency),
|
||||||
|
chtIndexer: light.NewChtIndexer(chainDb, true),
|
||||||
|
bloomTrieIndexer: light.NewBloomTrieIndexer(chainDb, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
eth.relay = NewLesTxRelay(peers, eth.reqDist)
|
leth.relay = NewLesTxRelay(peers, leth.reqDist)
|
||||||
eth.serverPool = newServerPool(chainDb, quitSync, ð.wg)
|
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg)
|
||||||
eth.retriever = newRetrieveManager(peers, eth.reqDist, eth.serverPool)
|
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
|
||||||
eth.odr = NewLesOdr(chainDb, eth.retriever)
|
leth.odr = NewLesOdr(chainDb, leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer, leth.retriever)
|
||||||
if eth.blockchain, err = light.NewLightChain(eth.odr, eth.chainConfig, eth.engine); err != nil {
|
if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
leth.bloomIndexer.Start(leth.blockchain)
|
||||||
// Rewind the chain in case of an incompatible config upgrade.
|
// Rewind the chain in case of an incompatible config upgrade.
|
||||||
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
||||||
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
||||||
eth.blockchain.SetHead(compat.RewindTo)
|
leth.blockchain.SetHead(compat.RewindTo)
|
||||||
core.WriteChainConfig(chainDb, genesisHash, chainConfig)
|
core.WriteChainConfig(chainDb, genesisHash, chainConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
eth.txPool = light.NewTxPool(eth.chainConfig, eth.blockchain, eth.relay)
|
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
|
||||||
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, true, config.NetworkId, eth.eventMux, eth.engine, eth.peers, eth.blockchain, nil, chainDb, eth.odr, eth.relay, quitSync, ð.wg); err != nil {
|
if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, true, ClientProtocolVersions, config.NetworkId, leth.eventMux, leth.engine, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.relay, quitSync, &leth.wg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eth.ApiBackend = &LesApiBackend{eth, nil}
|
leth.ApiBackend = &LesApiBackend{leth, nil}
|
||||||
gpoParams := config.GPO
|
gpoParams := config.GPO
|
||||||
if gpoParams.Default == nil {
|
if gpoParams.Default == nil {
|
||||||
gpoParams.Default = config.GasPrice
|
gpoParams.Default = config.GasPrice
|
||||||
}
|
}
|
||||||
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams)
|
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
|
||||||
return eth, nil
|
return leth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lesTopic(genesisHash common.Hash) discv5.Topic {
|
func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic {
|
||||||
return discv5.Topic("LES@" + common.Bytes2Hex(genesisHash.Bytes()[0:8]))
|
var name string
|
||||||
|
switch protocolVersion {
|
||||||
|
case lpv1:
|
||||||
|
name = "LES"
|
||||||
|
case lpv2:
|
||||||
|
name = "LES2"
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
|
return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8]))
|
||||||
}
|
}
|
||||||
|
|
||||||
type LightDummyAPI struct{}
|
type LightDummyAPI struct{}
|
||||||
|
|
@ -200,9 +218,13 @@ func (s *LightEthereum) Protocols() []p2p.Protocol {
|
||||||
// Start implements node.Service, starting all internal goroutines needed by the
|
// Start implements node.Service, starting all internal goroutines needed by the
|
||||||
// Ethereum protocol implementation.
|
// Ethereum protocol implementation.
|
||||||
func (s *LightEthereum) Start(srvr *p2p.Server) error {
|
func (s *LightEthereum) Start(srvr *p2p.Server) error {
|
||||||
|
s.startBloomHandlers()
|
||||||
log.Warn("Light client mode is an experimental feature")
|
log.Warn("Light client mode is an experimental feature")
|
||||||
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId)
|
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId)
|
||||||
s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash()))
|
// search the topic belonging to the oldest supported protocol because
|
||||||
|
// servers always advertise all supported protocols
|
||||||
|
protocolVersion := ClientProtocolVersions[len(ClientProtocolVersions)-1]
|
||||||
|
s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion))
|
||||||
s.protocolManager.Start()
|
s.protocolManager.Start()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +233,15 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error {
|
||||||
// Ethereum protocol.
|
// Ethereum protocol.
|
||||||
func (s *LightEthereum) Stop() error {
|
func (s *LightEthereum) Stop() error {
|
||||||
s.odr.Stop()
|
s.odr.Stop()
|
||||||
|
if s.bloomIndexer != nil {
|
||||||
|
s.bloomIndexer.Close()
|
||||||
|
}
|
||||||
|
if s.chtIndexer != nil {
|
||||||
|
s.chtIndexer.Close()
|
||||||
|
}
|
||||||
|
if s.bloomTrieIndexer != nil {
|
||||||
|
s.bloomTrieIndexer.Close()
|
||||||
|
}
|
||||||
s.blockchain.Stop()
|
s.blockchain.Stop()
|
||||||
s.protocolManager.Stop()
|
s.protocolManager.Stop()
|
||||||
s.txPool.Stop()
|
s.txPool.Stop()
|
||||||
|
|
|
||||||
84
les/bloombits.go
Normal file
84
les/bloombits.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||||
|
"github.com/ethereum/go-ethereum/light"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// bloomServiceThreads is the number of goroutines used globally by an Ethereum
|
||||||
|
// instance to service bloombits lookups for all running filters.
|
||||||
|
bloomServiceThreads = 16
|
||||||
|
|
||||||
|
// bloomFilterThreads is the number of goroutines used locally per filter to
|
||||||
|
// multiplex requests onto the global servicing goroutines.
|
||||||
|
bloomFilterThreads = 3
|
||||||
|
|
||||||
|
// bloomRetrievalBatch is the maximum number of bloom bit retrievals to service
|
||||||
|
// in a single batch.
|
||||||
|
bloomRetrievalBatch = 16
|
||||||
|
|
||||||
|
// bloomRetrievalWait is the maximum time to wait for enough bloom bit requests
|
||||||
|
// to accumulate request an entire batch (avoiding hysteresis).
|
||||||
|
bloomRetrievalWait = time.Microsecond * 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// startBloomHandlers starts a batch of goroutines to accept bloom bit database
|
||||||
|
// retrievals from possibly a range of filters and serving the data to satisfy.
|
||||||
|
func (eth *LightEthereum) startBloomHandlers() {
|
||||||
|
for i := 0; i < bloomServiceThreads; i++ {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-eth.shutdownChan:
|
||||||
|
return
|
||||||
|
|
||||||
|
case request := <-eth.bloomRequests:
|
||||||
|
task := <-request
|
||||||
|
task.Bitsets = make([][]byte, len(task.Sections))
|
||||||
|
compVectors, err := light.GetBloomBits(task.Context, eth.odr, task.Bit, task.Sections)
|
||||||
|
if err == nil {
|
||||||
|
for i := range task.Sections {
|
||||||
|
if blob, err := bitutil.DecompressBytes(compVectors[i], int(light.BloomTrieFrequency/8)); err == nil {
|
||||||
|
task.Bitsets[i] = blob
|
||||||
|
} else {
|
||||||
|
task.Error = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
task.Error = err
|
||||||
|
}
|
||||||
|
request <- task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// bloomConfirms is the number of confirmation blocks before a bloom section is
|
||||||
|
// considered probably final and its rotated bits are calculated.
|
||||||
|
bloomConfirms = 256
|
||||||
|
|
||||||
|
// bloomThrottling is the time to wait between processing two consecutive index
|
||||||
|
// sections. It's useful during chain upgrades to prevent disk overload.
|
||||||
|
bloomThrottling = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
@ -191,7 +191,7 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) {
|
||||||
for (len(d.peers) > 0 || elem == d.reqQueue.Front()) && elem != nil {
|
for (len(d.peers) > 0 || elem == d.reqQueue.Front()) && elem != nil {
|
||||||
req := elem.Value.(*distReq)
|
req := elem.Value.(*distReq)
|
||||||
canSend := false
|
canSend := false
|
||||||
for peer, _ := range d.peers {
|
for peer := range d.peers {
|
||||||
if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) {
|
if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) {
|
||||||
canSend = true
|
canSend = true
|
||||||
cost := req.getCost(peer)
|
cost := req.getCost(peer)
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ func testRequestDistributor(t *testing.T, resend bool) {
|
||||||
|
|
||||||
dist := newRequestDistributor(nil, stop)
|
dist := newRequestDistributor(nil, stop)
|
||||||
var peers [testDistPeerCount]*testDistPeer
|
var peers [testDistPeerCount]*testDistPeer
|
||||||
for i, _ := range peers {
|
for i := range peers {
|
||||||
peers[i] = &testDistPeer{}
|
peers[i] = &testDistPeer{}
|
||||||
go peers[i].worker(t, !resend, stop)
|
go peers[i].worker(t, !resend, stop)
|
||||||
dist.registerTestPeer(peers[i])
|
dist.registerTestPeer(peers[i])
|
||||||
|
|
|
||||||
|
|
@ -117,16 +117,16 @@ func newLightFetcher(pm *ProtocolManager) *lightFetcher {
|
||||||
maxConfirmedTd: big.NewInt(0),
|
maxConfirmedTd: big.NewInt(0),
|
||||||
}
|
}
|
||||||
pm.peers.notify(f)
|
pm.peers.notify(f)
|
||||||
|
|
||||||
|
f.pm.wg.Add(1)
|
||||||
go f.syncLoop()
|
go f.syncLoop()
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncLoop is the main event loop of the light fetcher
|
// syncLoop is the main event loop of the light fetcher
|
||||||
func (f *lightFetcher) syncLoop() {
|
func (f *lightFetcher) syncLoop() {
|
||||||
f.pm.wg.Add(1)
|
|
||||||
defer f.pm.wg.Done()
|
|
||||||
|
|
||||||
requesting := false
|
requesting := false
|
||||||
|
defer f.pm.wg.Done()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-f.pm.quitSync:
|
case <-f.pm.quitSync:
|
||||||
|
|
|
||||||
351
les/handler.go
351
les/handler.go
|
|
@ -18,6 +18,7 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -35,6 +36,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
|
@ -55,8 +57,9 @@ const (
|
||||||
MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
|
MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
|
||||||
MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
|
MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
|
||||||
MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
|
MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
|
||||||
MaxHeaderProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
|
MaxHelperTrieProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
|
||||||
MaxTxSend = 64 // Amount of transactions to be send per request
|
MaxTxSend = 64 // Amount of transactions to be send per request
|
||||||
|
MaxTxStatus = 256 // Amount of transactions to queried per request
|
||||||
|
|
||||||
disableClientRemovePeer = false
|
disableClientRemovePeer = false
|
||||||
)
|
)
|
||||||
|
|
@ -86,8 +89,8 @@ type BlockChain interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type txPool interface {
|
type txPool interface {
|
||||||
// AddRemotes should add the given transactions to the pool.
|
AddRemotes(txs []*types.Transaction) []error
|
||||||
AddRemotes([]*types.Transaction) error
|
Status(hashes []common.Hash) []core.TxStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProtocolManager struct {
|
type ProtocolManager struct {
|
||||||
|
|
@ -125,7 +128,7 @@ type ProtocolManager struct {
|
||||||
|
|
||||||
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
||||||
// with the ethereum network.
|
// with the ethereum network.
|
||||||
func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) {
|
func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protocolVersions []uint, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) {
|
||||||
// Create the protocol manager with the base fields
|
// Create the protocol manager with the base fields
|
||||||
manager := &ProtocolManager{
|
manager := &ProtocolManager{
|
||||||
lightSync: lightSync,
|
lightSync: lightSync,
|
||||||
|
|
@ -147,15 +150,16 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, network
|
||||||
manager.retriever = odr.retriever
|
manager.retriever = odr.retriever
|
||||||
manager.reqDist = odr.retriever.dist
|
manager.reqDist = odr.retriever.dist
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate a sub-protocol for every implemented version we can handle
|
// Initiate a sub-protocol for every implemented version we can handle
|
||||||
manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions))
|
manager.SubProtocols = make([]p2p.Protocol, 0, len(protocolVersions))
|
||||||
for i, version := range ProtocolVersions {
|
for _, version := range protocolVersions {
|
||||||
// Compatible, initialize the sub-protocol
|
// Compatible, initialize the sub-protocol
|
||||||
version := version // Closure for the run
|
version := version // Closure for the run
|
||||||
manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
|
manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
|
||||||
Name: "les",
|
Name: "les",
|
||||||
Version: version,
|
Version: version,
|
||||||
Length: ProtocolLengths[i],
|
Length: ProtocolLengths[version],
|
||||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||||
var entry *poolEntry
|
var entry *poolEntry
|
||||||
peer := manager.newPeer(int(version), networkId, p, rw)
|
peer := manager.newPeer(int(version), networkId, p, rw)
|
||||||
|
|
@ -315,7 +319,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsMsg, SendTxMsg, GetHeaderProofsMsg}
|
var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, SendTxV2Msg, GetTxStatusMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetHelperTrieProofsMsg}
|
||||||
|
|
||||||
// handleMsg is invoked whenever an inbound message is received from a remote
|
// handleMsg is invoked whenever an inbound message is received from a remote
|
||||||
// peer. The remote connection is torn down upon returning any error.
|
// peer. The remote connection is torn down upon returning any error.
|
||||||
|
|
@ -362,11 +366,23 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
// Block header query, collect the requested headers and reply
|
// Block header query, collect the requested headers and reply
|
||||||
case AnnounceMsg:
|
case AnnounceMsg:
|
||||||
p.Log().Trace("Received announce message")
|
p.Log().Trace("Received announce message")
|
||||||
|
if p.requestAnnounceType == announceTypeNone {
|
||||||
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
}
|
||||||
|
|
||||||
var req announceData
|
var req announceData
|
||||||
if err := msg.Decode(&req); err != nil {
|
if err := msg.Decode(&req); err != nil {
|
||||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.requestAnnounceType == announceTypeSigned {
|
||||||
|
if err := req.checkSignature(p.pubKey); err != nil {
|
||||||
|
p.Log().Trace("Invalid announcement signature", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Log().Trace("Valid announcement signature")
|
||||||
|
}
|
||||||
|
|
||||||
p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
|
p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
|
||||||
if pm.fetcher != nil {
|
if pm.fetcher != nil {
|
||||||
pm.fetcher.announce(p, &req)
|
pm.fetcher.announce(p, &req)
|
||||||
|
|
@ -655,7 +671,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
Obj: resp.Receipts,
|
Obj: resp.Receipts,
|
||||||
}
|
}
|
||||||
|
|
||||||
case GetProofsMsg:
|
case GetProofsV1Msg:
|
||||||
p.Log().Trace("Received proofs request")
|
p.Log().Trace("Received proofs request")
|
||||||
// Decode the retrieval message
|
// Decode the retrieval message
|
||||||
var req struct {
|
var req struct {
|
||||||
|
|
@ -690,9 +706,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tr != nil {
|
if tr != nil {
|
||||||
proof := tr.Prove(req.Key)
|
var proof light.NodeList
|
||||||
|
tr.Prove(req.Key, 0, &proof)
|
||||||
proofs = append(proofs, proof)
|
proofs = append(proofs, proof)
|
||||||
bytes += len(proof)
|
bytes += proof.DataSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -701,7 +718,67 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
return p.SendProofs(req.ReqID, bv, proofs)
|
return p.SendProofs(req.ReqID, bv, proofs)
|
||||||
|
|
||||||
case ProofsMsg:
|
case GetProofsV2Msg:
|
||||||
|
p.Log().Trace("Received les/2 proofs request")
|
||||||
|
// Decode the retrieval message
|
||||||
|
var req struct {
|
||||||
|
ReqID uint64
|
||||||
|
Reqs []ProofReq
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&req); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
// Gather state data until the fetch or network limits is reached
|
||||||
|
var (
|
||||||
|
lastBHash common.Hash
|
||||||
|
lastAccKey []byte
|
||||||
|
tr, str *trie.Trie
|
||||||
|
)
|
||||||
|
reqCnt := len(req.Reqs)
|
||||||
|
if reject(uint64(reqCnt), MaxProofsFetch) {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := light.NewNodeSet()
|
||||||
|
|
||||||
|
for _, req := range req.Reqs {
|
||||||
|
if nodes.DataSize() >= softResponseLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tr == nil || req.BHash != lastBHash {
|
||||||
|
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
|
||||||
|
tr, _ = trie.New(header.Root, pm.chainDb)
|
||||||
|
} else {
|
||||||
|
tr = nil
|
||||||
|
}
|
||||||
|
lastBHash = req.BHash
|
||||||
|
str = nil
|
||||||
|
}
|
||||||
|
if tr != nil {
|
||||||
|
if len(req.AccKey) > 0 {
|
||||||
|
if str == nil || !bytes.Equal(req.AccKey, lastAccKey) {
|
||||||
|
sdata := tr.Get(req.AccKey)
|
||||||
|
str = nil
|
||||||
|
var acc state.Account
|
||||||
|
if err := rlp.DecodeBytes(sdata, &acc); err == nil {
|
||||||
|
str, _ = trie.New(acc.Root, pm.chainDb)
|
||||||
|
}
|
||||||
|
lastAccKey = common.CopyBytes(req.AccKey)
|
||||||
|
}
|
||||||
|
if str != nil {
|
||||||
|
str.Prove(req.Key, req.FromLevel, nodes)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr.Prove(req.Key, req.FromLevel, nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
proofs := nodes.NodeList()
|
||||||
|
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
||||||
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
|
return p.SendProofsV2(req.ReqID, bv, proofs)
|
||||||
|
|
||||||
|
case ProofsV1Msg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
return errResp(ErrUnexpectedResponse, "")
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
}
|
}
|
||||||
|
|
@ -710,14 +787,35 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
// A batch of merkle proofs arrived to one of our previous requests
|
// A batch of merkle proofs arrived to one of our previous requests
|
||||||
var resp struct {
|
var resp struct {
|
||||||
ReqID, BV uint64
|
ReqID, BV uint64
|
||||||
Data [][]rlp.RawValue
|
Data []light.NodeList
|
||||||
}
|
}
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgProofs,
|
MsgType: MsgProofsV1,
|
||||||
|
ReqID: resp.ReqID,
|
||||||
|
Obj: resp.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
case ProofsV2Msg:
|
||||||
|
if pm.odr == nil {
|
||||||
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Log().Trace("Received les/2 proofs response")
|
||||||
|
// A batch of merkle proofs arrived to one of our previous requests
|
||||||
|
var resp struct {
|
||||||
|
ReqID, BV uint64
|
||||||
|
Data light.NodeList
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&resp); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
||||||
|
deliverMsg = &Msg{
|
||||||
|
MsgType: MsgProofsV2,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
Obj: resp.Data,
|
Obj: resp.Data,
|
||||||
}
|
}
|
||||||
|
|
@ -738,22 +836,25 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
proofs []ChtResp
|
proofs []ChtResp
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Reqs)
|
reqCnt := len(req.Reqs)
|
||||||
if reject(uint64(reqCnt), MaxHeaderProofsFetch) {
|
if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
trieDb := ethdb.NewTable(pm.chainDb, light.ChtTablePrefix)
|
||||||
for _, req := range req.Reqs {
|
for _, req := range req.Reqs {
|
||||||
if bytes >= softResponseLimit {
|
if bytes >= softResponseLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil {
|
if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil {
|
||||||
if root := getChtRoot(pm.chainDb, req.ChtNum); root != (common.Hash{}) {
|
sectionHead := core.GetCanonicalHash(pm.chainDb, (req.ChtNum+1)*light.ChtV1Frequency-1)
|
||||||
if tr, _ := trie.New(root, pm.chainDb); tr != nil {
|
if root := light.GetChtRoot(pm.chainDb, req.ChtNum, sectionHead); root != (common.Hash{}) {
|
||||||
|
if tr, _ := trie.New(root, trieDb); tr != nil {
|
||||||
var encNumber [8]byte
|
var encNumber [8]byte
|
||||||
binary.BigEndian.PutUint64(encNumber[:], req.BlockNum)
|
binary.BigEndian.PutUint64(encNumber[:], req.BlockNum)
|
||||||
proof := tr.Prove(encNumber[:])
|
var proof light.NodeList
|
||||||
|
tr.Prove(encNumber[:], 0, &proof)
|
||||||
proofs = append(proofs, ChtResp{Header: header, Proof: proof})
|
proofs = append(proofs, ChtResp{Header: header, Proof: proof})
|
||||||
bytes += len(proof) + estHeaderRlpSize
|
bytes += proof.DataSize() + estHeaderRlpSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -762,6 +863,73 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
return p.SendHeaderProofs(req.ReqID, bv, proofs)
|
return p.SendHeaderProofs(req.ReqID, bv, proofs)
|
||||||
|
|
||||||
|
case GetHelperTrieProofsMsg:
|
||||||
|
p.Log().Trace("Received helper trie proof request")
|
||||||
|
// Decode the retrieval message
|
||||||
|
var req struct {
|
||||||
|
ReqID uint64
|
||||||
|
Reqs []HelperTrieReq
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&req); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
// Gather state data until the fetch or network limits is reached
|
||||||
|
var (
|
||||||
|
auxBytes int
|
||||||
|
auxData [][]byte
|
||||||
|
)
|
||||||
|
reqCnt := len(req.Reqs)
|
||||||
|
if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lastIdx uint64
|
||||||
|
lastType uint
|
||||||
|
root common.Hash
|
||||||
|
tr *trie.Trie
|
||||||
|
)
|
||||||
|
|
||||||
|
nodes := light.NewNodeSet()
|
||||||
|
|
||||||
|
for _, req := range req.Reqs {
|
||||||
|
if nodes.DataSize()+auxBytes >= softResponseLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tr == nil || req.HelperTrieType != lastType || req.TrieIdx != lastIdx {
|
||||||
|
var prefix string
|
||||||
|
root, prefix = pm.getHelperTrie(req.HelperTrieType, req.TrieIdx)
|
||||||
|
if root != (common.Hash{}) {
|
||||||
|
if t, err := trie.New(root, ethdb.NewTable(pm.chainDb, prefix)); err == nil {
|
||||||
|
tr = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastType = req.HelperTrieType
|
||||||
|
lastIdx = req.TrieIdx
|
||||||
|
}
|
||||||
|
if req.AuxReq == auxRoot {
|
||||||
|
var data []byte
|
||||||
|
if root != (common.Hash{}) {
|
||||||
|
data = root[:]
|
||||||
|
}
|
||||||
|
auxData = append(auxData, data)
|
||||||
|
auxBytes += len(data)
|
||||||
|
} else {
|
||||||
|
if tr != nil {
|
||||||
|
tr.Prove(req.Key, req.FromLevel, nodes)
|
||||||
|
}
|
||||||
|
if req.AuxReq != 0 {
|
||||||
|
data := pm.getHelperTrieAuxData(req)
|
||||||
|
auxData = append(auxData, data)
|
||||||
|
auxBytes += len(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
proofs := nodes.NodeList()
|
||||||
|
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
||||||
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
|
return p.SendHelperTrieProofs(req.ReqID, bv, HelperTrieResps{Proofs: proofs, AuxData: auxData})
|
||||||
|
|
||||||
case HeaderProofsMsg:
|
case HeaderProofsMsg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
return errResp(ErrUnexpectedResponse, "")
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
|
@ -782,9 +950,30 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
Obj: resp.Data,
|
Obj: resp.Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case HelperTrieProofsMsg:
|
||||||
|
if pm.odr == nil {
|
||||||
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Log().Trace("Received helper trie proof response")
|
||||||
|
var resp struct {
|
||||||
|
ReqID, BV uint64
|
||||||
|
Data HelperTrieResps
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&resp); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
||||||
|
deliverMsg = &Msg{
|
||||||
|
MsgType: MsgHelperTrieProofs,
|
||||||
|
ReqID: resp.ReqID,
|
||||||
|
Obj: resp.Data,
|
||||||
|
}
|
||||||
|
|
||||||
case SendTxMsg:
|
case SendTxMsg:
|
||||||
if pm.txpool == nil {
|
if pm.txpool == nil {
|
||||||
return errResp(ErrUnexpectedResponse, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
// Transactions arrived, parse all of them and deliver to the pool
|
// Transactions arrived, parse all of them and deliver to the pool
|
||||||
var txs []*types.Transaction
|
var txs []*types.Transaction
|
||||||
|
|
@ -795,14 +984,85 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if reject(uint64(reqCnt), MaxTxSend) {
|
if reject(uint64(reqCnt), MaxTxSend) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
pm.txpool.AddRemotes(txs)
|
||||||
if err := pm.txpool.AddRemotes(txs); err != nil {
|
|
||||||
return errResp(ErrUnexpectedResponse, "msg: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
_, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
|
|
||||||
|
case SendTxV2Msg:
|
||||||
|
if pm.txpool == nil {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
// Transactions arrived, parse all of them and deliver to the pool
|
||||||
|
var req struct {
|
||||||
|
ReqID uint64
|
||||||
|
Txs []*types.Transaction
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&req); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
reqCnt := len(req.Txs)
|
||||||
|
if reject(uint64(reqCnt), MaxTxSend) {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes := make([]common.Hash, len(req.Txs))
|
||||||
|
for i, tx := range req.Txs {
|
||||||
|
hashes[i] = tx.Hash()
|
||||||
|
}
|
||||||
|
stats := pm.txStatus(hashes)
|
||||||
|
for i, stat := range stats {
|
||||||
|
if stat.Status == core.TxStatusUnknown {
|
||||||
|
if errs := pm.txpool.AddRemotes([]*types.Transaction{req.Txs[i]}); errs[0] != nil {
|
||||||
|
stats[i].Error = errs[0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stats[i] = pm.txStatus([]common.Hash{hashes[i]})[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
||||||
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
|
|
||||||
|
return p.SendTxStatus(req.ReqID, bv, stats)
|
||||||
|
|
||||||
|
case GetTxStatusMsg:
|
||||||
|
if pm.txpool == nil {
|
||||||
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
}
|
||||||
|
// Transactions arrived, parse all of them and deliver to the pool
|
||||||
|
var req struct {
|
||||||
|
ReqID uint64
|
||||||
|
Hashes []common.Hash
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&req); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
reqCnt := len(req.Hashes)
|
||||||
|
if reject(uint64(reqCnt), MaxTxStatus) {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
||||||
|
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
||||||
|
|
||||||
|
return p.SendTxStatus(req.ReqID, bv, pm.txStatus(req.Hashes))
|
||||||
|
|
||||||
|
case TxStatusMsg:
|
||||||
|
if pm.odr == nil {
|
||||||
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Log().Trace("Received tx status response")
|
||||||
|
var resp struct {
|
||||||
|
ReqID, BV uint64
|
||||||
|
Status []core.TxStatus
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&resp); err != nil {
|
||||||
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
p.Log().Trace("Received unknown message", "code", msg.Code)
|
p.Log().Trace("Received unknown message", "code", msg.Code)
|
||||||
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
|
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
|
||||||
|
|
@ -820,6 +1080,49 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHelperTrie returns the post-processed trie root for the given trie ID and section index
|
||||||
|
func (pm *ProtocolManager) getHelperTrie(id uint, idx uint64) (common.Hash, string) {
|
||||||
|
switch id {
|
||||||
|
case htCanonical:
|
||||||
|
sectionHead := core.GetCanonicalHash(pm.chainDb, (idx+1)*light.ChtFrequency-1)
|
||||||
|
return light.GetChtV2Root(pm.chainDb, idx, sectionHead), light.ChtTablePrefix
|
||||||
|
case htBloomBits:
|
||||||
|
sectionHead := core.GetCanonicalHash(pm.chainDb, (idx+1)*light.BloomTrieFrequency-1)
|
||||||
|
return light.GetBloomTrieRoot(pm.chainDb, idx, sectionHead), light.BloomTrieTablePrefix
|
||||||
|
}
|
||||||
|
return common.Hash{}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHelperTrieAuxData returns requested auxiliary data for the given HelperTrie request
|
||||||
|
func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte {
|
||||||
|
if req.HelperTrieType == htCanonical && req.AuxReq == auxHeader {
|
||||||
|
if len(req.Key) != 8 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockNum := binary.BigEndian.Uint64(req.Key)
|
||||||
|
hash := core.GetCanonicalHash(pm.chainDb, blockNum)
|
||||||
|
return core.GetHeaderRLP(pm.chainDb, hash, blockNum)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus {
|
||||||
|
stats := make([]txStatus, len(hashes))
|
||||||
|
for i, stat := range pm.txpool.Status(hashes) {
|
||||||
|
// Save the status we've got from the transaction pool
|
||||||
|
stats[i].Status = stat
|
||||||
|
|
||||||
|
// If the transaction is unknown to the pool, try looking it up locally
|
||||||
|
if stat == core.TxStatusUnknown {
|
||||||
|
if block, number, index := core.GetTxLookupEntry(pm.chainDb, hashes[i]); block != (common.Hash{}) {
|
||||||
|
stats[i].Status = core.TxStatusIncluded
|
||||||
|
stats[i].Lookup = &core.TxLookupEntry{BlockHash: block, BlockIndex: number, Index: index}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
// NodeInfo retrieves some protocol metadata about the running host node.
|
// NodeInfo retrieves some protocol metadata about the running host node.
|
||||||
func (self *ProtocolManager) NodeInfo() *eth.EthNodeInfo {
|
func (self *ProtocolManager) NodeInfo() *eth.EthNodeInfo {
|
||||||
return ð.EthNodeInfo{
|
return ð.EthNodeInfo{
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,11 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -26,7 +29,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
@ -39,9 +44,29 @@ func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}
|
||||||
return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data})
|
return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCheckProof(t *testing.T, exp *light.NodeSet, got light.NodeList) {
|
||||||
|
if exp.KeyCount() > len(got) {
|
||||||
|
t.Errorf("proof has fewer nodes than expected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exp.KeyCount() < len(got) {
|
||||||
|
t.Errorf("proof has more nodes than expected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, node := range got {
|
||||||
|
n, _ := exp.Get(crypto.Keccak256(node))
|
||||||
|
if !bytes.Equal(n, node) {
|
||||||
|
t.Errorf("proof contents mismatch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that block headers can be retrieved from a remote chain based on user queries.
|
// Tests that block headers can be retrieved from a remote chain based on user queries.
|
||||||
func TestGetBlockHeadersLes1(t *testing.T) { testGetBlockHeaders(t, 1) }
|
func TestGetBlockHeadersLes1(t *testing.T) { testGetBlockHeaders(t, 1) }
|
||||||
|
|
||||||
|
func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) }
|
||||||
|
|
||||||
func testGetBlockHeaders(t *testing.T, protocol int) {
|
func testGetBlockHeaders(t *testing.T, protocol int) {
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil, nil, db)
|
pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil, nil, db)
|
||||||
|
|
@ -171,6 +196,8 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
|
||||||
// Tests that block contents can be retrieved from a remote chain based on their hashes.
|
// Tests that block contents can be retrieved from a remote chain based on their hashes.
|
||||||
func TestGetBlockBodiesLes1(t *testing.T) { testGetBlockBodies(t, 1) }
|
func TestGetBlockBodiesLes1(t *testing.T) { testGetBlockBodies(t, 1) }
|
||||||
|
|
||||||
|
func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) }
|
||||||
|
|
||||||
func testGetBlockBodies(t *testing.T, protocol int) {
|
func testGetBlockBodies(t *testing.T, protocol int) {
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
pm := newTestProtocolManagerMust(t, false, downloader.MaxBlockFetch+15, nil, nil, nil, db)
|
pm := newTestProtocolManagerMust(t, false, downloader.MaxBlockFetch+15, nil, nil, nil, db)
|
||||||
|
|
@ -247,6 +274,8 @@ func testGetBlockBodies(t *testing.T, protocol int) {
|
||||||
// Tests that the contract codes can be retrieved based on account addresses.
|
// Tests that the contract codes can be retrieved based on account addresses.
|
||||||
func TestGetCodeLes1(t *testing.T) { testGetCode(t, 1) }
|
func TestGetCodeLes1(t *testing.T) { testGetCode(t, 1) }
|
||||||
|
|
||||||
|
func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) }
|
||||||
|
|
||||||
func testGetCode(t *testing.T, protocol int) {
|
func testGetCode(t *testing.T, protocol int) {
|
||||||
// Assemble the test environment
|
// Assemble the test environment
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
|
@ -280,6 +309,8 @@ func testGetCode(t *testing.T, protocol int) {
|
||||||
// Tests that the transaction receipts can be retrieved based on hashes.
|
// Tests that the transaction receipts can be retrieved based on hashes.
|
||||||
func TestGetReceiptLes1(t *testing.T) { testGetReceipt(t, 1) }
|
func TestGetReceiptLes1(t *testing.T) { testGetReceipt(t, 1) }
|
||||||
|
|
||||||
|
func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) }
|
||||||
|
|
||||||
func testGetReceipt(t *testing.T, protocol int) {
|
func testGetReceipt(t *testing.T, protocol int) {
|
||||||
// Assemble the test environment
|
// Assemble the test environment
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
|
@ -307,6 +338,8 @@ func testGetReceipt(t *testing.T, protocol int) {
|
||||||
// Tests that trie merkle proofs can be retrieved
|
// Tests that trie merkle proofs can be retrieved
|
||||||
func TestGetProofsLes1(t *testing.T) { testGetProofs(t, 1) }
|
func TestGetProofsLes1(t *testing.T) { testGetProofs(t, 1) }
|
||||||
|
|
||||||
|
func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) }
|
||||||
|
|
||||||
func testGetProofs(t *testing.T, protocol int) {
|
func testGetProofs(t *testing.T, protocol int) {
|
||||||
// Assemble the test environment
|
// Assemble the test environment
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
|
@ -315,8 +348,11 @@ func testGetProofs(t *testing.T, protocol int) {
|
||||||
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
|
peer, _ := newTestPeer(t, "peer", protocol, pm, true)
|
||||||
defer peer.close()
|
defer peer.close()
|
||||||
|
|
||||||
var proofreqs []ProofReq
|
var (
|
||||||
var proofs [][]rlp.RawValue
|
proofreqs []ProofReq
|
||||||
|
proofsV1 [][]rlp.RawValue
|
||||||
|
)
|
||||||
|
proofsV2 := light.NewNodeSet()
|
||||||
|
|
||||||
accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, {}}
|
accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, {}}
|
||||||
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
|
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
|
||||||
|
|
@ -331,14 +367,135 @@ func testGetProofs(t *testing.T, protocol int) {
|
||||||
}
|
}
|
||||||
proofreqs = append(proofreqs, req)
|
proofreqs = append(proofreqs, req)
|
||||||
|
|
||||||
proof := trie.Prove(crypto.Keccak256(acc[:]))
|
switch protocol {
|
||||||
proofs = append(proofs, proof)
|
case 1:
|
||||||
|
var proof light.NodeList
|
||||||
|
trie.Prove(crypto.Keccak256(acc[:]), 0, &proof)
|
||||||
|
proofsV1 = append(proofsV1, proof)
|
||||||
|
case 2:
|
||||||
|
trie.Prove(crypto.Keccak256(acc[:]), 0, proofsV2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Send the proof request and verify the response
|
// Send the proof request and verify the response
|
||||||
cost := peer.GetRequestCost(GetProofsMsg, len(proofreqs))
|
switch protocol {
|
||||||
sendRequest(peer.app, GetProofsMsg, 42, cost, proofreqs)
|
case 1:
|
||||||
if err := expectResponse(peer.app, ProofsMsg, 42, testBufLimit, proofs); err != nil {
|
cost := peer.GetRequestCost(GetProofsV1Msg, len(proofreqs))
|
||||||
|
sendRequest(peer.app, GetProofsV1Msg, 42, cost, proofreqs)
|
||||||
|
if err := expectResponse(peer.app, ProofsV1Msg, 42, testBufLimit, proofsV1); err != nil {
|
||||||
t.Errorf("proofs mismatch: %v", err)
|
t.Errorf("proofs mismatch: %v", err)
|
||||||
}
|
}
|
||||||
|
case 2:
|
||||||
|
cost := peer.GetRequestCost(GetProofsV2Msg, len(proofreqs))
|
||||||
|
sendRequest(peer.app, GetProofsV2Msg, 42, cost, proofreqs)
|
||||||
|
msg, err := peer.app.ReadMsg()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Message read error: %v", err)
|
||||||
|
}
|
||||||
|
var resp struct {
|
||||||
|
ReqID, BV uint64
|
||||||
|
Data light.NodeList
|
||||||
|
}
|
||||||
|
if err := msg.Decode(&resp); err != nil {
|
||||||
|
t.Errorf("reply decode error: %v", err)
|
||||||
|
}
|
||||||
|
if msg.Code != ProofsV2Msg {
|
||||||
|
t.Errorf("Message code mismatch")
|
||||||
|
}
|
||||||
|
if resp.ReqID != 42 {
|
||||||
|
t.Errorf("ReqID mismatch")
|
||||||
|
}
|
||||||
|
if resp.BV != testBufLimit {
|
||||||
|
t.Errorf("BV mismatch")
|
||||||
|
}
|
||||||
|
testCheckProof(t, proofsV2, resp.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransactionStatusLes2(t *testing.T) {
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
pm := newTestProtocolManagerMust(t, false, 0, nil, nil, nil, db)
|
||||||
|
chain := pm.blockchain.(*core.BlockChain)
|
||||||
|
txpool := core.NewTxPool(core.DefaultTxPoolConfig, params.TestChainConfig, chain)
|
||||||
|
pm.txpool = txpool
|
||||||
|
peer, _ := newTestPeer(t, "peer", 2, pm, true)
|
||||||
|
defer peer.close()
|
||||||
|
|
||||||
|
var reqID uint64
|
||||||
|
|
||||||
|
test := func(tx *types.Transaction, send bool, expStatus txStatus) {
|
||||||
|
reqID++
|
||||||
|
if send {
|
||||||
|
cost := peer.GetRequestCost(SendTxV2Msg, 1)
|
||||||
|
sendRequest(peer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx})
|
||||||
|
} else {
|
||||||
|
cost := peer.GetRequestCost(GetTxStatusMsg, 1)
|
||||||
|
sendRequest(peer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()})
|
||||||
|
}
|
||||||
|
if err := expectResponse(peer.app, TxStatusMsg, reqID, testBufLimit, []txStatus{expStatus}); err != nil {
|
||||||
|
t.Errorf("transaction status mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := types.HomesteadSigner{}
|
||||||
|
|
||||||
|
// test error status by sending an underpriced transaction
|
||||||
|
tx0, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), bigTxGas, nil, nil), signer, testBankKey)
|
||||||
|
test(tx0, true, txStatus{Status: core.TxStatusUnknown, Error: core.ErrUnderpriced})
|
||||||
|
|
||||||
|
tx1, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), bigTxGas, big.NewInt(100000000000), nil), signer, testBankKey)
|
||||||
|
test(tx1, false, txStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown
|
||||||
|
test(tx1, true, txStatus{Status: core.TxStatusPending}) // send valid processable tx, should return pending
|
||||||
|
test(tx1, true, txStatus{Status: core.TxStatusPending}) // adding it again should not return an error
|
||||||
|
|
||||||
|
tx2, _ := types.SignTx(types.NewTransaction(1, acc1Addr, big.NewInt(10000), bigTxGas, big.NewInt(100000000000), nil), signer, testBankKey)
|
||||||
|
tx3, _ := types.SignTx(types.NewTransaction(2, acc1Addr, big.NewInt(10000), bigTxGas, big.NewInt(100000000000), nil), signer, testBankKey)
|
||||||
|
// send transactions in the wrong order, tx3 should be queued
|
||||||
|
test(tx3, true, txStatus{Status: core.TxStatusQueued})
|
||||||
|
test(tx2, true, txStatus{Status: core.TxStatusPending})
|
||||||
|
// query again, now tx3 should be pending too
|
||||||
|
test(tx3, false, txStatus{Status: core.TxStatusPending})
|
||||||
|
|
||||||
|
// generate and add a block with tx1 and tx2 included
|
||||||
|
gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), db, 1, func(i int, block *core.BlockGen) {
|
||||||
|
block.AddTx(tx1)
|
||||||
|
block.AddTx(tx2)
|
||||||
|
})
|
||||||
|
if _, err := chain.InsertChain(gchain); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// wait until TxPool processes the inserted block
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if pending, _ := txpool.Stats(); pending == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if pending, _ := txpool.Stats(); pending != 1 {
|
||||||
|
t.Fatalf("pending count mismatch: have %d, want 1", pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if their status is included now
|
||||||
|
block1hash := core.GetCanonicalHash(db, 1)
|
||||||
|
test(tx1, false, txStatus{Status: core.TxStatusIncluded, Lookup: &core.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
|
||||||
|
test(tx2, false, txStatus{Status: core.TxStatusIncluded, Lookup: &core.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
|
||||||
|
|
||||||
|
// create a reorg that rolls them back
|
||||||
|
gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), db, 2, func(i int, block *core.BlockGen) {})
|
||||||
|
if _, err := chain.InsertChain(gchain); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// wait until TxPool processes the reorg
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if pending, _ := txpool.Stats(); pending == 3 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if pending, _ := txpool.Stats(); pending != 3 {
|
||||||
|
t.Fatalf("pending count mismatch: have %d, want 3", pending)
|
||||||
|
}
|
||||||
|
// check if their status is pending again
|
||||||
|
test(tx1, false, txStatus{Status: core.TxStatusPending})
|
||||||
|
test(tx2, false, txStatus{Status: core.TxStatusPending})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import (
|
||||||
var (
|
var (
|
||||||
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||||
testBankFunds = big.NewInt(1000000)
|
testBankFunds = big.NewInt(1000000000000000000)
|
||||||
|
|
||||||
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||||
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||||
|
|
@ -156,7 +156,13 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
||||||
chain = blockchain
|
chain = blockchain
|
||||||
}
|
}
|
||||||
|
|
||||||
pm, err := NewProtocolManager(gspec.Config, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, make(chan struct{}), new(sync.WaitGroup))
|
var protocolVersions []uint
|
||||||
|
if lightSync {
|
||||||
|
protocolVersions = ClientProtocolVersions
|
||||||
|
} else {
|
||||||
|
protocolVersions = ServerProtocolVersions
|
||||||
|
}
|
||||||
|
pm, err := NewProtocolManager(gspec.Config, lightSync, protocolVersions, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, make(chan struct{}), new(sync.WaitGroup))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
les/odr.go
36
les/odr.go
|
|
@ -19,6 +19,7 @@ package les
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -27,32 +28,55 @@ import (
|
||||||
// LesOdr implements light.OdrBackend
|
// LesOdr implements light.OdrBackend
|
||||||
type LesOdr struct {
|
type LesOdr struct {
|
||||||
db ethdb.Database
|
db ethdb.Database
|
||||||
stop chan struct{}
|
chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer
|
||||||
retriever *retrieveManager
|
retriever *retrieveManager
|
||||||
|
stop chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLesOdr(db ethdb.Database, retriever *retrieveManager) *LesOdr {
|
func NewLesOdr(db ethdb.Database, chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer, retriever *retrieveManager) *LesOdr {
|
||||||
return &LesOdr{
|
return &LesOdr{
|
||||||
db: db,
|
db: db,
|
||||||
|
chtIndexer: chtIndexer,
|
||||||
|
bloomTrieIndexer: bloomTrieIndexer,
|
||||||
|
bloomIndexer: bloomIndexer,
|
||||||
retriever: retriever,
|
retriever: retriever,
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop cancels all pending retrievals
|
||||||
func (odr *LesOdr) Stop() {
|
func (odr *LesOdr) Stop() {
|
||||||
close(odr.stop)
|
close(odr.stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Database returns the backing database
|
||||||
func (odr *LesOdr) Database() ethdb.Database {
|
func (odr *LesOdr) Database() ethdb.Database {
|
||||||
return odr.db
|
return odr.db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChtIndexer returns the CHT chain indexer
|
||||||
|
func (odr *LesOdr) ChtIndexer() *core.ChainIndexer {
|
||||||
|
return odr.chtIndexer
|
||||||
|
}
|
||||||
|
|
||||||
|
// BloomTrieIndexer returns the bloom trie chain indexer
|
||||||
|
func (odr *LesOdr) BloomTrieIndexer() *core.ChainIndexer {
|
||||||
|
return odr.bloomTrieIndexer
|
||||||
|
}
|
||||||
|
|
||||||
|
// BloomIndexer returns the bloombits chain indexer
|
||||||
|
func (odr *LesOdr) BloomIndexer() *core.ChainIndexer {
|
||||||
|
return odr.bloomIndexer
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MsgBlockBodies = iota
|
MsgBlockBodies = iota
|
||||||
MsgCode
|
MsgCode
|
||||||
MsgReceipts
|
MsgReceipts
|
||||||
MsgProofs
|
MsgProofsV1
|
||||||
|
MsgProofsV2
|
||||||
MsgHeaderProofs
|
MsgHeaderProofs
|
||||||
|
MsgHelperTrieProofs
|
||||||
)
|
)
|
||||||
|
|
||||||
// Msg encodes a LES message that delivers reply data for a request
|
// Msg encodes a LES message that delivers reply data for a request
|
||||||
|
|
@ -64,7 +88,7 @@ type Msg struct {
|
||||||
|
|
||||||
// Retrieve tries to fetch an object from the LES network.
|
// Retrieve tries to fetch an object from the LES network.
|
||||||
// If the network retrieval was successful, it stores the object in local db.
|
// If the network retrieval was successful, it stores the object in local db.
|
||||||
func (self *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
|
func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
|
||||||
lreq := LesRequest(req)
|
lreq := LesRequest(req)
|
||||||
|
|
||||||
reqID := genReqID()
|
reqID := genReqID()
|
||||||
|
|
@ -84,9 +108,9 @@ func (self *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = self.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(self.db, msg) }); err == nil {
|
if err = odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err == nil {
|
||||||
// retrieved from network, store in db
|
// retrieved from network, store in db
|
||||||
req.StoreResult(self.db)
|
req.StoreResult(odr.db)
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Failed to retrieve data from network", "err", err)
|
log.Debug("Failed to retrieve data from network", "err", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,15 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidMessageType = errors.New("invalid message type")
|
errInvalidMessageType = errors.New("invalid message type")
|
||||||
errMultipleEntries = errors.New("multiple response entries")
|
errInvalidEntryCount = errors.New("invalid number of response entries")
|
||||||
errHeaderUnavailable = errors.New("header unavailable")
|
errHeaderUnavailable = errors.New("header unavailable")
|
||||||
errTxHashMismatch = errors.New("transaction hash mismatch")
|
errTxHashMismatch = errors.New("transaction hash mismatch")
|
||||||
errUncleHashMismatch = errors.New("uncle hash mismatch")
|
errUncleHashMismatch = errors.New("uncle hash mismatch")
|
||||||
errReceiptHashMismatch = errors.New("receipt hash mismatch")
|
errReceiptHashMismatch = errors.New("receipt hash mismatch")
|
||||||
errDataHashMismatch = errors.New("data hash mismatch")
|
errDataHashMismatch = errors.New("data hash mismatch")
|
||||||
errCHTHashMismatch = errors.New("cht hash mismatch")
|
errCHTHashMismatch = errors.New("cht hash mismatch")
|
||||||
|
errCHTNumberMismatch = errors.New("cht number mismatch")
|
||||||
|
errUselessNodes = errors.New("useless nodes in merkle proof nodeset")
|
||||||
)
|
)
|
||||||
|
|
||||||
type LesOdrRequest interface {
|
type LesOdrRequest interface {
|
||||||
|
|
@ -64,6 +66,8 @@ func LesRequest(req light.OdrRequest) LesOdrRequest {
|
||||||
return (*CodeRequest)(r)
|
return (*CodeRequest)(r)
|
||||||
case *light.ChtRequest:
|
case *light.ChtRequest:
|
||||||
return (*ChtRequest)(r)
|
return (*ChtRequest)(r)
|
||||||
|
case *light.BloomRequest:
|
||||||
|
return (*BloomRequest)(r)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +105,7 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
}
|
}
|
||||||
bodies := msg.Obj.([]*types.Body)
|
bodies := msg.Obj.([]*types.Body)
|
||||||
if len(bodies) != 1 {
|
if len(bodies) != 1 {
|
||||||
return errMultipleEntries
|
return errInvalidEntryCount
|
||||||
}
|
}
|
||||||
body := bodies[0]
|
body := bodies[0]
|
||||||
|
|
||||||
|
|
@ -157,7 +161,7 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
}
|
}
|
||||||
receipts := msg.Obj.([]types.Receipts)
|
receipts := msg.Obj.([]types.Receipts)
|
||||||
if len(receipts) != 1 {
|
if len(receipts) != 1 {
|
||||||
return errMultipleEntries
|
return errInvalidEntryCount
|
||||||
}
|
}
|
||||||
receipt := receipts[0]
|
receipt := receipts[0]
|
||||||
|
|
||||||
|
|
@ -186,7 +190,14 @@ type TrieRequest light.TrieRequest
|
||||||
// GetCost returns the cost of the given ODR request according to the serving
|
// GetCost returns the cost of the given ODR request according to the serving
|
||||||
// peer's cost table (implementation of LesOdrRequest)
|
// peer's cost table (implementation of LesOdrRequest)
|
||||||
func (r *TrieRequest) GetCost(peer *peer) uint64 {
|
func (r *TrieRequest) GetCost(peer *peer) uint64 {
|
||||||
return peer.GetRequestCost(GetProofsMsg, 1)
|
switch peer.version {
|
||||||
|
case lpv1:
|
||||||
|
return peer.GetRequestCost(GetProofsV1Msg, 1)
|
||||||
|
case lpv2:
|
||||||
|
return peer.GetRequestCost(GetProofsV2Msg, 1)
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanSend tells if a certain peer is suitable for serving the given request
|
// CanSend tells if a certain peer is suitable for serving the given request
|
||||||
|
|
@ -197,12 +208,12 @@ func (r *TrieRequest) CanSend(peer *peer) bool {
|
||||||
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
||||||
func (r *TrieRequest) Request(reqID uint64, peer *peer) error {
|
func (r *TrieRequest) Request(reqID uint64, peer *peer) error {
|
||||||
peer.Log().Debug("Requesting trie proof", "root", r.Id.Root, "key", r.Key)
|
peer.Log().Debug("Requesting trie proof", "root", r.Id.Root, "key", r.Key)
|
||||||
req := &ProofReq{
|
req := ProofReq{
|
||||||
BHash: r.Id.BlockHash,
|
BHash: r.Id.BlockHash,
|
||||||
AccKey: r.Id.AccKey,
|
AccKey: r.Id.AccKey,
|
||||||
Key: r.Key,
|
Key: r.Key,
|
||||||
}
|
}
|
||||||
return peer.RequestProofs(reqID, r.GetCost(peer), []*ProofReq{req})
|
return peer.RequestProofs(reqID, r.GetCost(peer), []ProofReq{req})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid processes an ODR request reply message from the LES network
|
// Valid processes an ODR request reply message from the LES network
|
||||||
|
|
@ -211,20 +222,38 @@ func (r *TrieRequest) Request(reqID uint64, peer *peer) error {
|
||||||
func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
|
func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key)
|
log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key)
|
||||||
|
|
||||||
// Ensure we have a correct message with a single proof
|
switch msg.MsgType {
|
||||||
if msg.MsgType != MsgProofs {
|
case MsgProofsV1:
|
||||||
return errInvalidMessageType
|
proofs := msg.Obj.([]light.NodeList)
|
||||||
}
|
|
||||||
proofs := msg.Obj.([][]rlp.RawValue)
|
|
||||||
if len(proofs) != 1 {
|
if len(proofs) != 1 {
|
||||||
return errMultipleEntries
|
return errInvalidEntryCount
|
||||||
}
|
}
|
||||||
|
nodeSet := proofs[0].NodeSet()
|
||||||
// Verify the proof and store if checks out
|
// Verify the proof and store if checks out
|
||||||
if _, err := trie.VerifyProof(r.Id.Root, r.Key, proofs[0]); err != nil {
|
if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, nodeSet); err != nil {
|
||||||
return fmt.Errorf("merkle proof verification failed: %v", err)
|
return fmt.Errorf("merkle proof verification failed: %v", err)
|
||||||
}
|
}
|
||||||
r.Proof = proofs[0]
|
r.Proof = nodeSet
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case MsgProofsV2:
|
||||||
|
proofs := msg.Obj.(light.NodeList)
|
||||||
|
// Verify the proof and store if checks out
|
||||||
|
nodeSet := proofs.NodeSet()
|
||||||
|
reads := &readTraceDB{db: nodeSet}
|
||||||
|
if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil {
|
||||||
|
return fmt.Errorf("merkle proof verification failed: %v", err)
|
||||||
|
}
|
||||||
|
// check if all nodes have been read by VerifyProof
|
||||||
|
if len(reads.reads) != nodeSet.KeyCount() {
|
||||||
|
return errUselessNodes
|
||||||
|
}
|
||||||
|
r.Proof = nodeSet
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errInvalidMessageType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeReq struct {
|
type CodeReq struct {
|
||||||
|
|
@ -249,11 +278,11 @@ func (r *CodeRequest) CanSend(peer *peer) bool {
|
||||||
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
||||||
func (r *CodeRequest) Request(reqID uint64, peer *peer) error {
|
func (r *CodeRequest) Request(reqID uint64, peer *peer) error {
|
||||||
peer.Log().Debug("Requesting code data", "hash", r.Hash)
|
peer.Log().Debug("Requesting code data", "hash", r.Hash)
|
||||||
req := &CodeReq{
|
req := CodeReq{
|
||||||
BHash: r.Id.BlockHash,
|
BHash: r.Id.BlockHash,
|
||||||
AccKey: r.Id.AccKey,
|
AccKey: r.Id.AccKey,
|
||||||
}
|
}
|
||||||
return peer.RequestCode(reqID, r.GetCost(peer), []*CodeReq{req})
|
return peer.RequestCode(reqID, r.GetCost(peer), []CodeReq{req})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid processes an ODR request reply message from the LES network
|
// Valid processes an ODR request reply message from the LES network
|
||||||
|
|
@ -268,7 +297,7 @@ func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
}
|
}
|
||||||
reply := msg.Obj.([][]byte)
|
reply := msg.Obj.([][]byte)
|
||||||
if len(reply) != 1 {
|
if len(reply) != 1 {
|
||||||
return errMultipleEntries
|
return errInvalidEntryCount
|
||||||
}
|
}
|
||||||
data := reply[0]
|
data := reply[0]
|
||||||
|
|
||||||
|
|
@ -280,10 +309,36 @@ func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChtReq struct {
|
const (
|
||||||
ChtNum, BlockNum, FromLevel uint64
|
// helper trie type constants
|
||||||
|
htCanonical = iota // Canonical hash trie
|
||||||
|
htBloomBits // BloomBits trie
|
||||||
|
|
||||||
|
// applicable for all helper trie requests
|
||||||
|
auxRoot = 1
|
||||||
|
// applicable for htCanonical
|
||||||
|
auxHeader = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type HelperTrieReq struct {
|
||||||
|
HelperTrieType uint
|
||||||
|
TrieIdx uint64
|
||||||
|
Key []byte
|
||||||
|
FromLevel, AuxReq uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HelperTrieResps struct { // describes all responses, not just a single one
|
||||||
|
Proofs light.NodeList
|
||||||
|
AuxData [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy LES/1
|
||||||
|
type ChtReq struct {
|
||||||
|
ChtNum, BlockNum uint64
|
||||||
|
FromLevel uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy LES/1
|
||||||
type ChtResp struct {
|
type ChtResp struct {
|
||||||
Header *types.Header
|
Header *types.Header
|
||||||
Proof []rlp.RawValue
|
Proof []rlp.RawValue
|
||||||
|
|
@ -295,7 +350,14 @@ type ChtRequest light.ChtRequest
|
||||||
// GetCost returns the cost of the given ODR request according to the serving
|
// GetCost returns the cost of the given ODR request according to the serving
|
||||||
// peer's cost table (implementation of LesOdrRequest)
|
// peer's cost table (implementation of LesOdrRequest)
|
||||||
func (r *ChtRequest) GetCost(peer *peer) uint64 {
|
func (r *ChtRequest) GetCost(peer *peer) uint64 {
|
||||||
|
switch peer.version {
|
||||||
|
case lpv1:
|
||||||
return peer.GetRequestCost(GetHeaderProofsMsg, 1)
|
return peer.GetRequestCost(GetHeaderProofsMsg, 1)
|
||||||
|
case lpv2:
|
||||||
|
return peer.GetRequestCost(GetHelperTrieProofsMsg, 1)
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanSend tells if a certain peer is suitable for serving the given request
|
// CanSend tells if a certain peer is suitable for serving the given request
|
||||||
|
|
@ -303,17 +365,21 @@ func (r *ChtRequest) CanSend(peer *peer) bool {
|
||||||
peer.lock.RLock()
|
peer.lock.RLock()
|
||||||
defer peer.lock.RUnlock()
|
defer peer.lock.RUnlock()
|
||||||
|
|
||||||
return r.ChtNum <= (peer.headInfo.Number-light.ChtConfirmations)/light.ChtFrequency
|
return peer.headInfo.Number >= light.HelperTrieConfirmations && r.ChtNum <= (peer.headInfo.Number-light.HelperTrieConfirmations)/light.ChtFrequency
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
||||||
func (r *ChtRequest) Request(reqID uint64, peer *peer) error {
|
func (r *ChtRequest) Request(reqID uint64, peer *peer) error {
|
||||||
peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum)
|
peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum)
|
||||||
req := &ChtReq{
|
var encNum [8]byte
|
||||||
ChtNum: r.ChtNum,
|
binary.BigEndian.PutUint64(encNum[:], r.BlockNum)
|
||||||
BlockNum: r.BlockNum,
|
req := HelperTrieReq{
|
||||||
|
HelperTrieType: htCanonical,
|
||||||
|
TrieIdx: r.ChtNum,
|
||||||
|
Key: encNum[:],
|
||||||
|
AuxReq: auxHeader,
|
||||||
}
|
}
|
||||||
return peer.RequestHeaderProofs(reqID, r.GetCost(peer), []*ChtReq{req})
|
return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), []HelperTrieReq{req})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid processes an ODR request reply message from the LES network
|
// Valid processes an ODR request reply message from the LES network
|
||||||
|
|
@ -322,13 +388,11 @@ func (r *ChtRequest) Request(reqID uint64, peer *peer) error {
|
||||||
func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
log.Debug("Validating CHT", "cht", r.ChtNum, "block", r.BlockNum)
|
log.Debug("Validating CHT", "cht", r.ChtNum, "block", r.BlockNum)
|
||||||
|
|
||||||
// Ensure we have a correct message with a single proof element
|
switch msg.MsgType {
|
||||||
if msg.MsgType != MsgHeaderProofs {
|
case MsgHeaderProofs: // LES/1 backwards compatibility
|
||||||
return errInvalidMessageType
|
|
||||||
}
|
|
||||||
proofs := msg.Obj.([]ChtResp)
|
proofs := msg.Obj.([]ChtResp)
|
||||||
if len(proofs) != 1 {
|
if len(proofs) != 1 {
|
||||||
return errMultipleEntries
|
return errInvalidEntryCount
|
||||||
}
|
}
|
||||||
proof := proofs[0]
|
proof := proofs[0]
|
||||||
|
|
||||||
|
|
@ -336,7 +400,7 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
var encNumber [8]byte
|
var encNumber [8]byte
|
||||||
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
|
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
|
||||||
|
|
||||||
value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], proof.Proof)
|
value, err, _ := trie.VerifyProof(r.ChtRoot, encNumber[:], light.NodeList(proof.Proof).NodeSet())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -349,8 +413,154 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
}
|
}
|
||||||
// Verifications passed, store and return
|
// Verifications passed, store and return
|
||||||
r.Header = proof.Header
|
r.Header = proof.Header
|
||||||
r.Proof = proof.Proof
|
r.Proof = light.NodeList(proof.Proof).NodeSet()
|
||||||
r.Td = node.Td
|
r.Td = node.Td
|
||||||
|
case MsgHelperTrieProofs:
|
||||||
|
resp := msg.Obj.(HelperTrieResps)
|
||||||
|
if len(resp.AuxData) != 1 {
|
||||||
|
return errInvalidEntryCount
|
||||||
|
}
|
||||||
|
nodeSet := resp.Proofs.NodeSet()
|
||||||
|
headerEnc := resp.AuxData[0]
|
||||||
|
if len(headerEnc) == 0 {
|
||||||
|
return errHeaderUnavailable
|
||||||
|
}
|
||||||
|
header := new(types.Header)
|
||||||
|
if err := rlp.DecodeBytes(headerEnc, header); err != nil {
|
||||||
|
return errHeaderUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the CHT
|
||||||
|
var encNumber [8]byte
|
||||||
|
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
|
||||||
|
|
||||||
|
reads := &readTraceDB{db: nodeSet}
|
||||||
|
value, err, _ := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("merkle proof verification failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(reads.reads) != nodeSet.KeyCount() {
|
||||||
|
return errUselessNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
var node light.ChtNode
|
||||||
|
if err := rlp.DecodeBytes(value, &node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if node.Hash != header.Hash() {
|
||||||
|
return errCHTHashMismatch
|
||||||
|
}
|
||||||
|
if r.BlockNum != header.Number.Uint64() {
|
||||||
|
return errCHTNumberMismatch
|
||||||
|
}
|
||||||
|
// Verifications passed, store and return
|
||||||
|
r.Header = header
|
||||||
|
r.Proof = nodeSet
|
||||||
|
r.Td = node.Td
|
||||||
|
default:
|
||||||
|
return errInvalidMessageType
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BloomReq struct {
|
||||||
|
BloomTrieNum, BitIdx, SectionIdx, FromLevel uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface
|
||||||
|
type BloomRequest light.BloomRequest
|
||||||
|
|
||||||
|
// GetCost returns the cost of the given ODR request according to the serving
|
||||||
|
// peer's cost table (implementation of LesOdrRequest)
|
||||||
|
func (r *BloomRequest) GetCost(peer *peer) uint64 {
|
||||||
|
return peer.GetRequestCost(GetHelperTrieProofsMsg, len(r.SectionIdxList))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanSend tells if a certain peer is suitable for serving the given request
|
||||||
|
func (r *BloomRequest) CanSend(peer *peer) bool {
|
||||||
|
peer.lock.RLock()
|
||||||
|
defer peer.lock.RUnlock()
|
||||||
|
|
||||||
|
if peer.version < lpv2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return peer.headInfo.Number >= light.HelperTrieConfirmations && r.BloomTrieNum <= (peer.headInfo.Number-light.HelperTrieConfirmations)/light.BloomTrieFrequency
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
|
||||||
|
func (r *BloomRequest) Request(reqID uint64, peer *peer) error {
|
||||||
|
peer.Log().Debug("Requesting BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIdxList)
|
||||||
|
reqs := make([]HelperTrieReq, len(r.SectionIdxList))
|
||||||
|
|
||||||
|
var encNumber [10]byte
|
||||||
|
binary.BigEndian.PutUint16(encNumber[0:2], uint16(r.BitIdx))
|
||||||
|
|
||||||
|
for i, sectionIdx := range r.SectionIdxList {
|
||||||
|
binary.BigEndian.PutUint64(encNumber[2:10], sectionIdx)
|
||||||
|
reqs[i] = HelperTrieReq{
|
||||||
|
HelperTrieType: htBloomBits,
|
||||||
|
TrieIdx: r.BloomTrieNum,
|
||||||
|
Key: common.CopyBytes(encNumber[:]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid processes an ODR request reply message from the LES network
|
||||||
|
// returns true and stores results in memory if the message was a valid reply
|
||||||
|
// to the request (implementation of LesOdrRequest)
|
||||||
|
func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error {
|
||||||
|
log.Debug("Validating BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIdxList)
|
||||||
|
|
||||||
|
// Ensure we have a correct message with a single proof element
|
||||||
|
if msg.MsgType != MsgHelperTrieProofs {
|
||||||
|
return errInvalidMessageType
|
||||||
|
}
|
||||||
|
resps := msg.Obj.(HelperTrieResps)
|
||||||
|
proofs := resps.Proofs
|
||||||
|
nodeSet := proofs.NodeSet()
|
||||||
|
reads := &readTraceDB{db: nodeSet}
|
||||||
|
|
||||||
|
r.BloomBits = make([][]byte, len(r.SectionIdxList))
|
||||||
|
|
||||||
|
// Verify the proofs
|
||||||
|
var encNumber [10]byte
|
||||||
|
binary.BigEndian.PutUint16(encNumber[0:2], uint16(r.BitIdx))
|
||||||
|
|
||||||
|
for i, idx := range r.SectionIdxList {
|
||||||
|
binary.BigEndian.PutUint64(encNumber[2:10], idx)
|
||||||
|
value, err, _ := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.BloomBits[i] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reads.reads) != nodeSet.KeyCount() {
|
||||||
|
return errUselessNodes
|
||||||
|
}
|
||||||
|
r.Proofs = nodeSet
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTraceDB stores the keys of database reads. We use this to check that received node
|
||||||
|
// sets contain only the trie nodes necessary to make proofs pass.
|
||||||
|
type readTraceDB struct {
|
||||||
|
db trie.DatabaseReader
|
||||||
|
reads map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a stored node
|
||||||
|
func (db *readTraceDB) Get(k []byte) ([]byte, error) {
|
||||||
|
if db.reads == nil {
|
||||||
|
db.reads = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
db.reads[string(k)] = struct{}{}
|
||||||
|
return db.db.Get(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns true if the node set contains the given key
|
||||||
|
func (db *readTraceDB) Has(key []byte) (bool, error) {
|
||||||
|
_, err := db.Get(key)
|
||||||
|
return err == nil, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -39,6 +40,8 @@ type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.Chain
|
||||||
|
|
||||||
func TestOdrGetBlockLes1(t *testing.T) { testOdr(t, 1, 1, odrGetBlock) }
|
func TestOdrGetBlockLes1(t *testing.T) { testOdr(t, 1, 1, odrGetBlock) }
|
||||||
|
|
||||||
|
func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, odrGetBlock) }
|
||||||
|
|
||||||
func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
|
func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
|
||||||
var block *types.Block
|
var block *types.Block
|
||||||
if bc != nil {
|
if bc != nil {
|
||||||
|
|
@ -55,6 +58,8 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainCon
|
||||||
|
|
||||||
func TestOdrGetReceiptsLes1(t *testing.T) { testOdr(t, 1, 1, odrGetReceipts) }
|
func TestOdrGetReceiptsLes1(t *testing.T) { testOdr(t, 1, 1, odrGetReceipts) }
|
||||||
|
|
||||||
|
func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, odrGetReceipts) }
|
||||||
|
|
||||||
func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
|
func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
|
||||||
var receipts types.Receipts
|
var receipts types.Receipts
|
||||||
if bc != nil {
|
if bc != nil {
|
||||||
|
|
@ -71,6 +76,8 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.Chain
|
||||||
|
|
||||||
func TestOdrAccountsLes1(t *testing.T) { testOdr(t, 1, 1, odrAccounts) }
|
func TestOdrAccountsLes1(t *testing.T) { testOdr(t, 1, 1, odrAccounts) }
|
||||||
|
|
||||||
|
func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, odrAccounts) }
|
||||||
|
|
||||||
func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
|
func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
|
||||||
dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
|
dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
|
||||||
acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
|
acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
|
||||||
|
|
@ -100,6 +107,8 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon
|
||||||
|
|
||||||
func TestOdrContractCallLes1(t *testing.T) { testOdr(t, 1, 2, odrContractCall) }
|
func TestOdrContractCallLes1(t *testing.T) { testOdr(t, 1, 2, odrContractCall) }
|
||||||
|
|
||||||
|
func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, odrContractCall) }
|
||||||
|
|
||||||
type callmsg struct {
|
type callmsg struct {
|
||||||
types.Message
|
types.Message
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +163,7 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
||||||
rm := newRetrieveManager(peers, dist, nil)
|
rm := newRetrieveManager(peers, dist, nil)
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
ldb, _ := ethdb.NewMemDatabase()
|
ldb, _ := ethdb.NewMemDatabase()
|
||||||
odr := NewLesOdr(ldb, rm)
|
odr := NewLesOdr(ldb, light.NewChtIndexer(db, true), light.NewBloomTrieIndexer(db, true), eth.NewBloomIndexer(db, light.BloomTrieFrequency), rm)
|
||||||
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
||||||
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
||||||
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
|
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
|
||||||
|
|
|
||||||
134
les/peer.go
134
les/peer.go
|
|
@ -18,6 +18,8 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -28,6 +30,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
@ -40,14 +43,23 @@ var (
|
||||||
|
|
||||||
const maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
|
const maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
|
||||||
|
|
||||||
|
const (
|
||||||
|
announceTypeNone = iota
|
||||||
|
announceTypeSimple
|
||||||
|
announceTypeSigned
|
||||||
|
)
|
||||||
|
|
||||||
type peer struct {
|
type peer struct {
|
||||||
*p2p.Peer
|
*p2p.Peer
|
||||||
|
pubKey *ecdsa.PublicKey
|
||||||
|
|
||||||
rw p2p.MsgReadWriter
|
rw p2p.MsgReadWriter
|
||||||
|
|
||||||
version int // Protocol version negotiated
|
version int // Protocol version negotiated
|
||||||
network uint64 // Network ID being on
|
network uint64 // Network ID being on
|
||||||
|
|
||||||
|
announceType, requestAnnounceType uint64
|
||||||
|
|
||||||
id string
|
id string
|
||||||
|
|
||||||
headInfo *announceData
|
headInfo *announceData
|
||||||
|
|
@ -68,9 +80,11 @@ type peer struct {
|
||||||
|
|
||||||
func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||||
id := p.ID()
|
id := p.ID()
|
||||||
|
pubKey, _ := id.Pubkey()
|
||||||
|
|
||||||
return &peer{
|
return &peer{
|
||||||
Peer: p,
|
Peer: p,
|
||||||
|
pubKey: pubKey,
|
||||||
rw: rw,
|
rw: rw,
|
||||||
version: version,
|
version: version,
|
||||||
network: network,
|
network: network,
|
||||||
|
|
@ -197,16 +211,31 @@ func (p *peer) SendReceiptsRLP(reqID, bv uint64, receipts []rlp.RawValue) error
|
||||||
return sendResponse(p.rw, ReceiptsMsg, reqID, bv, receipts)
|
return sendResponse(p.rw, ReceiptsMsg, reqID, bv, receipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendProofs sends a batch of merkle proofs, corresponding to the ones requested.
|
// SendProofs sends a batch of legacy LES/1 merkle proofs, corresponding to the ones requested.
|
||||||
func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error {
|
func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error {
|
||||||
return sendResponse(p.rw, ProofsMsg, reqID, bv, proofs)
|
return sendResponse(p.rw, ProofsV1Msg, reqID, bv, proofs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendHeaderProofs sends a batch of header proofs, corresponding to the ones requested.
|
// SendProofsV2 sends a batch of merkle proofs, corresponding to the ones requested.
|
||||||
|
func (p *peer) SendProofsV2(reqID, bv uint64, proofs light.NodeList) error {
|
||||||
|
return sendResponse(p.rw, ProofsV2Msg, reqID, bv, proofs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendHeaderProofs sends a batch of legacy LES/1 header proofs, corresponding to the ones requested.
|
||||||
func (p *peer) SendHeaderProofs(reqID, bv uint64, proofs []ChtResp) error {
|
func (p *peer) SendHeaderProofs(reqID, bv uint64, proofs []ChtResp) error {
|
||||||
return sendResponse(p.rw, HeaderProofsMsg, reqID, bv, proofs)
|
return sendResponse(p.rw, HeaderProofsMsg, reqID, bv, proofs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendHelperTrieProofs sends a batch of HelperTrie proofs, corresponding to the ones requested.
|
||||||
|
func (p *peer) SendHelperTrieProofs(reqID, bv uint64, resp HelperTrieResps) error {
|
||||||
|
return sendResponse(p.rw, HelperTrieProofsMsg, reqID, bv, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTxStatus sends a batch of transaction status records, corresponding to the ones requested.
|
||||||
|
func (p *peer) SendTxStatus(reqID, bv uint64, stats []txStatus) error {
|
||||||
|
return sendResponse(p.rw, TxStatusMsg, reqID, bv, stats)
|
||||||
|
}
|
||||||
|
|
||||||
// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
|
// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
|
||||||
// specified header query, based on the hash of an origin block.
|
// specified header query, based on the hash of an origin block.
|
||||||
func (p *peer) RequestHeadersByHash(reqID, cost uint64, origin common.Hash, amount int, skip int, reverse bool) error {
|
func (p *peer) RequestHeadersByHash(reqID, cost uint64, origin common.Hash, amount int, skip int, reverse bool) error {
|
||||||
|
|
@ -230,7 +259,7 @@ func (p *peer) RequestBodies(reqID, cost uint64, hashes []common.Hash) error {
|
||||||
|
|
||||||
// RequestCode fetches a batch of arbitrary data from a node's known state
|
// RequestCode fetches a batch of arbitrary data from a node's known state
|
||||||
// data, corresponding to the specified hashes.
|
// data, corresponding to the specified hashes.
|
||||||
func (p *peer) RequestCode(reqID, cost uint64, reqs []*CodeReq) error {
|
func (p *peer) RequestCode(reqID, cost uint64, reqs []CodeReq) error {
|
||||||
p.Log().Debug("Fetching batch of codes", "count", len(reqs))
|
p.Log().Debug("Fetching batch of codes", "count", len(reqs))
|
||||||
return sendRequest(p.rw, GetCodeMsg, reqID, cost, reqs)
|
return sendRequest(p.rw, GetCodeMsg, reqID, cost, reqs)
|
||||||
}
|
}
|
||||||
|
|
@ -242,20 +271,58 @@ func (p *peer) RequestReceipts(reqID, cost uint64, hashes []common.Hash) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestProofs fetches a batch of merkle proofs from a remote node.
|
// RequestProofs fetches a batch of merkle proofs from a remote node.
|
||||||
func (p *peer) RequestProofs(reqID, cost uint64, reqs []*ProofReq) error {
|
func (p *peer) RequestProofs(reqID, cost uint64, reqs []ProofReq) error {
|
||||||
p.Log().Debug("Fetching batch of proofs", "count", len(reqs))
|
p.Log().Debug("Fetching batch of proofs", "count", len(reqs))
|
||||||
return sendRequest(p.rw, GetProofsMsg, reqID, cost, reqs)
|
switch p.version {
|
||||||
|
case lpv1:
|
||||||
|
return sendRequest(p.rw, GetProofsV1Msg, reqID, cost, reqs)
|
||||||
|
case lpv2:
|
||||||
|
return sendRequest(p.rw, GetProofsV2Msg, reqID, cost, reqs)
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestHeaderProofs fetches a batch of header merkle proofs from a remote node.
|
// RequestHelperTrieProofs fetches a batch of HelperTrie merkle proofs from a remote node.
|
||||||
func (p *peer) RequestHeaderProofs(reqID, cost uint64, reqs []*ChtReq) error {
|
func (p *peer) RequestHelperTrieProofs(reqID, cost uint64, reqs []HelperTrieReq) error {
|
||||||
p.Log().Debug("Fetching batch of header proofs", "count", len(reqs))
|
p.Log().Debug("Fetching batch of HelperTrie proofs", "count", len(reqs))
|
||||||
return sendRequest(p.rw, GetHeaderProofsMsg, reqID, cost, reqs)
|
switch p.version {
|
||||||
|
case lpv1:
|
||||||
|
reqsV1 := make([]ChtReq, len(reqs))
|
||||||
|
for i, req := range reqs {
|
||||||
|
if req.HelperTrieType != htCanonical || req.AuxReq != auxHeader || len(req.Key) != 8 {
|
||||||
|
return fmt.Errorf("Request invalid in LES/1 mode")
|
||||||
|
}
|
||||||
|
blockNum := binary.BigEndian.Uint64(req.Key)
|
||||||
|
// convert HelperTrie request to old CHT request
|
||||||
|
reqsV1[i] = ChtReq{ChtNum: (req.TrieIdx+1)*(light.ChtFrequency/light.ChtV1Frequency) - 1, BlockNum: blockNum, FromLevel: req.FromLevel}
|
||||||
|
}
|
||||||
|
return sendRequest(p.rw, GetHeaderProofsMsg, reqID, cost, reqsV1)
|
||||||
|
case lpv2:
|
||||||
|
return sendRequest(p.rw, GetHelperTrieProofsMsg, reqID, cost, reqs)
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestTxStatus fetches a batch of transaction status records from a remote node.
|
||||||
|
func (p *peer) RequestTxStatus(reqID, cost uint64, txHashes []common.Hash) error {
|
||||||
|
p.Log().Debug("Requesting transaction status", "count", len(txHashes))
|
||||||
|
return sendRequest(p.rw, GetTxStatusMsg, reqID, cost, txHashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTxStatus sends a batch of transactions to be added to the remote transaction pool.
|
||||||
func (p *peer) SendTxs(reqID, cost uint64, txs types.Transactions) error {
|
func (p *peer) SendTxs(reqID, cost uint64, txs types.Transactions) error {
|
||||||
p.Log().Debug("Fetching batch of transactions", "count", len(txs))
|
p.Log().Debug("Fetching batch of transactions", "count", len(txs))
|
||||||
return p2p.Send(p.rw, SendTxMsg, txs)
|
switch p.version {
|
||||||
|
case lpv1:
|
||||||
|
return p2p.Send(p.rw, SendTxMsg, txs) // old message format does not include reqID
|
||||||
|
case lpv2:
|
||||||
|
return sendRequest(p.rw, SendTxV2Msg, reqID, cost, txs)
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyValueEntry struct {
|
type keyValueEntry struct {
|
||||||
|
|
@ -289,7 +356,7 @@ func (l keyValueList) decode() keyValueMap {
|
||||||
func (m keyValueMap) get(key string, val interface{}) error {
|
func (m keyValueMap) get(key string, val interface{}) error {
|
||||||
enc, ok := m[key]
|
enc, ok := m[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errResp(ErrHandshakeMissingKey, "%s", key)
|
return errResp(ErrMissingKey, "%s", key)
|
||||||
}
|
}
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -333,7 +400,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
|
|
||||||
var send keyValueList
|
var send keyValueList
|
||||||
send = send.add("protocolVersion", uint64(p.version))
|
send = send.add("protocolVersion", uint64(p.version))
|
||||||
send = send.add("networkId", uint64(p.network))
|
send = send.add("networkId", p.network)
|
||||||
send = send.add("headTd", td)
|
send = send.add("headTd", td)
|
||||||
send = send.add("headHash", head)
|
send = send.add("headHash", head)
|
||||||
send = send.add("headNum", headNum)
|
send = send.add("headNum", headNum)
|
||||||
|
|
@ -348,6 +415,9 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
list := server.fcCostStats.getCurrentList()
|
list := server.fcCostStats.getCurrentList()
|
||||||
send = send.add("flowControl/MRC", list)
|
send = send.add("flowControl/MRC", list)
|
||||||
p.fcCosts = list.decode()
|
p.fcCosts = list.decode()
|
||||||
|
} else {
|
||||||
|
p.requestAnnounceType = announceTypeSimple // set to default until "very light" client mode is implemented
|
||||||
|
send = send.add("announceType", p.requestAnnounceType)
|
||||||
}
|
}
|
||||||
recvList, err := p.sendReceiveHandshake(send)
|
recvList, err := p.sendReceiveHandshake(send)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -392,6 +462,9 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
/*if recv.get("serveStateSince", nil) == nil {
|
/*if recv.get("serveStateSince", nil) == nil {
|
||||||
return errResp(ErrUselessPeer, "wanted client, got server")
|
return errResp(ErrUselessPeer, "wanted client, got server")
|
||||||
}*/
|
}*/
|
||||||
|
if recv.get("announceType", &p.announceType) != nil {
|
||||||
|
p.announceType = announceTypeSimple
|
||||||
|
}
|
||||||
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
|
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
|
||||||
} else {
|
} else {
|
||||||
if recv.get("serveChainSince", nil) != nil {
|
if recv.get("serveChainSince", nil) != nil {
|
||||||
|
|
@ -456,11 +529,15 @@ func newPeerSet() *peerSet {
|
||||||
// notify adds a service to be notified about added or removed peers
|
// notify adds a service to be notified about added or removed peers
|
||||||
func (ps *peerSet) notify(n peerSetNotify) {
|
func (ps *peerSet) notify(n peerSetNotify) {
|
||||||
ps.lock.Lock()
|
ps.lock.Lock()
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
ps.notifyList = append(ps.notifyList, n)
|
ps.notifyList = append(ps.notifyList, n)
|
||||||
|
peers := make([]*peer, 0, len(ps.peers))
|
||||||
for _, p := range ps.peers {
|
for _, p := range ps.peers {
|
||||||
go n.registerPeer(p)
|
peers = append(peers, p)
|
||||||
|
}
|
||||||
|
ps.lock.Unlock()
|
||||||
|
|
||||||
|
for _, p := range peers {
|
||||||
|
n.registerPeer(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -468,8 +545,6 @@ func (ps *peerSet) notify(n peerSetNotify) {
|
||||||
// peer is already known.
|
// peer is already known.
|
||||||
func (ps *peerSet) Register(p *peer) error {
|
func (ps *peerSet) Register(p *peer) error {
|
||||||
ps.lock.Lock()
|
ps.lock.Lock()
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
if ps.closed {
|
if ps.closed {
|
||||||
return errClosed
|
return errClosed
|
||||||
}
|
}
|
||||||
|
|
@ -478,8 +553,12 @@ func (ps *peerSet) Register(p *peer) error {
|
||||||
}
|
}
|
||||||
ps.peers[p.id] = p
|
ps.peers[p.id] = p
|
||||||
p.sendQueue = newExecQueue(100)
|
p.sendQueue = newExecQueue(100)
|
||||||
for _, n := range ps.notifyList {
|
peers := make([]peerSetNotify, len(ps.notifyList))
|
||||||
go n.registerPeer(p)
|
copy(peers, ps.notifyList)
|
||||||
|
ps.lock.Unlock()
|
||||||
|
|
||||||
|
for _, n := range peers {
|
||||||
|
n.registerPeer(p)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -488,19 +567,22 @@ func (ps *peerSet) Register(p *peer) error {
|
||||||
// actions to/from that particular entity. It also initiates disconnection at the networking layer.
|
// actions to/from that particular entity. It also initiates disconnection at the networking layer.
|
||||||
func (ps *peerSet) Unregister(id string) error {
|
func (ps *peerSet) Unregister(id string) error {
|
||||||
ps.lock.Lock()
|
ps.lock.Lock()
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
if p, ok := ps.peers[id]; !ok {
|
if p, ok := ps.peers[id]; !ok {
|
||||||
|
ps.lock.Unlock()
|
||||||
return errNotRegistered
|
return errNotRegistered
|
||||||
} else {
|
} else {
|
||||||
for _, n := range ps.notifyList {
|
delete(ps.peers, id)
|
||||||
go n.unregisterPeer(p)
|
peers := make([]peerSetNotify, len(ps.notifyList))
|
||||||
|
copy(peers, ps.notifyList)
|
||||||
|
ps.lock.Unlock()
|
||||||
|
|
||||||
|
for _, n := range peers {
|
||||||
|
n.unregisterPeer(p)
|
||||||
}
|
}
|
||||||
p.sendQueue.quit()
|
p.sendQueue.quit()
|
||||||
p.Peer.Disconnect(p2p.DiscUselessPeer)
|
p.Peer.Disconnect(p2p.DiscUselessPeer)
|
||||||
}
|
|
||||||
delete(ps.peers, id)
|
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllPeerIDs returns a list of all registered peer IDs
|
// AllPeerIDs returns a list of all registered peer IDs
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,35 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants to match up protocol versions and messages
|
// Constants to match up protocol versions and messages
|
||||||
const (
|
const (
|
||||||
lpv1 = 1
|
lpv1 = 1
|
||||||
|
lpv2 = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// Supported versions of the les protocol (first is primary).
|
// Supported versions of the les protocol (first is primary)
|
||||||
var ProtocolVersions = []uint{lpv1}
|
var (
|
||||||
|
ClientProtocolVersions = []uint{lpv2, lpv1}
|
||||||
|
ServerProtocolVersions = []uint{lpv2, lpv1}
|
||||||
|
)
|
||||||
|
|
||||||
// Number of implemented message corresponding to different protocol versions.
|
// Number of implemented message corresponding to different protocol versions.
|
||||||
var ProtocolLengths = []uint64{15}
|
var ProtocolLengths = map[uint]uint64{lpv1: 15, lpv2: 22}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NetworkId = 1
|
NetworkId = 1
|
||||||
|
|
@ -53,13 +64,21 @@ const (
|
||||||
BlockBodiesMsg = 0x05
|
BlockBodiesMsg = 0x05
|
||||||
GetReceiptsMsg = 0x06
|
GetReceiptsMsg = 0x06
|
||||||
ReceiptsMsg = 0x07
|
ReceiptsMsg = 0x07
|
||||||
GetProofsMsg = 0x08
|
GetProofsV1Msg = 0x08
|
||||||
ProofsMsg = 0x09
|
ProofsV1Msg = 0x09
|
||||||
GetCodeMsg = 0x0a
|
GetCodeMsg = 0x0a
|
||||||
CodeMsg = 0x0b
|
CodeMsg = 0x0b
|
||||||
SendTxMsg = 0x0c
|
SendTxMsg = 0x0c
|
||||||
GetHeaderProofsMsg = 0x0d
|
GetHeaderProofsMsg = 0x0d
|
||||||
HeaderProofsMsg = 0x0e
|
HeaderProofsMsg = 0x0e
|
||||||
|
// Protocol messages belonging to LPV2
|
||||||
|
GetProofsV2Msg = 0x0f
|
||||||
|
ProofsV2Msg = 0x10
|
||||||
|
GetHelperTrieProofsMsg = 0x11
|
||||||
|
HelperTrieProofsMsg = 0x12
|
||||||
|
SendTxV2Msg = 0x13
|
||||||
|
GetTxStatusMsg = 0x14
|
||||||
|
TxStatusMsg = 0x15
|
||||||
)
|
)
|
||||||
|
|
||||||
type errCode int
|
type errCode int
|
||||||
|
|
@ -79,7 +98,7 @@ const (
|
||||||
ErrUnexpectedResponse
|
ErrUnexpectedResponse
|
||||||
ErrInvalidResponse
|
ErrInvalidResponse
|
||||||
ErrTooManyTimeouts
|
ErrTooManyTimeouts
|
||||||
ErrHandshakeMissingKey
|
ErrMissingKey
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e errCode) String() string {
|
func (e errCode) String() string {
|
||||||
|
|
@ -101,7 +120,13 @@ var errorToString = map[int]string{
|
||||||
ErrUnexpectedResponse: "Unexpected response",
|
ErrUnexpectedResponse: "Unexpected response",
|
||||||
ErrInvalidResponse: "Invalid response",
|
ErrInvalidResponse: "Invalid response",
|
||||||
ErrTooManyTimeouts: "Too many request timeouts",
|
ErrTooManyTimeouts: "Too many request timeouts",
|
||||||
ErrHandshakeMissingKey: "Key missing from handshake message",
|
ErrMissingKey: "Key missing from list",
|
||||||
|
}
|
||||||
|
|
||||||
|
type announceBlock struct {
|
||||||
|
Hash common.Hash // Hash of one particular block being announced
|
||||||
|
Number uint64 // Number of one particular block being announced
|
||||||
|
Td *big.Int // Total difficulty of one particular block being announced
|
||||||
}
|
}
|
||||||
|
|
||||||
// announceData is the network packet for the block announcements.
|
// announceData is the network packet for the block announcements.
|
||||||
|
|
@ -113,6 +138,32 @@ type announceData struct {
|
||||||
Update keyValueList
|
Update keyValueList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sign adds a signature to the block announcement by the given privKey
|
||||||
|
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
|
||||||
|
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
||||||
|
sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey)
|
||||||
|
a.Update = a.Update.add("sign", sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSignature verifies if the block announcement has a valid signature by the given pubKey
|
||||||
|
func (a *announceData) checkSignature(pubKey *ecdsa.PublicKey) error {
|
||||||
|
var sig []byte
|
||||||
|
if err := a.Update.decode().get("sign", &sig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
||||||
|
recPubkey, err := secp256k1.RecoverPubkey(crypto.Keccak256(rlp), sig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pbytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
|
||||||
|
if bytes.Equal(pbytes, recPubkey) {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("Wrong signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type blockInfo struct {
|
type blockInfo struct {
|
||||||
Hash common.Hash // Hash of one particular block being announced
|
Hash common.Hash // Hash of one particular block being announced
|
||||||
Number uint64 // Number of one particular block being announced
|
Number uint64 // Number of one particular block being announced
|
||||||
|
|
@ -169,3 +220,9 @@ type CodeData []struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type proofsData [][]rlp.RawValue
|
type proofsData [][]rlp.RawValue
|
||||||
|
|
||||||
|
type txStatus struct {
|
||||||
|
Status core.TxStatus
|
||||||
|
Lookup *core.TxLookupEntry
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
)
|
)
|
||||||
|
|
@ -38,24 +39,32 @@ type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) ligh
|
||||||
|
|
||||||
func TestBlockAccessLes1(t *testing.T) { testAccess(t, 1, tfBlockAccess) }
|
func TestBlockAccessLes1(t *testing.T) { testAccess(t, 1, tfBlockAccess) }
|
||||||
|
|
||||||
|
func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) }
|
||||||
|
|
||||||
func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
||||||
return &light.BlockRequest{Hash: bhash, Number: number}
|
return &light.BlockRequest{Hash: bhash, Number: number}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReceiptsAccessLes1(t *testing.T) { testAccess(t, 1, tfReceiptsAccess) }
|
func TestReceiptsAccessLes1(t *testing.T) { testAccess(t, 1, tfReceiptsAccess) }
|
||||||
|
|
||||||
|
func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) }
|
||||||
|
|
||||||
func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
||||||
return &light.ReceiptsRequest{Hash: bhash, Number: number}
|
return &light.ReceiptsRequest{Hash: bhash, Number: number}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrieEntryAccessLes1(t *testing.T) { testAccess(t, 1, tfTrieEntryAccess) }
|
func TestTrieEntryAccessLes1(t *testing.T) { testAccess(t, 1, tfTrieEntryAccess) }
|
||||||
|
|
||||||
|
func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) }
|
||||||
|
|
||||||
func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
||||||
return &light.TrieRequest{Id: light.StateTrieID(core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))), Key: testBankSecureTrieKey}
|
return &light.TrieRequest{Id: light.StateTrieID(core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))), Key: testBankSecureTrieKey}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodeAccessLes1(t *testing.T) { testAccess(t, 1, tfCodeAccess) }
|
func TestCodeAccessLes1(t *testing.T) { testAccess(t, 1, tfCodeAccess) }
|
||||||
|
|
||||||
|
func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) }
|
||||||
|
|
||||||
func tfCodeAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
func tfCodeAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
|
||||||
header := core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))
|
header := core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))
|
||||||
if header.Number.Uint64() < testContractDeployed {
|
if header.Number.Uint64() < testContractDeployed {
|
||||||
|
|
@ -73,7 +82,7 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
|
||||||
rm := newRetrieveManager(peers, dist, nil)
|
rm := newRetrieveManager(peers, dist, nil)
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
ldb, _ := ethdb.NewMemDatabase()
|
ldb, _ := ethdb.NewMemDatabase()
|
||||||
odr := NewLesOdr(ldb, rm)
|
odr := NewLesOdr(ldb, light.NewChtIndexer(db, true), light.NewBloomTrieIndexer(db, true), eth.NewBloomIndexer(db, light.BloomTrieFrequency), rm)
|
||||||
|
|
||||||
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
||||||
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -111,12 +112,14 @@ func newRetrieveManager(peers *peerSet, dist *requestDistributor, serverPool pee
|
||||||
// that is delivered through the deliver function and successfully validated by the
|
// that is delivered through the deliver function and successfully validated by the
|
||||||
// validator callback. It returns when a valid answer is delivered or the context is
|
// validator callback. It returns when a valid answer is delivered or the context is
|
||||||
// cancelled.
|
// cancelled.
|
||||||
func (rm *retrieveManager) retrieve(ctx context.Context, reqID uint64, req *distReq, val validatorFunc) error {
|
func (rm *retrieveManager) retrieve(ctx context.Context, reqID uint64, req *distReq, val validatorFunc, shutdown chan struct{}) error {
|
||||||
sentReq := rm.sendReq(reqID, req, val)
|
sentReq := rm.sendReq(reqID, req, val)
|
||||||
select {
|
select {
|
||||||
case <-sentReq.stopCh:
|
case <-sentReq.stopCh:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
sentReq.stop(ctx.Err())
|
sentReq.stop(ctx.Err())
|
||||||
|
case <-shutdown:
|
||||||
|
sentReq.stop(fmt.Errorf("Client is shutting down"))
|
||||||
}
|
}
|
||||||
return sentReq.getError()
|
return sentReq.getError()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
176
les/server.go
176
les/server.go
|
|
@ -18,10 +18,11 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -34,7 +35,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LesServer struct {
|
type LesServer struct {
|
||||||
|
|
@ -42,23 +42,55 @@ type LesServer struct {
|
||||||
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
||||||
fcCostStats *requestCostStats
|
fcCostStats *requestCostStats
|
||||||
defParams *flowcontrol.ServerParams
|
defParams *flowcontrol.ServerParams
|
||||||
lesTopic discv5.Topic
|
lesTopics []discv5.Topic
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
quitSync chan struct{}
|
quitSync chan struct{}
|
||||||
|
|
||||||
|
chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
quitSync := make(chan struct{})
|
quitSync := make(chan struct{})
|
||||||
pm, err := NewProtocolManager(eth.BlockChain().Config(), false, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, quitSync, new(sync.WaitGroup))
|
pm, err := NewProtocolManager(eth.BlockChain().Config(), false, ServerProtocolVersions, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, quitSync, new(sync.WaitGroup))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pm.blockLoop()
|
|
||||||
|
lesTopics := make([]discv5.Topic, len(ServerProtocolVersions))
|
||||||
|
for i, pv := range ServerProtocolVersions {
|
||||||
|
lesTopics[i] = lesTopic(eth.BlockChain().Genesis().Hash(), pv)
|
||||||
|
}
|
||||||
|
|
||||||
srv := &LesServer{
|
srv := &LesServer{
|
||||||
protocolManager: pm,
|
protocolManager: pm,
|
||||||
quitSync: quitSync,
|
quitSync: quitSync,
|
||||||
lesTopic: lesTopic(eth.BlockChain().Genesis().Hash()),
|
lesTopics: lesTopics,
|
||||||
|
chtIndexer: light.NewChtIndexer(eth.ChainDb(), false),
|
||||||
|
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false),
|
||||||
}
|
}
|
||||||
|
logger := log.New()
|
||||||
|
|
||||||
|
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
|
||||||
|
chtV2SectionCount := chtV1SectionCount / (light.ChtFrequency / light.ChtV1Frequency)
|
||||||
|
if chtV2SectionCount != 0 {
|
||||||
|
// convert to LES/2 section
|
||||||
|
chtLastSection := chtV2SectionCount - 1
|
||||||
|
// convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead
|
||||||
|
chtLastSectionV1 := (chtLastSection+1)*(light.ChtFrequency/light.ChtV1Frequency) - 1
|
||||||
|
chtSectionHead := srv.chtIndexer.SectionHead(chtLastSectionV1)
|
||||||
|
chtRoot := light.GetChtV2Root(pm.chainDb, chtLastSection, chtSectionHead)
|
||||||
|
logger.Info("CHT", "section", chtLastSection, "sectionHead", fmt.Sprintf("%064x", chtSectionHead), "root", fmt.Sprintf("%064x", chtRoot))
|
||||||
|
}
|
||||||
|
|
||||||
|
bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections()
|
||||||
|
if bloomTrieSectionCount != 0 {
|
||||||
|
bloomTrieLastSection := bloomTrieSectionCount - 1
|
||||||
|
bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection)
|
||||||
|
bloomTrieRoot := light.GetBloomTrieRoot(pm.chainDb, bloomTrieLastSection, bloomTrieSectionHead)
|
||||||
|
logger.Info("BloomTrie", "section", bloomTrieLastSection, "sectionHead", fmt.Sprintf("%064x", bloomTrieSectionHead), "root", fmt.Sprintf("%064x", bloomTrieRoot))
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.chtIndexer.Start(eth.BlockChain())
|
||||||
pm.server = srv
|
pm.server = srv
|
||||||
|
|
||||||
srv.defParams = &flowcontrol.ServerParams{
|
srv.defParams = &flowcontrol.ServerParams{
|
||||||
|
|
@ -77,17 +109,28 @@ func (s *LesServer) Protocols() []p2p.Protocol {
|
||||||
// Start starts the LES server
|
// Start starts the LES server
|
||||||
func (s *LesServer) Start(srvr *p2p.Server) {
|
func (s *LesServer) Start(srvr *p2p.Server) {
|
||||||
s.protocolManager.Start()
|
s.protocolManager.Start()
|
||||||
|
for _, topic := range s.lesTopics {
|
||||||
|
topic := topic
|
||||||
go func() {
|
go func() {
|
||||||
logger := log.New("topic", s.lesTopic)
|
logger := log.New("topic", topic)
|
||||||
logger.Info("Starting topic registration")
|
logger.Info("Starting topic registration")
|
||||||
defer logger.Info("Terminated topic registration")
|
defer logger.Info("Terminated topic registration")
|
||||||
|
|
||||||
srvr.DiscV5.RegisterTopic(s.lesTopic, s.quitSync)
|
srvr.DiscV5.RegisterTopic(topic, s.quitSync)
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
s.privateKey = srvr.PrivateKey
|
||||||
|
s.protocolManager.blockLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
|
||||||
|
bloomIndexer.AddChildIndexer(s.bloomTrieIndexer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the LES service
|
// Stop stops the LES service
|
||||||
func (s *LesServer) Stop() {
|
func (s *LesServer) Stop() {
|
||||||
|
s.chtIndexer.Close()
|
||||||
|
// bloom trie indexer is closed by parent bloombits indexer
|
||||||
s.fcCostStats.store()
|
s.fcCostStats.store()
|
||||||
s.fcManager.Stop()
|
s.fcManager.Stop()
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -273,10 +316,7 @@ func (pm *ProtocolManager) blockLoop() {
|
||||||
pm.wg.Add(1)
|
pm.wg.Add(1)
|
||||||
headCh := make(chan core.ChainHeadEvent, 10)
|
headCh := make(chan core.ChainHeadEvent, 10)
|
||||||
headSub := pm.blockchain.SubscribeChainHeadEvent(headCh)
|
headSub := pm.blockchain.SubscribeChainHeadEvent(headCh)
|
||||||
newCht := make(chan struct{}, 10)
|
|
||||||
newCht <- struct{}{}
|
|
||||||
go func() {
|
go func() {
|
||||||
var mu sync.Mutex
|
|
||||||
var lastHead *types.Header
|
var lastHead *types.Header
|
||||||
lastBroadcastTd := common.Big0
|
lastBroadcastTd := common.Big0
|
||||||
for {
|
for {
|
||||||
|
|
@ -299,26 +339,37 @@ func (pm *ProtocolManager) blockLoop() {
|
||||||
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
|
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
|
||||||
|
|
||||||
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}
|
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}
|
||||||
|
var (
|
||||||
|
signed bool
|
||||||
|
signedAnnounce announceData
|
||||||
|
)
|
||||||
|
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
|
switch p.announceType {
|
||||||
|
|
||||||
|
case announceTypeSimple:
|
||||||
select {
|
select {
|
||||||
case p.announceChn <- announce:
|
case p.announceChn <- announce:
|
||||||
default:
|
default:
|
||||||
pm.removePeer(p.id)
|
pm.removePeer(p.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case announceTypeSigned:
|
||||||
|
if !signed {
|
||||||
|
signedAnnounce = announce
|
||||||
|
signedAnnounce.sign(pm.server.privateKey)
|
||||||
|
signed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p.announceChn <- signedAnnounce:
|
||||||
|
default:
|
||||||
|
pm.removePeer(p.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newCht <- struct{}{}
|
|
||||||
case <-newCht:
|
|
||||||
go func() {
|
|
||||||
mu.Lock()
|
|
||||||
more := makeCht(pm.chainDb)
|
|
||||||
mu.Unlock()
|
|
||||||
if more {
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
newCht <- struct{}{}
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
case <-pm.quitSync:
|
case <-pm.quitSync:
|
||||||
headSub.Unsubscribe()
|
headSub.Unsubscribe()
|
||||||
pm.wg.Done()
|
pm.wg.Done()
|
||||||
|
|
@ -327,86 +378,3 @@ func (pm *ProtocolManager) blockLoop() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
lastChtKey = []byte("LastChtNumber") // chtNum (uint64 big endian)
|
|
||||||
chtPrefix = []byte("cht") // chtPrefix + chtNum (uint64 big endian) -> trie root hash
|
|
||||||
)
|
|
||||||
|
|
||||||
func getChtRoot(db ethdb.Database, num uint64) common.Hash {
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], num)
|
|
||||||
data, _ := db.Get(append(chtPrefix, encNumber[:]...))
|
|
||||||
return common.BytesToHash(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func storeChtRoot(db ethdb.Database, num uint64, root common.Hash) {
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], num)
|
|
||||||
db.Put(append(chtPrefix, encNumber[:]...), root[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeCht(db ethdb.Database) bool {
|
|
||||||
headHash := core.GetHeadBlockHash(db)
|
|
||||||
headNum := core.GetBlockNumber(db, headHash)
|
|
||||||
|
|
||||||
var newChtNum uint64
|
|
||||||
if headNum > light.ChtConfirmations {
|
|
||||||
newChtNum = (headNum - light.ChtConfirmations) / light.ChtFrequency
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastChtNum uint64
|
|
||||||
data, _ := db.Get(lastChtKey)
|
|
||||||
if len(data) == 8 {
|
|
||||||
lastChtNum = binary.BigEndian.Uint64(data[:])
|
|
||||||
}
|
|
||||||
if newChtNum <= lastChtNum {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var t *trie.Trie
|
|
||||||
if lastChtNum > 0 {
|
|
||||||
var err error
|
|
||||||
t, err = trie.New(getChtRoot(db, lastChtNum), db)
|
|
||||||
if err != nil {
|
|
||||||
lastChtNum = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lastChtNum == 0 {
|
|
||||||
t, _ = trie.New(common.Hash{}, db)
|
|
||||||
}
|
|
||||||
|
|
||||||
for num := lastChtNum * light.ChtFrequency; num < (lastChtNum+1)*light.ChtFrequency; num++ {
|
|
||||||
hash := core.GetCanonicalHash(db, num)
|
|
||||||
if hash == (common.Hash{}) {
|
|
||||||
panic("Canonical hash not found")
|
|
||||||
}
|
|
||||||
td := core.GetTd(db, hash, num)
|
|
||||||
if td == nil {
|
|
||||||
panic("TD not found")
|
|
||||||
}
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], num)
|
|
||||||
var node light.ChtNode
|
|
||||||
node.Hash = hash
|
|
||||||
node.Td = td
|
|
||||||
data, _ := rlp.EncodeToBytes(node)
|
|
||||||
t.Update(encNumber[:], data)
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := t.Commit()
|
|
||||||
if err != nil {
|
|
||||||
lastChtNum = 0
|
|
||||||
} else {
|
|
||||||
lastChtNum++
|
|
||||||
|
|
||||||
log.Trace("Generated CHT", "number", lastChtNum, "root", root.Hex())
|
|
||||||
|
|
||||||
storeChtRoot(db, lastChtNum, root)
|
|
||||||
var data [8]byte
|
|
||||||
binary.BigEndian.PutUint64(data[:], lastChtNum)
|
|
||||||
db.Put(lastChtKey, data[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return newChtNum > lastChtNum
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -145,15 +145,15 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
|
||||||
pool.wg.Add(1)
|
pool.wg.Add(1)
|
||||||
pool.loadNodes()
|
pool.loadNodes()
|
||||||
|
|
||||||
go pool.eventLoop()
|
|
||||||
|
|
||||||
pool.checkDial()
|
|
||||||
if pool.server.DiscV5 != nil {
|
if pool.server.DiscV5 != nil {
|
||||||
pool.discSetPeriod = make(chan time.Duration, 1)
|
pool.discSetPeriod = make(chan time.Duration, 1)
|
||||||
pool.discNodes = make(chan *discv5.Node, 100)
|
pool.discNodes = make(chan *discv5.Node, 100)
|
||||||
pool.discLookups = make(chan bool, 100)
|
pool.discLookups = make(chan bool, 100)
|
||||||
go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
|
go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go pool.eventLoop()
|
||||||
|
pool.checkDial()
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect should be called upon any incoming connection. If the connection has been
|
// connect should be called upon any incoming connection. If the connection has been
|
||||||
|
|
|
||||||
|
|
@ -95,15 +95,8 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
|
||||||
if bc.genesisBlock == nil {
|
if bc.genesisBlock == nil {
|
||||||
return nil, core.ErrNoGenesis
|
return nil, core.ErrNoGenesis
|
||||||
}
|
}
|
||||||
if bc.genesisBlock.Hash() == params.MainnetGenesisHash {
|
if cp, ok := trustedCheckpoints[bc.genesisBlock.Hash()]; ok {
|
||||||
// add trusted CHT
|
bc.addTrustedCheckpoint(cp)
|
||||||
WriteTrustedCht(bc.chainDb, TrustedCht{Number: 1040, Root: common.HexToHash("bb4fb4076cbe6923c8a8ce8f158452bbe19564959313466989fda095a60884ca")})
|
|
||||||
log.Info("Added trusted CHT for mainnet")
|
|
||||||
}
|
|
||||||
if bc.genesisBlock.Hash() == params.TestnetGenesisHash {
|
|
||||||
// add trusted CHT
|
|
||||||
WriteTrustedCht(bc.chainDb, TrustedCht{Number: 400, Root: common.HexToHash("2a4befa19e4675d939c3dc22dca8c6ae9fcd642be1f04b06bd6e4203cc304660")})
|
|
||||||
log.Info("Added trusted CHT for ropsten testnet")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bc.loadLastState(); err != nil {
|
if err := bc.loadLastState(); err != nil {
|
||||||
|
|
@ -120,6 +113,22 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
|
||||||
return bc, nil
|
return bc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addTrustedCheckpoint adds a trusted checkpoint to the blockchain
|
||||||
|
func (self *LightChain) addTrustedCheckpoint(cp trustedCheckpoint) {
|
||||||
|
if self.odr.ChtIndexer() != nil {
|
||||||
|
StoreChtRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.chtRoot)
|
||||||
|
self.odr.ChtIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
||||||
|
}
|
||||||
|
if self.odr.BloomTrieIndexer() != nil {
|
||||||
|
StoreBloomTrieRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.bloomTrieRoot)
|
||||||
|
self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
||||||
|
}
|
||||||
|
if self.odr.BloomIndexer() != nil {
|
||||||
|
self.odr.BloomIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
||||||
|
}
|
||||||
|
log.Info("Added trusted checkpoint", "chain name", cp.name)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *LightChain) getProcInterrupt() bool {
|
func (self *LightChain) getProcInterrupt() bool {
|
||||||
return atomic.LoadInt32(&self.procInterrupt) == 1
|
return atomic.LoadInt32(&self.procInterrupt) == 1
|
||||||
}
|
}
|
||||||
|
|
@ -449,10 +458,13 @@ func (self *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *LightChain) SyncCht(ctx context.Context) bool {
|
func (self *LightChain) SyncCht(ctx context.Context) bool {
|
||||||
|
if self.odr.ChtIndexer() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
headNum := self.CurrentHeader().Number.Uint64()
|
headNum := self.CurrentHeader().Number.Uint64()
|
||||||
cht := GetTrustedCht(self.chainDb)
|
chtCount, _, _ := self.odr.ChtIndexer().Sections()
|
||||||
if headNum+1 < cht.Number*ChtFrequency {
|
if headNum+1 < chtCount*ChtFrequency {
|
||||||
num := cht.Number*ChtFrequency - 1
|
num := chtCount*ChtFrequency - 1
|
||||||
header, err := GetHeaderByNumber(ctx, self.odr, num)
|
header, err := GetHeaderByNumber(ctx, self.odr, num)
|
||||||
if header != nil && err == nil {
|
if header != nil && err == nil {
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
|
|
|
||||||
141
light/nodeset.go
Normal file
141
light/nodeset.go
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright 2014 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 light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeSet stores a set of trie nodes. It implements trie.Database and can also
|
||||||
|
// act as a cache for another trie.Database.
|
||||||
|
type NodeSet struct {
|
||||||
|
db map[string][]byte
|
||||||
|
dataSize int
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNodeSet creates an empty node set
|
||||||
|
func NewNodeSet() *NodeSet {
|
||||||
|
return &NodeSet{
|
||||||
|
db: make(map[string][]byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put stores a new node in the set
|
||||||
|
func (db *NodeSet) Put(key []byte, value []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if _, ok := db.db[string(key)]; !ok {
|
||||||
|
db.db[string(key)] = common.CopyBytes(value)
|
||||||
|
db.dataSize += len(value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a stored node
|
||||||
|
func (db *NodeSet) Get(key []byte) ([]byte, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if entry, ok := db.db[string(key)]; ok {
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns true if the node set contains the given key
|
||||||
|
func (db *NodeSet) Has(key []byte) (bool, error) {
|
||||||
|
_, err := db.Get(key)
|
||||||
|
return err == nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyCount returns the number of nodes in the set
|
||||||
|
func (db *NodeSet) KeyCount() int {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
return len(db.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSize returns the aggregated data size of nodes in the set
|
||||||
|
func (db *NodeSet) DataSize() int {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
return db.dataSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeList converts the node set to a NodeList
|
||||||
|
func (db *NodeSet) NodeList() NodeList {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
var values NodeList
|
||||||
|
for _, value := range db.db {
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store writes the contents of the set to the given database
|
||||||
|
func (db *NodeSet) Store(target trie.Database) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
for key, value := range db.db {
|
||||||
|
target.Put([]byte(key), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeList stores an ordered list of trie nodes. It implements trie.DatabaseWriter.
|
||||||
|
type NodeList []rlp.RawValue
|
||||||
|
|
||||||
|
// Store writes the contents of the list to the given database
|
||||||
|
func (n NodeList) Store(db trie.Database) {
|
||||||
|
for _, node := range n {
|
||||||
|
db.Put(crypto.Keccak256(node), node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeSet converts the node list to a NodeSet
|
||||||
|
func (n NodeList) NodeSet() *NodeSet {
|
||||||
|
db := NewNodeSet()
|
||||||
|
n.Store(db)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put stores a new node at the end of the list
|
||||||
|
func (n *NodeList) Put(key []byte, value []byte) error {
|
||||||
|
*n = append(*n, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSize returns the aggregated data size of nodes in the list
|
||||||
|
func (n NodeList) DataSize() int {
|
||||||
|
var size int
|
||||||
|
for _, node := range n {
|
||||||
|
size += len(node)
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
48
light/odr.go
48
light/odr.go
|
|
@ -25,9 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NoOdr is the default context passed to an ODR capable function when the ODR
|
// NoOdr is the default context passed to an ODR capable function when the ODR
|
||||||
|
|
@ -37,6 +35,9 @@ var NoOdr = context.Background()
|
||||||
// OdrBackend is an interface to a backend service that handles ODR retrievals type
|
// OdrBackend is an interface to a backend service that handles ODR retrievals type
|
||||||
type OdrBackend interface {
|
type OdrBackend interface {
|
||||||
Database() ethdb.Database
|
Database() ethdb.Database
|
||||||
|
ChtIndexer() *core.ChainIndexer
|
||||||
|
BloomTrieIndexer() *core.ChainIndexer
|
||||||
|
BloomIndexer() *core.ChainIndexer
|
||||||
Retrieve(ctx context.Context, req OdrRequest) error
|
Retrieve(ctx context.Context, req OdrRequest) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,23 +81,12 @@ type TrieRequest struct {
|
||||||
OdrRequest
|
OdrRequest
|
||||||
Id *TrieID
|
Id *TrieID
|
||||||
Key []byte
|
Key []byte
|
||||||
Proof []rlp.RawValue
|
Proof *NodeSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
// StoreResult stores the retrieved data in local database
|
||||||
func (req *TrieRequest) StoreResult(db ethdb.Database) {
|
func (req *TrieRequest) StoreResult(db ethdb.Database) {
|
||||||
storeProof(db, req.Proof)
|
req.Proof.Store(db)
|
||||||
}
|
|
||||||
|
|
||||||
// storeProof stores the new trie nodes obtained from a merkle proof in the database
|
|
||||||
func storeProof(db ethdb.Database, proof []rlp.RawValue) {
|
|
||||||
for _, buf := range proof {
|
|
||||||
hash := crypto.Keccak256(buf)
|
|
||||||
val, _ := db.Get(hash)
|
|
||||||
if val == nil {
|
|
||||||
db.Put(hash, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeRequest is the ODR request type for retrieving contract code
|
// CodeRequest is the ODR request type for retrieving contract code
|
||||||
|
|
@ -138,14 +128,14 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
|
||||||
core.WriteBlockReceipts(db, req.Hash, req.Number, req.Receipts)
|
core.WriteBlockReceipts(db, req.Hash, req.Number, req.Receipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrieRequest is the ODR request type for state/storage trie entries
|
// ChtRequest is the ODR request type for state/storage trie entries
|
||||||
type ChtRequest struct {
|
type ChtRequest struct {
|
||||||
OdrRequest
|
OdrRequest
|
||||||
ChtNum, BlockNum uint64
|
ChtNum, BlockNum uint64
|
||||||
ChtRoot common.Hash
|
ChtRoot common.Hash
|
||||||
Header *types.Header
|
Header *types.Header
|
||||||
Td *big.Int
|
Td *big.Int
|
||||||
Proof []rlp.RawValue
|
Proof *NodeSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
// StoreResult stores the retrieved data in local database
|
||||||
|
|
@ -155,5 +145,27 @@ func (req *ChtRequest) StoreResult(db ethdb.Database) {
|
||||||
hash, num := req.Header.Hash(), req.Header.Number.Uint64()
|
hash, num := req.Header.Hash(), req.Header.Number.Uint64()
|
||||||
core.WriteTd(db, hash, num, req.Td)
|
core.WriteTd(db, hash, num, req.Td)
|
||||||
core.WriteCanonicalHash(db, hash, num)
|
core.WriteCanonicalHash(db, hash, num)
|
||||||
//storeProof(db, req.Proof)
|
}
|
||||||
|
|
||||||
|
// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure
|
||||||
|
type BloomRequest struct {
|
||||||
|
OdrRequest
|
||||||
|
BloomTrieNum uint64
|
||||||
|
BitIdx uint
|
||||||
|
SectionIdxList []uint64
|
||||||
|
BloomTrieRoot common.Hash
|
||||||
|
BloomBits [][]byte
|
||||||
|
Proofs *NodeSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreResult stores the retrieved data in local database
|
||||||
|
func (req *BloomRequest) StoreResult(db ethdb.Database) {
|
||||||
|
for i, sectionIdx := range req.SectionIdxList {
|
||||||
|
sectionHead := core.GetCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1)
|
||||||
|
// if we don't have the canonical hash stored for this section head number, we'll still store it under
|
||||||
|
// a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical
|
||||||
|
// hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the
|
||||||
|
// bit vector again from the network.
|
||||||
|
core.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,9 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
|
||||||
req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash))
|
req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash))
|
||||||
case *TrieRequest:
|
case *TrieRequest:
|
||||||
t, _ := trie.New(req.Id.Root, odr.sdb)
|
t, _ := trie.New(req.Id.Root, odr.sdb)
|
||||||
req.Proof = t.Prove(req.Key)
|
nodes := NewNodeSet()
|
||||||
|
t.Prove(req.Key, 0, nodes)
|
||||||
|
req.Proof = nodes
|
||||||
case *CodeRequest:
|
case *CodeRequest:
|
||||||
req.Data, _ = odr.sdb.Get(req.Hash[:])
|
req.Data, _ = odr.sdb.Get(req.Hash[:])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,56 +19,16 @@ package light
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sha3_nil = crypto.Keccak256Hash(nil)
|
var sha3_nil = crypto.Keccak256Hash(nil)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoTrustedCht = errors.New("No trusted canonical hash trie")
|
|
||||||
ErrNoHeader = errors.New("Header not found")
|
|
||||||
|
|
||||||
ChtFrequency = uint64(4096)
|
|
||||||
ChtConfirmations = uint64(2048)
|
|
||||||
trustedChtKey = []byte("TrustedCHT")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChtNode struct {
|
|
||||||
Hash common.Hash
|
|
||||||
Td *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
type TrustedCht struct {
|
|
||||||
Number uint64
|
|
||||||
Root common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTrustedCht(db ethdb.Database) TrustedCht {
|
|
||||||
data, _ := db.Get(trustedChtKey)
|
|
||||||
var res TrustedCht
|
|
||||||
if err := rlp.DecodeBytes(data, &res); err != nil {
|
|
||||||
return TrustedCht{0, common.Hash{}}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteTrustedCht(db ethdb.Database, cht TrustedCht) {
|
|
||||||
data, _ := rlp.EncodeToBytes(cht)
|
|
||||||
db.Put(trustedChtKey, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTrustedCht(db ethdb.Database) {
|
|
||||||
db.Delete(trustedChtKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
|
func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
|
||||||
db := odr.Database()
|
db := odr.Database()
|
||||||
hash := core.GetCanonicalHash(db, number)
|
hash := core.GetCanonicalHash(db, number)
|
||||||
|
|
@ -81,12 +41,29 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ
|
||||||
return header, nil
|
return header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cht := GetTrustedCht(db)
|
var (
|
||||||
if number >= cht.Number*ChtFrequency {
|
chtCount, sectionHeadNum uint64
|
||||||
|
sectionHead common.Hash
|
||||||
|
)
|
||||||
|
if odr.ChtIndexer() != nil {
|
||||||
|
chtCount, sectionHeadNum, sectionHead = odr.ChtIndexer().Sections()
|
||||||
|
canonicalHash := core.GetCanonicalHash(db, sectionHeadNum)
|
||||||
|
// if the CHT was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too
|
||||||
|
for chtCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) {
|
||||||
|
chtCount--
|
||||||
|
if chtCount > 0 {
|
||||||
|
sectionHeadNum = chtCount*ChtFrequency - 1
|
||||||
|
sectionHead = odr.ChtIndexer().SectionHead(chtCount - 1)
|
||||||
|
canonicalHash = core.GetCanonicalHash(db, sectionHeadNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if number >= chtCount*ChtFrequency {
|
||||||
return nil, ErrNoTrustedCht
|
return nil, ErrNoTrustedCht
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &ChtRequest{ChtRoot: cht.Root, ChtNum: cht.Number, BlockNum: number}
|
r := &ChtRequest{ChtRoot: GetChtRoot(db, chtCount-1, sectionHead), ChtNum: chtCount - 1, BlockNum: number}
|
||||||
if err := odr.Retrieve(ctx, r); err != nil {
|
if err := odr.Retrieve(ctx, r); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -162,3 +139,61 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
|
||||||
}
|
}
|
||||||
return r.Receipts, nil
|
return r.Receipts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes
|
||||||
|
func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxList []uint64) ([][]byte, error) {
|
||||||
|
db := odr.Database()
|
||||||
|
result := make([][]byte, len(sectionIdxList))
|
||||||
|
var (
|
||||||
|
reqList []uint64
|
||||||
|
reqIdx []int
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bloomTrieCount, sectionHeadNum uint64
|
||||||
|
sectionHead common.Hash
|
||||||
|
)
|
||||||
|
if odr.BloomTrieIndexer() != nil {
|
||||||
|
bloomTrieCount, sectionHeadNum, sectionHead = odr.BloomTrieIndexer().Sections()
|
||||||
|
canonicalHash := core.GetCanonicalHash(db, sectionHeadNum)
|
||||||
|
// if the BloomTrie was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too
|
||||||
|
for bloomTrieCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) {
|
||||||
|
bloomTrieCount--
|
||||||
|
if bloomTrieCount > 0 {
|
||||||
|
sectionHeadNum = bloomTrieCount*BloomTrieFrequency - 1
|
||||||
|
sectionHead = odr.BloomTrieIndexer().SectionHead(bloomTrieCount - 1)
|
||||||
|
canonicalHash = core.GetCanonicalHash(db, sectionHeadNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sectionIdx := range sectionIdxList {
|
||||||
|
sectionHead := core.GetCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1)
|
||||||
|
// if we don't have the canonical hash stored for this section head number, we'll still look for
|
||||||
|
// an entry with a zero sectionHead (we store it with zero section head too if we don't know it
|
||||||
|
// at the time of the retrieval)
|
||||||
|
bloomBits, err := core.GetBloomBits(db, bitIdx, sectionIdx, sectionHead)
|
||||||
|
if err == nil {
|
||||||
|
result[i] = bloomBits
|
||||||
|
} else {
|
||||||
|
if sectionIdx >= bloomTrieCount {
|
||||||
|
return nil, ErrNoTrustedBloomTrie
|
||||||
|
}
|
||||||
|
reqList = append(reqList, sectionIdx)
|
||||||
|
reqIdx = append(reqIdx, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reqList == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &BloomRequest{BloomTrieRoot: GetBloomTrieRoot(db, bloomTrieCount-1, sectionHead), BloomTrieNum: bloomTrieCount - 1, BitIdx: bitIdx, SectionIdxList: reqList}
|
||||||
|
if err := odr.Retrieve(ctx, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
for i, idx := range reqIdx {
|
||||||
|
result[idx] = r.BloomBits[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
295
light/postprocess.go
Normal file
295
light/postprocess.go
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
// Copyright 2016 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 light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChtFrequency = 32768
|
||||||
|
ChtV1Frequency = 4096 // as long as we want to retain LES/1 compatibility, servers generate CHTs with the old, higher frequency
|
||||||
|
HelperTrieConfirmations = 2048 // number of confirmations before a server is expected to have the given HelperTrie available
|
||||||
|
HelperTrieProcessConfirmations = 256 // number of confirmations before a HelperTrie is generated
|
||||||
|
)
|
||||||
|
|
||||||
|
// trustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with
|
||||||
|
// the appropriate section index and head hash. It is used to start light syncing from this checkpoint
|
||||||
|
// and avoid downloading the entire header chain while still being able to securely access old headers/logs.
|
||||||
|
type trustedCheckpoint struct {
|
||||||
|
name string
|
||||||
|
sectionIdx uint64
|
||||||
|
sectionHead, chtRoot, bloomTrieRoot common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mainnetCheckpoint = trustedCheckpoint{
|
||||||
|
name: "ETH mainnet",
|
||||||
|
sectionIdx: 129,
|
||||||
|
sectionHead: common.HexToHash("64100587c8ec9a76870056d07cb0f58622552d16de6253a59cac4b580c899501"),
|
||||||
|
chtRoot: common.HexToHash("bb4fb4076cbe6923c8a8ce8f158452bbe19564959313466989fda095a60884ca"),
|
||||||
|
bloomTrieRoot: common.HexToHash("0db524b2c4a2a9520a42fd842b02d2e8fb58ff37c75cf57bd0eb82daeace6716"),
|
||||||
|
}
|
||||||
|
|
||||||
|
ropstenCheckpoint = trustedCheckpoint{
|
||||||
|
name: "Ropsten testnet",
|
||||||
|
sectionIdx: 50,
|
||||||
|
sectionHead: common.HexToHash("00bd65923a1aa67f85e6b4ae67835784dd54be165c37f056691723c55bf016bd"),
|
||||||
|
chtRoot: common.HexToHash("6f56dc61936752cc1f8c84b4addabdbe6a1c19693de3f21cb818362df2117f03"),
|
||||||
|
bloomTrieRoot: common.HexToHash("aca7d7c504d22737242effc3fdc604a762a0af9ced898036b5986c3a15220208"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// trustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to
|
||||||
|
var trustedCheckpoints = map[common.Hash]trustedCheckpoint{
|
||||||
|
params.MainnetGenesisHash: mainnetCheckpoint,
|
||||||
|
params.TestnetGenesisHash: ropstenCheckpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoTrustedCht = errors.New("No trusted canonical hash trie")
|
||||||
|
ErrNoTrustedBloomTrie = errors.New("No trusted bloom trie")
|
||||||
|
ErrNoHeader = errors.New("Header not found")
|
||||||
|
chtPrefix = []byte("chtRoot-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash
|
||||||
|
ChtTablePrefix = "cht-"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format
|
||||||
|
type ChtNode struct {
|
||||||
|
Hash common.Hash
|
||||||
|
Td *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChtRoot reads the CHT root assoctiated to the given section from the database
|
||||||
|
// Note that sectionIdx is specified according to LES/1 CHT section size
|
||||||
|
func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
|
||||||
|
var encNumber [8]byte
|
||||||
|
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
||||||
|
data, _ := db.Get(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...))
|
||||||
|
return common.BytesToHash(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChtV2Root reads the CHT root assoctiated to the given section from the database
|
||||||
|
// Note that sectionIdx is specified according to LES/2 CHT section size
|
||||||
|
func GetChtV2Root(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
|
||||||
|
return GetChtRoot(db, (sectionIdx+1)*(ChtFrequency/ChtV1Frequency)-1, sectionHead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreChtRoot writes the CHT root assoctiated to the given section into the database
|
||||||
|
// Note that sectionIdx is specified according to LES/1 CHT section size
|
||||||
|
func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
|
||||||
|
var encNumber [8]byte
|
||||||
|
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
||||||
|
db.Put(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChtIndexerBackend implements core.ChainIndexerBackend
|
||||||
|
type ChtIndexerBackend struct {
|
||||||
|
db, cdb ethdb.Database
|
||||||
|
section, sectionSize uint64
|
||||||
|
lastHash common.Hash
|
||||||
|
trie *trie.Trie
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
||||||
|
func NewChtIndexer(db ethdb.Database, clientMode bool) *core.ChainIndexer {
|
||||||
|
cdb := ethdb.NewTable(db, ChtTablePrefix)
|
||||||
|
idb := ethdb.NewTable(db, "chtIndex-")
|
||||||
|
var sectionSize, confirmReq uint64
|
||||||
|
if clientMode {
|
||||||
|
sectionSize = ChtFrequency
|
||||||
|
confirmReq = HelperTrieConfirmations
|
||||||
|
} else {
|
||||||
|
sectionSize = ChtV1Frequency
|
||||||
|
confirmReq = HelperTrieProcessConfirmations
|
||||||
|
}
|
||||||
|
return core.NewChainIndexer(db, idb, &ChtIndexerBackend{db: db, cdb: cdb, sectionSize: sectionSize}, sectionSize, confirmReq, time.Millisecond*100, "cht")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset implements core.ChainIndexerBackend
|
||||||
|
func (c *ChtIndexerBackend) Reset(section uint64, lastSectionHead common.Hash) error {
|
||||||
|
var root common.Hash
|
||||||
|
if section > 0 {
|
||||||
|
root = GetChtRoot(c.db, section-1, lastSectionHead)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
c.trie, err = trie.New(root, c.cdb)
|
||||||
|
c.section = section
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements core.ChainIndexerBackend
|
||||||
|
func (c *ChtIndexerBackend) Process(header *types.Header) {
|
||||||
|
hash, num := header.Hash(), header.Number.Uint64()
|
||||||
|
c.lastHash = hash
|
||||||
|
|
||||||
|
td := core.GetTd(c.db, hash, num)
|
||||||
|
if td == nil {
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
|
var encNumber [8]byte
|
||||||
|
binary.BigEndian.PutUint64(encNumber[:], num)
|
||||||
|
data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
|
||||||
|
c.trie.Update(encNumber[:], data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit implements core.ChainIndexerBackend
|
||||||
|
func (c *ChtIndexerBackend) Commit() error {
|
||||||
|
batch := c.cdb.NewBatch()
|
||||||
|
root, err := c.trie.CommitTo(batch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
batch.Write()
|
||||||
|
if ((c.section+1)*c.sectionSize)%ChtFrequency == 0 {
|
||||||
|
log.Info("Storing CHT", "idx", c.section*c.sectionSize/ChtFrequency, "sectionHead", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
|
||||||
|
}
|
||||||
|
StoreChtRoot(c.db, c.section, c.lastHash, root)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
BloomTrieFrequency = 32768
|
||||||
|
ethBloomBitsSection = 4096
|
||||||
|
ethBloomBitsConfirmations = 256
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bloomTriePrefix = []byte("bltRoot-") // bloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash
|
||||||
|
BloomTrieTablePrefix = "blt-"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetBloomTrieRoot reads the BloomTrie root assoctiated to the given section from the database
|
||||||
|
func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
|
||||||
|
var encNumber [8]byte
|
||||||
|
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
||||||
|
data, _ := db.Get(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...))
|
||||||
|
return common.BytesToHash(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreBloomTrieRoot writes the BloomTrie root assoctiated to the given section into the database
|
||||||
|
func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
|
||||||
|
var encNumber [8]byte
|
||||||
|
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
||||||
|
db.Put(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// BloomTrieIndexerBackend implements core.ChainIndexerBackend
|
||||||
|
type BloomTrieIndexerBackend struct {
|
||||||
|
db, cdb ethdb.Database
|
||||||
|
section, parentSectionSize, bloomTrieRatio uint64
|
||||||
|
trie *trie.Trie
|
||||||
|
sectionHeads []common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
||||||
|
func NewBloomTrieIndexer(db ethdb.Database, clientMode bool) *core.ChainIndexer {
|
||||||
|
cdb := ethdb.NewTable(db, BloomTrieTablePrefix)
|
||||||
|
idb := ethdb.NewTable(db, "bltIndex-")
|
||||||
|
backend := &BloomTrieIndexerBackend{db: db, cdb: cdb}
|
||||||
|
var confirmReq uint64
|
||||||
|
if clientMode {
|
||||||
|
backend.parentSectionSize = BloomTrieFrequency
|
||||||
|
confirmReq = HelperTrieConfirmations
|
||||||
|
} else {
|
||||||
|
backend.parentSectionSize = ethBloomBitsSection
|
||||||
|
confirmReq = HelperTrieProcessConfirmations
|
||||||
|
}
|
||||||
|
backend.bloomTrieRatio = BloomTrieFrequency / backend.parentSectionSize
|
||||||
|
backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
|
||||||
|
return core.NewChainIndexer(db, idb, backend, BloomTrieFrequency, confirmReq-ethBloomBitsConfirmations, time.Millisecond*100, "bloomtrie")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset implements core.ChainIndexerBackend
|
||||||
|
func (b *BloomTrieIndexerBackend) Reset(section uint64, lastSectionHead common.Hash) error {
|
||||||
|
var root common.Hash
|
||||||
|
if section > 0 {
|
||||||
|
root = GetBloomTrieRoot(b.db, section-1, lastSectionHead)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
b.trie, err = trie.New(root, b.cdb)
|
||||||
|
b.section = section
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements core.ChainIndexerBackend
|
||||||
|
func (b *BloomTrieIndexerBackend) Process(header *types.Header) {
|
||||||
|
num := header.Number.Uint64() - b.section*BloomTrieFrequency
|
||||||
|
if (num+1)%b.parentSectionSize == 0 {
|
||||||
|
b.sectionHeads[num/b.parentSectionSize] = header.Hash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit implements core.ChainIndexerBackend
|
||||||
|
func (b *BloomTrieIndexerBackend) Commit() error {
|
||||||
|
var compSize, decompSize uint64
|
||||||
|
|
||||||
|
for i := uint(0); i < types.BloomBitLength; i++ {
|
||||||
|
var encKey [10]byte
|
||||||
|
binary.BigEndian.PutUint16(encKey[0:2], uint16(i))
|
||||||
|
binary.BigEndian.PutUint64(encKey[2:10], b.section)
|
||||||
|
var decomp []byte
|
||||||
|
for j := uint64(0); j < b.bloomTrieRatio; j++ {
|
||||||
|
data, err := core.GetBloomBits(b.db, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSectionSize/8))
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
decomp = append(decomp, decompData...)
|
||||||
|
}
|
||||||
|
comp := bitutil.CompressBytes(decomp)
|
||||||
|
|
||||||
|
decompSize += uint64(len(decomp))
|
||||||
|
compSize += uint64(len(comp))
|
||||||
|
if len(comp) > 0 {
|
||||||
|
b.trie.Update(encKey[:], comp)
|
||||||
|
} else {
|
||||||
|
b.trie.Delete(encKey[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := b.cdb.NewBatch()
|
||||||
|
root, err := b.trie.CommitTo(batch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
batch.Write()
|
||||||
|
sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
|
||||||
|
log.Info("Storing BloomTrie", "section", b.section, "sectionHead", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression ratio", float64(compSize)/float64(decompSize))
|
||||||
|
StoreBloomTrieRoot(b.db, b.section, sectionHead, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -112,10 +112,10 @@ func CollectProcessMetrics(refresh time.Duration) {
|
||||||
memPauses.Mark(int64(memstats[i%2].PauseTotalNs - memstats[(i-1)%2].PauseTotalNs))
|
memPauses.Mark(int64(memstats[i%2].PauseTotalNs - memstats[(i-1)%2].PauseTotalNs))
|
||||||
|
|
||||||
if ReadDiskStats(diskstats[i%2]) == nil {
|
if ReadDiskStats(diskstats[i%2]) == nil {
|
||||||
diskReads.Mark(int64(diskstats[i%2].ReadCount - diskstats[(i-1)%2].ReadCount))
|
diskReads.Mark(diskstats[i%2].ReadCount - diskstats[(i-1)%2].ReadCount)
|
||||||
diskReadBytes.Mark(int64(diskstats[i%2].ReadBytes - diskstats[(i-1)%2].ReadBytes))
|
diskReadBytes.Mark(diskstats[i%2].ReadBytes - diskstats[(i-1)%2].ReadBytes)
|
||||||
diskWrites.Mark(int64(diskstats[i%2].WriteCount - diskstats[(i-1)%2].WriteCount))
|
diskWrites.Mark(diskstats[i%2].WriteCount - diskstats[(i-1)%2].WriteCount)
|
||||||
diskWriteBytes.Mark(int64(diskstats[i%2].WriteBytes - diskstats[(i-1)%2].WriteBytes))
|
diskWriteBytes.Mark(diskstats[i%2].WriteBytes - diskstats[(i-1)%2].WriteBytes)
|
||||||
}
|
}
|
||||||
time.Sleep(refresh)
|
time.Sleep(refresh)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,11 @@ func (self *worker) update() {
|
||||||
|
|
||||||
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
|
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
|
||||||
self.currentMu.Unlock()
|
self.currentMu.Unlock()
|
||||||
|
} else {
|
||||||
|
// If we're mining, but nothing is being processed, wake on new transactions
|
||||||
|
if self.config.Clique != nil && self.config.Clique.Period == 0 {
|
||||||
|
self.commitNewWork()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// System stopped
|
// System stopped
|
||||||
|
|
|
||||||
|
|
@ -198,8 +198,8 @@ func (ec *EthereumClient) FilterLogs(ctx *Context, query *FilterQuery) (logs *Lo
|
||||||
}
|
}
|
||||||
// Temp hack due to vm.Logs being []*vm.Log
|
// Temp hack due to vm.Logs being []*vm.Log
|
||||||
res := make([]*types.Log, len(rawLogs))
|
res := make([]*types.Log, len(rawLogs))
|
||||||
for i, log := range rawLogs {
|
for i := range rawLogs {
|
||||||
res[i] = &log
|
res[i] = &rawLogs[i]
|
||||||
}
|
}
|
||||||
return &Logs{res}, nil
|
return &Logs{res}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func (i *Interface) SetInt64(n int64) { i.object = &n }
|
||||||
func (i *Interface) SetUint8(bigint *BigInt) { n := uint8(bigint.bigint.Uint64()); i.object = &n }
|
func (i *Interface) SetUint8(bigint *BigInt) { n := uint8(bigint.bigint.Uint64()); i.object = &n }
|
||||||
func (i *Interface) SetUint16(bigint *BigInt) { n := uint16(bigint.bigint.Uint64()); i.object = &n }
|
func (i *Interface) SetUint16(bigint *BigInt) { n := uint16(bigint.bigint.Uint64()); i.object = &n }
|
||||||
func (i *Interface) SetUint32(bigint *BigInt) { n := uint32(bigint.bigint.Uint64()); i.object = &n }
|
func (i *Interface) SetUint32(bigint *BigInt) { n := uint32(bigint.bigint.Uint64()); i.object = &n }
|
||||||
func (i *Interface) SetUint64(bigint *BigInt) { n := uint64(bigint.bigint.Uint64()); i.object = &n }
|
func (i *Interface) SetUint64(bigint *BigInt) { n := bigint.bigint.Uint64(); i.object = &n }
|
||||||
func (i *Interface) SetBigInt(bigint *BigInt) { i.object = &bigint.bigint }
|
func (i *Interface) SetBigInt(bigint *BigInt) { i.object = &bigint.bigint }
|
||||||
func (i *Interface) SetBigInts(bigints *BigInts) { i.object = &bigints.bigints }
|
func (i *Interface) SetBigInts(bigints *BigInts) { i.object = &bigints.bigints }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -427,7 +427,7 @@ func (tab *Table) bondall(nodes []*Node) (result []*Node) {
|
||||||
rc := make(chan *Node, len(nodes))
|
rc := make(chan *Node, len(nodes))
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
go func(n *Node) {
|
go func(n *Node) {
|
||||||
nn, _ := tab.bond(false, n.ID, n.addr(), uint16(n.TCP))
|
nn, _ := tab.bond(false, n.ID, n.addr(), n.TCP)
|
||||||
rc <- nn
|
rc <- nn
|
||||||
}(nodes[i])
|
}(nodes[i])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,15 +77,20 @@ var (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllProtocolChanges contains every protocol change (EIPs)
|
// AllEthashProtocolChanges contains every protocol change (EIPs) introduced
|
||||||
// introduced and accepted by the Ethereum core developers.
|
// and accepted by the Ethereum core developers into the Ethash consensus.
|
||||||
//
|
//
|
||||||
// This configuration is intentionally not using keyed fields.
|
// This configuration is intentionally not using keyed fields to force anyone
|
||||||
// This configuration must *always* have all forks enabled, which
|
// adding flags to the config to also have to set these fields.
|
||||||
// means that all fields must be set at all times. This forces
|
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), new(EthashConfig), nil}
|
||||||
// anyone adding flags to the config to also have to set these
|
|
||||||
// fields.
|
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
|
||||||
AllProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), new(EthashConfig), nil}
|
// and accepted by the Ethereum core developers into the Clique consensus.
|
||||||
|
//
|
||||||
|
// This configuration is intentionally not using keyed fields to force anyone
|
||||||
|
// adding flags to the config to also have to set these fields.
|
||||||
|
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &CliqueConfig{Period: 0, Epoch: 30000}}
|
||||||
|
|
||||||
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), new(EthashConfig), nil}
|
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), new(EthashConfig), nil}
|
||||||
TestRules = TestChainConfig.Rules(new(big.Int))
|
TestRules = TestChainConfig.Rules(new(big.Int))
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ func TestCheckCompatible(t *testing.T) {
|
||||||
wantErr *ConfigCompatError
|
wantErr *ConfigCompatError
|
||||||
}
|
}
|
||||||
tests := []test{
|
tests := []test{
|
||||||
{stored: AllProtocolChanges, new: AllProtocolChanges, head: 0, wantErr: nil},
|
{stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 0, wantErr: nil},
|
||||||
{stored: AllProtocolChanges, new: AllProtocolChanges, head: 100, wantErr: nil},
|
{stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 100, wantErr: nil},
|
||||||
{
|
{
|
||||||
stored: &ChainConfig{EIP150Block: big.NewInt(10)},
|
stored: &ChainConfig{EIP150Block: big.NewInt(10)},
|
||||||
new: &ChainConfig{EIP150Block: big.NewInt(20)},
|
new: &ChainConfig{EIP150Block: big.NewInt(20)},
|
||||||
|
|
@ -38,7 +38,7 @@ func TestCheckCompatible(t *testing.T) {
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stored: AllProtocolChanges,
|
stored: AllEthashProtocolChanges,
|
||||||
new: &ChainConfig{HomesteadBlock: nil},
|
new: &ChainConfig{HomesteadBlock: nil},
|
||||||
head: 3,
|
head: 3,
|
||||||
wantErr: &ConfigCompatError{
|
wantErr: &ConfigCompatError{
|
||||||
|
|
@ -49,7 +49,7 @@ func TestCheckCompatible(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stored: AllProtocolChanges,
|
stored: AllEthashProtocolChanges,
|
||||||
new: &ChainConfig{HomesteadBlock: big.NewInt(1)},
|
new: &ChainConfig{HomesteadBlock: big.NewInt(1)},
|
||||||
head: 3,
|
head: 3,
|
||||||
wantErr: &ConfigCompatError{
|
wantErr: &ConfigCompatError{
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 7 // Minor version component of the current release
|
VersionMinor = 7 // Minor version component of the current release
|
||||||
VersionPatch = 2 // Patch version component of the current release
|
VersionPatch = 3 // Patch version component of the current release
|
||||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
11
rpc/http.go
11
rpc/http.go
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -151,6 +152,16 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
http.StatusRequestEntityTooLarge)
|
http.StatusRequestEntityTooLarge)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct := r.Header.Get("content-type")
|
||||||
|
mt, _, err := mime.ParseMediaType(ct)
|
||||||
|
if err != nil || mt != "application/json" {
|
||||||
|
http.Error(w,
|
||||||
|
"invalid content type, only application/json is supported",
|
||||||
|
http.StatusUnsupportedMediaType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("content-type", "application/json")
|
w.Header().Set("content-type", "application/json")
|
||||||
|
|
||||||
// create a codec that reads direct from the request body until
|
// create a codec that reads direct from the request body until
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue