forked from cerc-io/plugeth
Merge remote-tracking branch 'upstream/develop' into statediff
This commit is contained in:
commit
4ad641831b
@ -6,7 +6,7 @@ version: 2.1
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.19.4
|
- image: cimg/go:1.21.0
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
@ -38,7 +38,7 @@ jobs:
|
|||||||
command: go test ./core/rawdb/
|
command: go test ./core/rawdb/
|
||||||
build_geth_push:
|
build_geth_push:
|
||||||
docker: # run the steps with Docker
|
docker: # run the steps with Docker
|
||||||
- image: cimg/go:1.19.4 # ...with this image as the primary container
|
- image: cimg/go:1.21.0 # ...with this image as the primary container
|
||||||
# this is where all `steps` will run
|
# this is where all `steps` will run
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
23
.github/workflows/go.yml
vendored
Normal file
23
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: i386 linux tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.21.4
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -short ./...
|
||||||
|
env:
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: 386
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -48,3 +48,5 @@ profile.cov
|
|||||||
|
|
||||||
**/yarn-error.log
|
**/yarn-error.log
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
|
tests/spec-tests/
|
||||||
|
@ -12,7 +12,6 @@ run:
|
|||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- goconst
|
|
||||||
- goimports
|
- goimports
|
||||||
- gosimple
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
@ -39,9 +38,6 @@ linters:
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
gofmt:
|
gofmt:
|
||||||
simplify: true
|
simplify: true
|
||||||
goconst:
|
|
||||||
min-len: 3 # minimum length of string constant
|
|
||||||
min-occurrences: 6 # minimum number of occurrences
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
55
.travis.yml
55
.travis.yml
@ -9,18 +9,6 @@ jobs:
|
|||||||
- azure-osx
|
- azure-osx
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# This builder only tests code linters on latest version of Go
|
|
||||||
- stage: lint
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
go: 1.20.x
|
|
||||||
env:
|
|
||||||
- lint
|
|
||||||
git:
|
|
||||||
submodules: false # avoid cloning ethereum/tests
|
|
||||||
script:
|
|
||||||
- go run build/ci.go lint
|
|
||||||
|
|
||||||
# These builders create the Docker sub-images for multi-arch push and each
|
# These builders create the Docker sub-images for multi-arch push and each
|
||||||
# will attempt to push the multi-arch image if they are the last builder
|
# will attempt to push the multi-arch image if they are the last builder
|
||||||
- stage: build
|
- stage: build
|
||||||
@ -28,7 +16,7 @@ jobs:
|
|||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
env:
|
||||||
- docker
|
- docker
|
||||||
services:
|
services:
|
||||||
@ -45,7 +33,7 @@ jobs:
|
|||||||
os: linux
|
os: linux
|
||||||
arch: arm64
|
arch: arm64
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
env:
|
||||||
- docker
|
- docker
|
||||||
services:
|
services:
|
||||||
@ -63,10 +51,9 @@ jobs:
|
|||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
env:
|
||||||
- azure-linux
|
- azure-linux
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
@ -97,56 +84,51 @@ jobs:
|
|||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
os: osx
|
os: osx
|
||||||
go: 1.20.x
|
osx_image: xcode14.2
|
||||||
|
go: 1.21.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go install -dlgo
|
- go run build/ci.go install -dlgo
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
- go run build/ci.go install -dlgo -arch arm64
|
||||||
|
- go run build/ci.go archive -arch arm64 -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# These builders run the tests
|
# These builders run the tests
|
||||||
- stage: build
|
- stage: build
|
||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test $TEST_PACKAGES
|
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
|
||||||
|
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = pull_request
|
if: type = pull_request
|
||||||
os: linux
|
os: linux
|
||||||
arch: arm64
|
arch: arm64
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.19.x
|
go: 1.20.x
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test $TEST_PACKAGES
|
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
|
||||||
|
|
||||||
- stage: build
|
- stage: build
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.19.x
|
go: 1.20.x
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test $TEST_PACKAGES
|
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA nightly uploads
|
# This builder does the Ubuntu PPA nightly uploads
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = cron || (type = push && tag ~= /^v[0-9]/)
|
if: type = cron || (type = push && tag ~= /^v[0-9]/)
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
@ -167,10 +149,9 @@ jobs:
|
|||||||
if: type = cron
|
if: type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
@ -181,9 +162,7 @@ jobs:
|
|||||||
if: type = cron
|
if: type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
go: 1.20.x
|
go: 1.21.x
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test -race $TEST_PACKAGES
|
- travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ ARG VERSION=""
|
|||||||
ARG BUILDNUM=""
|
ARG BUILDNUM=""
|
||||||
|
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.20-alpine3.18 as builder
|
FROM golang:1.21-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache gcc musl-dev binutils-gold linux-headers git
|
RUN apk add --no-cache gcc musl-dev binutils-gold linux-headers git
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ ARG VERSION=""
|
|||||||
ARG BUILDNUM=""
|
ARG BUILDNUM=""
|
||||||
|
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.20-alpine as builder
|
FROM golang:1.21-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache gcc musl-dev linux-headers git
|
RUN apk add --no-cache gcc musl-dev linux-headers git
|
||||||
|
|
||||||
|
16
Makefile
16
Makefile
@ -6,29 +6,35 @@
|
|||||||
|
|
||||||
GOBIN = ./build/bin
|
GOBIN = ./build/bin
|
||||||
GO ?= latest
|
GO ?= latest
|
||||||
GORUN = env GO111MODULE=on go run
|
GORUN = go run
|
||||||
|
|
||||||
|
#? geth: Build geth
|
||||||
geth:
|
geth:
|
||||||
$(GORUN) build/ci.go install ./cmd/geth
|
$(GORUN) build/ci.go install ./cmd/geth
|
||||||
@echo "Done building."
|
@echo "Done building."
|
||||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||||
|
|
||||||
|
#? all: Build all packages and executables
|
||||||
all:
|
all:
|
||||||
$(GORUN) build/ci.go install
|
$(GORUN) build/ci.go install
|
||||||
|
|
||||||
|
#? test: Run the tests
|
||||||
test: all
|
test: all
|
||||||
$(GORUN) build/ci.go test
|
$(GORUN) build/ci.go test
|
||||||
|
|
||||||
|
#? lint: Run certain pre-selected linters
|
||||||
lint: ## Run linters.
|
lint: ## Run linters.
|
||||||
$(GORUN) build/ci.go lint
|
$(GORUN) build/ci.go lint
|
||||||
|
|
||||||
|
#? clean: Clean go cache, built executables, and the auto generated folder
|
||||||
clean:
|
clean:
|
||||||
env GO111MODULE=on go clean -cache
|
go clean -cache
|
||||||
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
||||||
|
|
||||||
# The devtools target installs tools required for 'go generate'.
|
# The devtools target installs tools required for 'go generate'.
|
||||||
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
|
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
|
||||||
|
|
||||||
|
#? devtools: Install recommended developer tools
|
||||||
devtools:
|
devtools:
|
||||||
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
|
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
|
||||||
env GOBIN= go install github.com/fjl/gencodec@latest
|
env GOBIN= go install github.com/fjl/gencodec@latest
|
||||||
@ -36,3 +42,9 @@ devtools:
|
|||||||
env GOBIN= go install ./cmd/abigen
|
env GOBIN= go install ./cmd/abigen
|
||||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||||
|
|
||||||
|
#? help: Get more info on make commands.
|
||||||
|
help: Makefile
|
||||||
|
@echo " Choose a command run in go-ethereum:"
|
||||||
|
@sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /'
|
||||||
|
.PHONY: help
|
||||||
|
@ -25,6 +25,10 @@ rather than forking the Geth project. Out of the box, PluGeth behaves exactly
|
|||||||
like upstream Geth, but by installing plugins written in Golang, developers can
|
like upstream Geth, but by installing plugins written in Golang, developers can
|
||||||
extend its functionality in a wide variety of way.
|
extend its functionality in a wide variety of way.
|
||||||
|
|
||||||
|
### Submitting Pull Requests
|
||||||
|
|
||||||
|
We are eager to include contributions from the community into the project. We ask that pull requests which include new features to be covered by our test plugin found in: `/plugins/test-plugin`. The test design and instructions for use are documented there. If further assistance is needed please get in touch with us.
|
||||||
|
|
||||||
### Contact Us
|
### Contact Us
|
||||||
|
|
||||||
If you're trying to do something that isn't supported by the current plugin system, Reach out to us on [Discord](https://discord.gg/Epf7b7Gr) and we'll help you figure out how to make it work.
|
If you're trying to do something that isn't supported by the current plugin system, Reach out to us on [Discord](https://discord.gg/Epf7b7Gr) and we'll help you figure out how to make it work.
|
||||||
|
@ -22,13 +22,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
// invokable methods. It will allow you to type check function calls and
|
// invocable methods. It will allow you to type check function calls and
|
||||||
// packs data accordingly.
|
// packs data accordingly.
|
||||||
type ABI struct {
|
type ABI struct {
|
||||||
Constructor Method
|
Constructor Method
|
||||||
@ -222,6 +223,17 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
|
|||||||
return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
|
return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorByID looks up an error by the 4-byte id,
|
||||||
|
// returns nil if none found.
|
||||||
|
func (abi *ABI) ErrorByID(sigdata [4]byte) (*Error, error) {
|
||||||
|
for _, errABI := range abi.Errors {
|
||||||
|
if bytes.Equal(errABI.ID[:4], sigdata[:]) {
|
||||||
|
return &errABI, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no error with id: %#x", sigdata[:])
|
||||||
|
}
|
||||||
|
|
||||||
// HasFallback returns an indicator whether a fallback function is included.
|
// HasFallback returns an indicator whether a fallback function is included.
|
||||||
func (abi *ABI) HasFallback() bool {
|
func (abi *ABI) HasFallback() bool {
|
||||||
return abi.Fallback.Type == Fallback
|
return abi.Fallback.Type == Fallback
|
||||||
@ -235,17 +247,37 @@ func (abi *ABI) HasReceive() bool {
|
|||||||
// revertSelector is a special function selector for revert reason unpacking.
|
// revertSelector is a special function selector for revert reason unpacking.
|
||||||
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
||||||
|
|
||||||
|
// panicSelector is a special function selector for panic reason unpacking.
|
||||||
|
var panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4]
|
||||||
|
|
||||||
|
// panicReasons map is for readable panic codes
|
||||||
|
// see this linkage for the details
|
||||||
|
// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
|
||||||
|
// the reason string list is copied from ether.js
|
||||||
|
// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
|
||||||
|
var panicReasons = map[uint64]string{
|
||||||
|
0x00: "generic panic",
|
||||||
|
0x01: "assert(false)",
|
||||||
|
0x11: "arithmetic underflow or overflow",
|
||||||
|
0x12: "division or modulo by zero",
|
||||||
|
0x21: "enum overflow",
|
||||||
|
0x22: "invalid encoded storage byte array accessed",
|
||||||
|
0x31: "out-of-bounds array access; popping on an empty array",
|
||||||
|
0x32: "out-of-bounds access of an array or bytesN",
|
||||||
|
0x41: "out of memory",
|
||||||
|
0x51: "uninitialized function",
|
||||||
|
}
|
||||||
|
|
||||||
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
||||||
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
||||||
// the provided revert reason is abi-encoded as if it were a call to a function
|
// the provided revert reason is abi-encoded as if it were a call to function
|
||||||
// `Error(string)`. So it's a special tool for it.
|
// `Error(string)` or `Panic(uint256)`. So it's a special tool for it.
|
||||||
func UnpackRevert(data []byte) (string, error) {
|
func UnpackRevert(data []byte) (string, error) {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", errors.New("invalid data for unpacking")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(data[:4], revertSelector) {
|
switch {
|
||||||
return "", errors.New("invalid data for unpacking")
|
case bytes.Equal(data[:4], revertSelector):
|
||||||
}
|
|
||||||
typ, err := NewType("string", "", nil)
|
typ, err := NewType("string", "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -255,4 +287,25 @@ func UnpackRevert(data []byte) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return unpacked[0].(string), nil
|
return unpacked[0].(string), nil
|
||||||
|
case bytes.Equal(data[:4], panicSelector):
|
||||||
|
typ, err := NewType("uint256", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pCode := unpacked[0].(*big.Int)
|
||||||
|
// uint64 safety check for future
|
||||||
|
// but the code is not bigger than MAX(uint64) now
|
||||||
|
if pCode.IsUint64() {
|
||||||
|
if reason, ok := panicReasons[pCode.Uint64()]; ok {
|
||||||
|
return reason, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown panic code: %#x", pCode), nil
|
||||||
|
default:
|
||||||
|
return "", errors.New("invalid data for unpacking")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ var methods = map[string]Method{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReader(t *testing.T) {
|
func TestReader(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi := ABI{
|
abi := ABI{
|
||||||
Methods: methods,
|
Methods: methods,
|
||||||
}
|
}
|
||||||
@ -151,6 +152,7 @@ func TestReader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidABI(t *testing.T) {
|
func TestInvalidABI(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
|
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
|
||||||
_, err := JSON(strings.NewReader(json))
|
_, err := JSON(strings.NewReader(json))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -170,6 +172,7 @@ func TestInvalidABI(t *testing.T) {
|
|||||||
// constructor(uint256 a, uint256 b) public{}
|
// constructor(uint256 a, uint256 b) public{}
|
||||||
// }
|
// }
|
||||||
func TestConstructor(t *testing.T) {
|
func TestConstructor(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
|
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
|
||||||
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
|
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
|
||||||
// Test from JSON
|
// Test from JSON
|
||||||
@ -199,6 +202,7 @@ func TestConstructor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTestNumbers(t *testing.T) {
|
func TestTestNumbers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -236,6 +240,7 @@ func TestTestNumbers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodSignature(t *testing.T) {
|
func TestMethodSignature(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil)
|
m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil)
|
||||||
exp := "foo(string,string)"
|
exp := "foo(string,string)"
|
||||||
if m.Sig != exp {
|
if m.Sig != exp {
|
||||||
@ -274,6 +279,7 @@ func TestMethodSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverloadedMethodSignature(t *testing.T) {
|
func TestOverloadedMethodSignature(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]`
|
json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(json))
|
abi, err := JSON(strings.NewReader(json))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -297,6 +303,7 @@ func TestOverloadedMethodSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCustomErrors(t *testing.T) {
|
func TestCustomErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
|
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
|
||||||
abi, err := JSON(strings.NewReader(json))
|
abi, err := JSON(strings.NewReader(json))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -311,6 +318,7 @@ func TestCustomErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiPack(t *testing.T) {
|
func TestMultiPack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -348,6 +356,7 @@ func ExampleJSON() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInputVariableInputLength(t *testing.T) {
|
func TestInputVariableInputLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
||||||
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
||||||
@ -476,6 +485,7 @@ func TestInputVariableInputLength(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -650,6 +660,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultFunctionParsing(t *testing.T) {
|
func TestDefaultFunctionParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{ "name" : "balance", "type" : "function" }]`
|
const definition = `[{ "name" : "balance", "type" : "function" }]`
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
@ -663,6 +674,7 @@ func TestDefaultFunctionParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareEvents(t *testing.T) {
|
func TestBareEvents(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "event", "name" : "balance" },
|
{ "type" : "event", "name" : "balance" },
|
||||||
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
||||||
@ -739,6 +751,7 @@ func TestBareEvents(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
func TestUnpackEvent(t *testing.T) {
|
func TestUnpackEvent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -777,6 +790,7 @@ func TestUnpackEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackEventIntoMap(t *testing.T) {
|
func TestUnpackEventIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -827,6 +841,7 @@ func TestUnpackEventIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackMethodIntoMap(t *testing.T) {
|
func TestUnpackMethodIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -877,6 +892,7 @@ func TestUnpackMethodIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Two methods have the same name
|
// Two methods have the same name
|
||||||
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
@ -960,6 +976,7 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestABI_MethodById(t *testing.T) {
|
func TestABI_MethodById(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -992,6 +1009,7 @@ func TestABI_MethodById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestABI_EventById(t *testing.T) {
|
func TestABI_EventById(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
json string
|
json string
|
||||||
@ -1057,9 +1075,39 @@ func TestABI_EventById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestABI_ErrorByID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
abi, err := JSON(strings.NewReader(`[
|
||||||
|
{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"MyError1","type":"error"},
|
||||||
|
{"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"x","type":"tuple"},{"internalType":"address","name":"y","type":"address"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"z","type":"tuple"}],"name":"MyError2","type":"error"},
|
||||||
|
{"inputs":[{"internalType":"uint256[]","name":"x","type":"uint256[]"}],"name":"MyError3","type":"error"}
|
||||||
|
]`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, m := range abi.Errors {
|
||||||
|
a := fmt.Sprintf("%v", &m)
|
||||||
|
var id [4]byte
|
||||||
|
copy(id[:], m.ID[:4])
|
||||||
|
m2, err := abi.ErrorByID(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to look up ABI error: %v", err)
|
||||||
|
}
|
||||||
|
b := fmt.Sprintf("%v", m2)
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("Error %v (id %x) not 'findable' by id in ABI", name, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// test unsuccessful lookups
|
||||||
|
if _, err = abi.ErrorByID([4]byte{}); err == nil {
|
||||||
|
t.Error("Expected error: no error with this id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
|
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
|
||||||
// conflict and that the second transfer method will be renamed transfer1.
|
// conflict and that the second transfer method will be renamed transfer1.
|
||||||
func TestDoubleDuplicateMethodNames(t *testing.T) {
|
func TestDoubleDuplicateMethodNames(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
|
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
|
||||||
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1089,6 +1137,7 @@ func TestDoubleDuplicateMethodNames(t *testing.T) {
|
|||||||
// event send();
|
// event send();
|
||||||
// }
|
// }
|
||||||
func TestDoubleDuplicateEventNames(t *testing.T) {
|
func TestDoubleDuplicateEventNames(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
|
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
|
||||||
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1116,6 +1165,7 @@ func TestDoubleDuplicateEventNames(t *testing.T) {
|
|||||||
// event send(uint256, uint256);
|
// event send(uint256, uint256);
|
||||||
// }
|
// }
|
||||||
func TestUnnamedEventParam(t *testing.T) {
|
func TestUnnamedEventParam(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
|
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
|
||||||
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1145,9 +1195,13 @@ func TestUnpackRevert(t *testing.T) {
|
|||||||
{"", "", errors.New("invalid data for unpacking")},
|
{"", "", errors.New("invalid data for unpacking")},
|
||||||
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
||||||
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
||||||
|
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
|
||||||
|
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
|
||||||
}
|
}
|
||||||
for index, c := range cases {
|
for index, c := range cases {
|
||||||
|
index, c := index, c
|
||||||
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
||||||
if c.expectErr != nil {
|
if c.expectErr != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -20,17 +20,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
fuzz "github.com/google/gofuzz"
|
fuzz "github.com/google/gofuzz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestReplicate can be used to replicate crashers from the fuzzing tests.
|
||||||
|
// Just replace testString with the data in .quoted
|
||||||
|
func TestReplicate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
//t.Skip("Test only useful for reproducing issues")
|
||||||
|
fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"))
|
||||||
|
//fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzABI is the main entrypoint for fuzzing
|
||||||
|
func FuzzABI(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
fuzzAbi(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
|
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
|
||||||
stateMut = []string{"", "pure", "view", "payable"}
|
stateMut = []string{"pure", "view", "payable"}
|
||||||
stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]}
|
pays = []string{"true", "false"}
|
||||||
pays = []string{"", "true", "false"}
|
|
||||||
payables = []*string{&pays[0], &pays[1]}
|
|
||||||
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
|
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
|
||||||
varNames = append(vNames, names...)
|
varNames = append(vNames, names...)
|
||||||
varTypes = []string{"bool", "address", "bytes", "string",
|
varTypes = []string{"bool", "address", "bytes", "string",
|
||||||
@ -47,7 +61,7 @@ var (
|
|||||||
"bytes32", "bytes"}
|
"bytes32", "bytes"}
|
||||||
)
|
)
|
||||||
|
|
||||||
func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) {
|
func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) {
|
||||||
if out, err := abi.Unpack(method, input); err == nil {
|
if out, err := abi.Unpack(method, input); err == nil {
|
||||||
_, err := abi.Pack(method, out...)
|
_, err := abi.Pack(method, out...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,7 +77,7 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool)
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
|
func packUnpack(abi ABI, method string, input *[]interface{}) bool {
|
||||||
if packed, err := abi.Pack(method, input); err == nil {
|
if packed, err := abi.Pack(method, input); err == nil {
|
||||||
outptr := reflect.New(reflect.TypeOf(input))
|
outptr := reflect.New(reflect.TypeOf(input))
|
||||||
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
|
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
|
||||||
@ -79,12 +93,12 @@ func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type args struct {
|
type arg struct {
|
||||||
name string
|
name string
|
||||||
typ string
|
typ string
|
||||||
}
|
}
|
||||||
|
|
||||||
func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) {
|
func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) {
|
||||||
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
|
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
|
||||||
if stateMutability != nil {
|
if stateMutability != nil {
|
||||||
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
|
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
|
||||||
@ -111,60 +125,55 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab
|
|||||||
sig += "} ]"
|
sig += "} ]"
|
||||||
}
|
}
|
||||||
sig += `}]`
|
sig += `}]`
|
||||||
|
//fmt.Printf("sig: %s\n", sig)
|
||||||
return abi.JSON(strings.NewReader(sig))
|
return JSON(strings.NewReader(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFuzzer(input []byte) int {
|
func fuzzAbi(input []byte) {
|
||||||
good := false
|
var (
|
||||||
fuzzer := fuzz.NewFromGoFuzz(input)
|
fuzzer = fuzz.NewFromGoFuzz(input)
|
||||||
|
name = oneOf(fuzzer, names)
|
||||||
name := names[getUInt(fuzzer)%len(names)]
|
stateM = oneOfOrNil(fuzzer, stateMut)
|
||||||
stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)]
|
payable = oneOfOrNil(fuzzer, pays)
|
||||||
payable := payables[getUInt(fuzzer)%len(payables)]
|
arguments []arg
|
||||||
maxLen := 5
|
)
|
||||||
for k := 1; k < maxLen; k++ {
|
for i := 0; i < upTo(fuzzer, 10); i++ {
|
||||||
var arg []args
|
argName := oneOf(fuzzer, varNames)
|
||||||
for i := k; i > 0; i-- {
|
argTyp := oneOf(fuzzer, varTypes)
|
||||||
argName := varNames[i]
|
switch upTo(fuzzer, 10) {
|
||||||
argTyp := varTypes[getUInt(fuzzer)%len(varTypes)]
|
case 0: // 10% chance to make it a slice
|
||||||
if getUInt(fuzzer)%10 == 0 {
|
|
||||||
argTyp += "[]"
|
argTyp += "[]"
|
||||||
} else if getUInt(fuzzer)%10 == 0 {
|
case 1: // 10% chance to make it an array
|
||||||
arrayArgs := getUInt(fuzzer)%30 + 1
|
argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30))
|
||||||
argTyp += fmt.Sprintf("[%d]", arrayArgs)
|
default:
|
||||||
}
|
}
|
||||||
arg = append(arg, args{
|
arguments = append(arguments, arg{name: argName, typ: argTyp})
|
||||||
name: argName,
|
|
||||||
typ: argTyp,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
abi, err := createABI(name, stateM, payable, arg)
|
abi, err := createABI(name, stateM, payable, arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
//fmt.Printf("err: %v\n", err)
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
structs, b := unpackPack(abi, name, input)
|
structs, _ := unpackPack(abi, name, input)
|
||||||
c := packUnpack(abi, name, &structs)
|
_ = packUnpack(abi, name, &structs)
|
||||||
good = good || b || c
|
|
||||||
}
|
|
||||||
if good {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fuzz(input []byte) int {
|
func upTo(fuzzer *fuzz.Fuzzer, max int) int {
|
||||||
return runFuzzer(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUInt(fuzzer *fuzz.Fuzzer) int {
|
|
||||||
var i int
|
var i int
|
||||||
fuzzer.Fuzz(&i)
|
fuzzer.Fuzz(&i)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
i = -i
|
return (-1 - i) % max
|
||||||
if i < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
}
|
return i % max
|
||||||
return i
|
}
|
||||||
|
|
||||||
|
func oneOf(fuzzer *fuzz.Fuzzer, options []string) string {
|
||||||
|
return options[upTo(fuzzer, len(options))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string {
|
||||||
|
if i := upTo(fuzzer, len(options)+1); i < len(options) {
|
||||||
|
return &options[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
@ -80,7 +80,7 @@ func (arguments Arguments) isTuple() bool {
|
|||||||
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
|
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
if len(arguments.NonIndexed()) != 0 {
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
|
return nil, errors.New("abi: attempting to unmarshal an empty string while arguments are expected")
|
||||||
}
|
}
|
||||||
return make([]interface{}, 0), nil
|
return make([]interface{}, 0), nil
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte)
|
|||||||
}
|
}
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
if len(arguments.NonIndexed()) != 0 {
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
return errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
|
return errors.New("abi: attempting to unmarshal an empty string while arguments are expected")
|
||||||
}
|
}
|
||||||
return nil // Nothing to unmarshal, return
|
return nil // Nothing to unmarshal, return
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
||||||
// an decrypted key from a keystore.
|
// a decrypted key from a keystore.
|
||||||
//
|
//
|
||||||
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
|
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
|
||||||
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
||||||
@ -117,7 +117,7 @@ func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
|
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
|
||||||
// an decrypted key from a keystore.
|
// a decrypted key from a keystore.
|
||||||
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
|
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
return nil, ErrNoChainID
|
return nil, ErrNoChainID
|
||||||
|
@ -29,13 +29,17 @@ import (
|
|||||||
var (
|
var (
|
||||||
// ErrNoCode is returned by call and transact operations for which the requested
|
// ErrNoCode is returned by call and transact operations for which the requested
|
||||||
// recipient contract to operate on does not exist in the state db or does not
|
// recipient contract to operate on does not exist in the state db or does not
|
||||||
// have any code associated with it (i.e. suicided).
|
// have any code associated with it (i.e. self-destructed).
|
||||||
ErrNoCode = errors.New("no contract code at given address")
|
ErrNoCode = errors.New("no contract code at given address")
|
||||||
|
|
||||||
// ErrNoPendingState is raised when attempting to perform a pending state action
|
// ErrNoPendingState is raised when attempting to perform a pending state action
|
||||||
// on a backend that doesn't implement PendingContractCaller.
|
// on a backend that doesn't implement PendingContractCaller.
|
||||||
ErrNoPendingState = errors.New("backend does not support pending state")
|
ErrNoPendingState = errors.New("backend does not support pending state")
|
||||||
|
|
||||||
|
// ErrNoBlockHashState is raised when attempting to perform a block hash action
|
||||||
|
// on a backend that doesn't implement BlockHashContractCaller.
|
||||||
|
ErrNoBlockHashState = errors.New("backend does not support block hash state")
|
||||||
|
|
||||||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||||
// an empty contract behind.
|
// an empty contract behind.
|
||||||
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
||||||
@ -64,11 +68,27 @@ type PendingContractCaller interface {
|
|||||||
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockHashContractCaller defines methods to perform contract calls on a specific block hash.
|
||||||
|
// Call will try to discover this interface when access to a block by hash is requested.
|
||||||
|
// If the backend does not support the block hash state, Call returns ErrNoBlockHashState.
|
||||||
|
type BlockHashContractCaller interface {
|
||||||
|
// CodeAtHash returns the code of the given account in the state at the specified block hash.
|
||||||
|
CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error)
|
||||||
|
|
||||||
|
// CallContractAtHash executes an Ethereum contract call against the state at the specified block hash.
|
||||||
|
CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ContractTransactor defines the methods needed to allow operating with a contract
|
// ContractTransactor defines the methods needed to allow operating with a contract
|
||||||
// on a write only basis. Besides the transacting method, the remainder are helpers
|
// on a write only basis. Besides the transacting method, the remainder are helpers
|
||||||
// used when the user does not provide some needed values, but rather leaves it up
|
// used when the user does not provide some needed values, but rather leaves it up
|
||||||
// to the transactor to decide.
|
// to the transactor to decide.
|
||||||
type ContractTransactor interface {
|
type ContractTransactor interface {
|
||||||
|
ethereum.GasEstimator
|
||||||
|
ethereum.GasPricer
|
||||||
|
ethereum.GasPricer1559
|
||||||
|
ethereum.TransactionSender
|
||||||
|
|
||||||
// HeaderByNumber returns a block header from the current canonical chain. If
|
// HeaderByNumber returns a block header from the current canonical chain. If
|
||||||
// number is nil, the latest known header is returned.
|
// number is nil, the latest known header is returned.
|
||||||
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
||||||
@ -78,38 +98,6 @@ type ContractTransactor interface {
|
|||||||
|
|
||||||
// PendingNonceAt retrieves the current pending nonce associated with an account.
|
// PendingNonceAt retrieves the current pending nonce associated with an account.
|
||||||
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
||||||
|
|
||||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
|
||||||
// execution of a transaction.
|
|
||||||
SuggestGasPrice(ctx context.Context) (*big.Int, error)
|
|
||||||
|
|
||||||
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
|
|
||||||
// a timely execution of a transaction.
|
|
||||||
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
|
||||||
|
|
||||||
// EstimateGas tries to estimate the gas needed to execute a specific
|
|
||||||
// transaction based on the current pending state of the backend blockchain.
|
|
||||||
// There is no guarantee that this is the true gas limit requirement as other
|
|
||||||
// transactions may be added or removed by miners, but it should provide a basis
|
|
||||||
// for setting a reasonable default.
|
|
||||||
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
|
|
||||||
|
|
||||||
// SendTransaction injects the transaction into the pending pool for execution.
|
|
||||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContractFilterer defines the methods needed to access log events using one-off
|
|
||||||
// queries or continuous event subscriptions.
|
|
||||||
type ContractFilterer interface {
|
|
||||||
// FilterLogs executes a log filter operation, blocking during execution and
|
|
||||||
// returning all the results in one batch.
|
|
||||||
//
|
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
|
||||||
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
|
|
||||||
|
|
||||||
// SubscribeFilterLogs creates a background log filtering operation, returning
|
|
||||||
// a subscription immediately, which can be used to stream the found events.
|
|
||||||
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||||
@ -118,6 +106,12 @@ type DeployBackend interface {
|
|||||||
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContractFilterer defines the methods needed to access log events using one-off
|
||||||
|
// queries or continuous event subscriptions.
|
||||||
|
type ContractFilterer interface {
|
||||||
|
ethereum.LogFilterer
|
||||||
|
}
|
||||||
|
|
||||||
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
||||||
type ContractBackend interface {
|
type ContractBackend interface {
|
||||||
ContractCaller
|
ContractCaller
|
||||||
|
@ -18,917 +18,35 @@ package backends
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"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/ethclient/simulated"
|
||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend.
|
// SimulatedBackend is a simulated blockchain.
|
||||||
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead.
|
||||||
|
|
||||||
var (
|
|
||||||
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
|
||||||
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
|
||||||
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
|
||||||
// the background. Its main purpose is to allow for easy testing of contract bindings.
|
|
||||||
// Simulated backend implements the following interfaces:
|
|
||||||
// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor,
|
|
||||||
// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender
|
|
||||||
type SimulatedBackend struct {
|
type SimulatedBackend struct {
|
||||||
database ethdb.Database // In memory database to store our testing data
|
*simulated.Backend
|
||||||
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
|
simulated.Client
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
pendingBlock *types.Block // Currently pending block that will be imported on request
|
|
||||||
pendingState *state.StateDB // Currently pending state that will be the active on request
|
|
||||||
pendingReceipts types.Receipts // Currently receipts for the pending block
|
|
||||||
|
|
||||||
events *filters.EventSystem // for filtering log events live
|
|
||||||
filterSystem *filters.FilterSystem // for filtering database logs
|
|
||||||
|
|
||||||
config *params.ChainConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimulatedBackendWithDatabase creates a new binding backend based on the given database
|
// Fork sets the head to a new block, which is based on the provided parentHash.
|
||||||
// and uses a simulated blockchain for testing purposes.
|
func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error {
|
||||||
// A simulated backend always uses chainID 1337.
|
return b.Backend.Fork(parentHash)
|
||||||
func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
|
||||||
genesis := core.Genesis{
|
|
||||||
Config: params.AllEthashProtocolChanges,
|
|
||||||
GasLimit: gasLimit,
|
|
||||||
Alloc: alloc,
|
|
||||||
}
|
|
||||||
blockchain, _ := core.NewBlockChain(database, nil, &genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
|
||||||
|
|
||||||
backend := &SimulatedBackend{
|
|
||||||
database: database,
|
|
||||||
blockchain: blockchain,
|
|
||||||
config: genesis.Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
filterBackend := &filterBackend{database, blockchain, backend}
|
|
||||||
backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
|
|
||||||
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
|
||||||
|
|
||||||
header := backend.blockchain.CurrentBlock()
|
|
||||||
block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64())
|
|
||||||
|
|
||||||
backend.rollback(block)
|
|
||||||
return backend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||||
// for testing purposes.
|
// for testing purposes.
|
||||||
|
//
|
||||||
// A simulated backend always uses chainID 1337.
|
// A simulated backend always uses chainID 1337.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
|
||||||
return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close terminates the underlying blockchain's update loop.
|
|
||||||
func (b *SimulatedBackend) Close() error {
|
|
||||||
b.blockchain.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit imports all the pending transactions as a single block and starts a
|
|
||||||
// fresh new state.
|
|
||||||
func (b *SimulatedBackend) Commit() common.Hash {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
|
|
||||||
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
|
|
||||||
}
|
|
||||||
blockHash := b.pendingBlock.Hash()
|
|
||||||
|
|
||||||
// Using the last inserted block here makes it possible to build on a side
|
|
||||||
// chain after a fork.
|
|
||||||
b.rollback(b.pendingBlock)
|
|
||||||
|
|
||||||
return blockHash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
|
||||||
func (b *SimulatedBackend) Rollback() {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
header := b.blockchain.CurrentBlock()
|
|
||||||
block := b.blockchain.GetBlock(header.Hash(), header.Number.Uint64())
|
|
||||||
|
|
||||||
b.rollback(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SimulatedBackend) rollback(parent *types.Block) {
|
|
||||||
blocks, _ := core.GenerateChain(b.config, parent, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fork creates a side-chain that can be used to simulate reorgs.
|
|
||||||
//
|
//
|
||||||
// This function should be called with the ancestor block where the new side
|
// Deprecated: please use simulated.Backend from package
|
||||||
// chain should be started. Transactions (old and new) can then be applied on
|
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
|
||||||
// top and Commit-ed.
|
func NewSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||||
//
|
b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit))
|
||||||
// Note, the side-chain will only become canonical (and trigger the events) when
|
return &SimulatedBackend{
|
||||||
// it becomes longer. Until then CallContract will still operate on the current
|
Backend: b,
|
||||||
// canonical chain.
|
Client: b.Client(),
|
||||||
//
|
|
||||||
// There is a % chance that the side chain becomes canonical at the same length
|
|
||||||
// to simulate live network behavior.
|
|
||||||
func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if len(b.pendingBlock.Transactions()) != 0 {
|
|
||||||
return errors.New("pending block dirty")
|
|
||||||
}
|
|
||||||
block, err := b.blockByHash(ctx, parent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.rollback(block)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateByBlockNumber retrieves a state by a given blocknumber.
|
|
||||||
func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) {
|
|
||||||
if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number) == 0 {
|
|
||||||
return b.blockchain.State()
|
|
||||||
}
|
|
||||||
block, err := b.blockByNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b.blockchain.StateAt(block.Root())
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateDB.GetCode(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateDB.GetBalance(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonceAt returns the nonce of a certain account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateDB.GetNonce(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageAt returns the value of key in the storage of an account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
val := stateDB.GetState(contract, key)
|
|
||||||
return val[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionReceipt returns the receipt of a transaction.
|
|
||||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config)
|
|
||||||
if receipt == nil {
|
|
||||||
return nil, ethereum.NotFound
|
|
||||||
}
|
|
||||||
return receipt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionByHash checks the pool of pending transactions in addition to the
|
|
||||||
// blockchain. The isPending return value indicates whether the transaction has been
|
|
||||||
// mined yet. Note that the transaction may not be part of the canonical chain even if
|
|
||||||
// it's not pending.
|
|
||||||
func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
tx := b.pendingBlock.Transaction(txHash)
|
|
||||||
if tx != nil {
|
|
||||||
return tx, true, nil
|
|
||||||
}
|
|
||||||
tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash)
|
|
||||||
if tx != nil {
|
|
||||||
return tx, false, nil
|
|
||||||
}
|
|
||||||
return nil, false, ethereum.NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockByHash retrieves a block based on the block hash.
|
|
||||||
func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.blockByHash(ctx, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockByHash retrieves a block based on the block hash without Locking.
|
|
||||||
func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
||||||
if hash == b.pendingBlock.Hash() {
|
|
||||||
return b.pendingBlock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByHash(hash)
|
|
||||||
if block != nil {
|
|
||||||
return block, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockByNumber retrieves a block from the database by number, caching it
|
|
||||||
// (associated with its hash) if found.
|
|
||||||
func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.blockByNumber(ctx, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockByNumber retrieves a block from the database by number, caching it
|
|
||||||
// (associated with its hash) if found without Lock.
|
|
||||||
func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
|
||||||
if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 {
|
|
||||||
return b.blockByHash(ctx, b.blockchain.CurrentBlock().Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByNumber(uint64(number.Int64()))
|
|
||||||
if block == nil {
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return block, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderByHash returns a block header from the current canonical chain.
|
|
||||||
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if hash == b.pendingBlock.Hash() {
|
|
||||||
return b.pendingBlock.Header(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
header := b.blockchain.GetHeaderByHash(hash)
|
|
||||||
if header == nil {
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderByNumber returns a block header from the current canonical chain. If number is
|
|
||||||
// nil, the latest known header is returned.
|
|
||||||
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 {
|
|
||||||
return b.blockchain.CurrentHeader(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionCount returns the number of transactions in a given block.
|
|
||||||
func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if blockHash == b.pendingBlock.Hash() {
|
|
||||||
return uint(b.pendingBlock.Transactions().Len()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByHash(blockHash)
|
|
||||||
if block == nil {
|
|
||||||
return uint(0), errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint(block.Transactions().Len()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionInBlock returns the transaction for a specific block at a specific index.
|
|
||||||
func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if blockHash == b.pendingBlock.Hash() {
|
|
||||||
transactions := b.pendingBlock.Transactions()
|
|
||||||
if uint(len(transactions)) < index+1 {
|
|
||||||
return nil, errTransactionDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions[index], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByHash(blockHash)
|
|
||||||
if block == nil {
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
transactions := block.Transactions()
|
|
||||||
if uint(len(transactions)) < index+1 {
|
|
||||||
return nil, errTransactionDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions[index], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
|
||||||
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.pendingState.GetCode(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRevertError(result *core.ExecutionResult) *revertError {
|
|
||||||
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
|
||||||
err := errors.New("execution reverted")
|
|
||||||
if errUnpack == nil {
|
|
||||||
err = fmt.Errorf("execution reverted: %v", reason)
|
|
||||||
}
|
|
||||||
return &revertError{
|
|
||||||
error: err,
|
|
||||||
reason: hexutil.Encode(result.Revert()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// revertError is an API error that encompasses an EVM revert with JSON error
|
|
||||||
// code and a binary data blob.
|
|
||||||
type revertError struct {
|
|
||||||
error
|
|
||||||
reason string // revert reason hex encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorCode returns the JSON error code for a revert.
|
|
||||||
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
|
||||||
func (e *revertError) ErrorCode() int {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorData returns the hex encoded revert reason.
|
|
||||||
func (e *revertError) ErrorData() interface{} {
|
|
||||||
return e.reason
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallContract executes a contract call.
|
|
||||||
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 {
|
|
||||||
return nil, errBlockNumberUnsupported
|
|
||||||
}
|
|
||||||
stateDB, err := b.blockchain.State()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If the result contains a revert reason, try to unpack and return it.
|
|
||||||
if len(res.Revert()) > 0 {
|
|
||||||
return nil, newRevertError(res)
|
|
||||||
}
|
|
||||||
return res.Return(), res.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingCallContract executes a contract call on the pending state.
|
|
||||||
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
|
||||||
|
|
||||||
res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If the result contains a revert reason, try to unpack and return it.
|
|
||||||
if len(res.Revert()) > 0 {
|
|
||||||
return nil, newRevertError(res)
|
|
||||||
}
|
|
||||||
return res.Return(), res.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
|
||||||
// the nonce currently pending for the account.
|
|
||||||
func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
|
||||||
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
|
||||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if b.pendingBlock.Header().BaseFee != nil {
|
|
||||||
return b.pendingBlock.Header().BaseFee, nil
|
|
||||||
}
|
|
||||||
return big.NewInt(1), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuggestGasTipCap implements ContractTransactor.SuggestGasTipCap. Since the simulated
|
|
||||||
// chain doesn't have miners, we just return a gas tip of 1 for any call.
|
|
||||||
func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
|
|
||||||
return big.NewInt(1), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EstimateGas executes the requested code against the currently pending block/state and
|
|
||||||
// returns the used amount of gas.
|
|
||||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
// Determine the lowest and highest possible gas limits to binary search in between
|
|
||||||
var (
|
|
||||||
lo uint64 = params.TxGas - 1
|
|
||||||
hi uint64
|
|
||||||
cap uint64
|
|
||||||
)
|
|
||||||
if call.Gas >= params.TxGas {
|
|
||||||
hi = call.Gas
|
|
||||||
} else {
|
|
||||||
hi = b.pendingBlock.GasLimit()
|
|
||||||
}
|
|
||||||
// Normalize the max fee per gas the call is willing to spend.
|
|
||||||
var feeCap *big.Int
|
|
||||||
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
|
|
||||||
return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
||||||
} else if call.GasPrice != nil {
|
|
||||||
feeCap = call.GasPrice
|
|
||||||
} else if call.GasFeeCap != nil {
|
|
||||||
feeCap = call.GasFeeCap
|
|
||||||
} else {
|
|
||||||
feeCap = common.Big0
|
|
||||||
}
|
|
||||||
// Recap the highest gas allowance with account's balance.
|
|
||||||
if feeCap.BitLen() != 0 {
|
|
||||||
balance := b.pendingState.GetBalance(call.From) // from can't be nil
|
|
||||||
available := new(big.Int).Set(balance)
|
|
||||||
if call.Value != nil {
|
|
||||||
if call.Value.Cmp(available) >= 0 {
|
|
||||||
return 0, core.ErrInsufficientFundsForTransfer
|
|
||||||
}
|
|
||||||
available.Sub(available, call.Value)
|
|
||||||
}
|
|
||||||
allowance := new(big.Int).Div(available, feeCap)
|
|
||||||
if allowance.IsUint64() && hi > allowance.Uint64() {
|
|
||||||
transfer := call.Value
|
|
||||||
if transfer == nil {
|
|
||||||
transfer = new(big.Int)
|
|
||||||
}
|
|
||||||
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
|
||||||
"sent", transfer, "feecap", feeCap, "fundable", allowance)
|
|
||||||
hi = allowance.Uint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cap = hi
|
|
||||||
|
|
||||||
// Create a helper to check if a gas allowance results in an executable transaction
|
|
||||||
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
|
||||||
call.Gas = gas
|
|
||||||
|
|
||||||
snapshot := b.pendingState.Snapshot()
|
|
||||||
res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState)
|
|
||||||
b.pendingState.RevertToSnapshot(snapshot)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, core.ErrIntrinsicGas) {
|
|
||||||
return true, nil, nil // Special case, raise gas limit
|
|
||||||
}
|
|
||||||
return true, nil, err // Bail out
|
|
||||||
}
|
|
||||||
return res.Failed(), res, nil
|
|
||||||
}
|
|
||||||
// Execute the binary search and hone in on an executable gas limit
|
|
||||||
for lo+1 < hi {
|
|
||||||
mid := (hi + lo) / 2
|
|
||||||
failed, _, err := executable(mid)
|
|
||||||
|
|
||||||
// If the error is not nil(consensus error), it means the provided message
|
|
||||||
// call or transaction will never be accepted no matter how much gas it is
|
|
||||||
// assigned. Return the error directly, don't struggle any more
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
lo = mid
|
|
||||||
} else {
|
|
||||||
hi = mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Reject the transaction as invalid if it still fails at the highest allowance
|
|
||||||
if hi == cap {
|
|
||||||
failed, result, err := executable(hi)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
|
||||||
if len(result.Revert()) > 0 {
|
|
||||||
return 0, newRevertError(result)
|
|
||||||
}
|
|
||||||
return 0, result.Err
|
|
||||||
}
|
|
||||||
// Otherwise, the specified gas cap is too low
|
|
||||||
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hi, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// callContract implements common code between normal and pending contract calls.
|
|
||||||
// state is modified during execution, make sure to copy it if necessary.
|
|
||||||
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, header *types.Header, stateDB *state.StateDB) (*core.ExecutionResult, error) {
|
|
||||||
// Gas prices post 1559 need to be initialized
|
|
||||||
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
|
|
||||||
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
||||||
}
|
|
||||||
head := b.blockchain.CurrentHeader()
|
|
||||||
if !b.blockchain.Config().IsLondon(head.Number) {
|
|
||||||
// If there's no basefee, then it must be a non-1559 execution
|
|
||||||
if call.GasPrice == nil {
|
|
||||||
call.GasPrice = new(big.Int)
|
|
||||||
}
|
|
||||||
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
|
|
||||||
} else {
|
|
||||||
// A basefee is provided, necessitating 1559-type execution
|
|
||||||
if call.GasPrice != nil {
|
|
||||||
// User specified the legacy gas field, convert to 1559 gas typing
|
|
||||||
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
|
|
||||||
} else {
|
|
||||||
// User specified 1559 gas fields (or none), use those
|
|
||||||
if call.GasFeeCap == nil {
|
|
||||||
call.GasFeeCap = new(big.Int)
|
|
||||||
}
|
|
||||||
if call.GasTipCap == nil {
|
|
||||||
call.GasTipCap = new(big.Int)
|
|
||||||
}
|
|
||||||
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
|
|
||||||
call.GasPrice = new(big.Int)
|
|
||||||
if call.GasFeeCap.BitLen() > 0 || call.GasTipCap.BitLen() > 0 {
|
|
||||||
call.GasPrice = math.BigMin(new(big.Int).Add(call.GasTipCap, head.BaseFee), call.GasFeeCap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ensure message is initialized properly.
|
|
||||||
if call.Gas == 0 {
|
|
||||||
call.Gas = 50000000
|
|
||||||
}
|
|
||||||
if call.Value == nil {
|
|
||||||
call.Value = new(big.Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set infinite balance to the fake caller account.
|
|
||||||
from := stateDB.GetOrNewStateObject(call.From)
|
|
||||||
from.SetBalance(math.MaxBig256)
|
|
||||||
|
|
||||||
// Execute the call.
|
|
||||||
msg := &core.Message{
|
|
||||||
From: call.From,
|
|
||||||
To: call.To,
|
|
||||||
Value: call.Value,
|
|
||||||
GasLimit: call.Gas,
|
|
||||||
GasPrice: call.GasPrice,
|
|
||||||
GasFeeCap: call.GasFeeCap,
|
|
||||||
GasTipCap: call.GasTipCap,
|
|
||||||
Data: call.Data,
|
|
||||||
AccessList: call.AccessList,
|
|
||||||
SkipAccountChecks: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new environment which holds all relevant information
|
|
||||||
// about the transaction and calling mechanisms.
|
|
||||||
txContext := core.NewEVMTxContext(msg)
|
|
||||||
evmContext := core.NewEVMBlockContext(header, b.blockchain, nil)
|
|
||||||
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
|
|
||||||
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
|
|
||||||
|
|
||||||
return core.ApplyMessage(vmEnv, msg, gasPool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTransaction updates the pending block to include the given transaction.
|
|
||||||
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
// Get the last block
|
|
||||||
block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not fetch parent")
|
|
||||||
}
|
|
||||||
// Check transaction validity
|
|
||||||
signer := types.MakeSigner(b.blockchain.Config(), block.Number())
|
|
||||||
sender, err := types.Sender(signer, tx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid transaction: %v", err)
|
|
||||||
}
|
|
||||||
nonce := b.pendingState.GetNonce(sender)
|
|
||||||
if tx.Nonce() != nonce {
|
|
||||||
return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)
|
|
||||||
}
|
|
||||||
// Include tx in chain
|
|
||||||
blocks, receipts := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
|
||||||
block.AddTxWithChain(b.blockchain, tx)
|
|
||||||
}
|
|
||||||
block.AddTxWithChain(b.blockchain, tx)
|
|
||||||
})
|
|
||||||
stateDB, _ := b.blockchain.State()
|
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
|
||||||
b.pendingReceipts = receipts[0]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterLogs executes a log filter operation, blocking during execution and
|
|
||||||
// returning all the results in one batch.
|
|
||||||
//
|
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
|
||||||
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
|
||||||
var filter *filters.Filter
|
|
||||||
if query.BlockHash != nil {
|
|
||||||
// Block filter requested, construct a single-shot filter
|
|
||||||
filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics)
|
|
||||||
} else {
|
|
||||||
// Initialize unset filter boundaries to run from genesis to chain head
|
|
||||||
from := int64(0)
|
|
||||||
if query.FromBlock != nil {
|
|
||||||
from = query.FromBlock.Int64()
|
|
||||||
}
|
|
||||||
to := int64(-1)
|
|
||||||
if query.ToBlock != nil {
|
|
||||||
to = query.ToBlock.Int64()
|
|
||||||
}
|
|
||||||
// Construct the range filter
|
|
||||||
filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics)
|
|
||||||
}
|
|
||||||
// Run the filter and return all the logs
|
|
||||||
logs, err := filter.Logs(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res := make([]types.Log, len(logs))
|
|
||||||
for i, nLog := range logs {
|
|
||||||
res[i] = *nLog
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
|
||||||
// subscription immediately, which can be used to stream the found events.
|
|
||||||
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
|
||||||
// Subscribe to contract events
|
|
||||||
sink := make(chan []*types.Log)
|
|
||||||
|
|
||||||
sub, err := b.events.SubscribeLogs(query, sink)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Since we're getting logs in batches, we need to flatten them into a plain stream
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case logs := <-sink:
|
|
||||||
for _, nlog := range logs {
|
|
||||||
select {
|
|
||||||
case ch <- *nlog:
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeNewHead returns an event subscription for a new header.
|
|
||||||
func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
|
|
||||||
// subscribe to a new head
|
|
||||||
sink := make(chan *types.Header)
|
|
||||||
sub := b.events.SubscribeNewHeads(sink)
|
|
||||||
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case head := <-sink:
|
|
||||||
select {
|
|
||||||
case ch <- head:
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdjustTime adds a time shift to the simulated clock.
|
|
||||||
// It can only be called on empty blocks.
|
|
||||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if len(b.pendingBlock.Transactions()) != 0 {
|
|
||||||
return errors.New("Could not adjust time on non-empty block")
|
|
||||||
}
|
|
||||||
// Get the last block
|
|
||||||
block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash())
|
|
||||||
if block == nil {
|
|
||||||
return fmt.Errorf("could not find parent")
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
|
||||||
block.OffsetTime(int64(adjustment.Seconds()))
|
|
||||||
})
|
|
||||||
stateDB, _ := b.blockchain.State()
|
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blockchain returns the underlying blockchain.
|
|
||||||
func (b *SimulatedBackend) Blockchain() *core.BlockChain {
|
|
||||||
return b.blockchain
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterBackend implements filters.Backend to support filtering for logs without
|
|
||||||
// taking bloom-bits acceleration structures into account.
|
|
||||||
type filterBackend struct {
|
|
||||||
db ethdb.Database
|
|
||||||
bc *core.BlockChain
|
|
||||||
backend *SimulatedBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
|
||||||
|
|
||||||
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
|
||||||
|
|
||||||
func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
|
||||||
switch number {
|
|
||||||
case rpc.PendingBlockNumber:
|
|
||||||
if block := fb.backend.pendingBlock; block != nil {
|
|
||||||
return block.Header(), nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
case rpc.LatestBlockNumber:
|
|
||||||
return fb.bc.CurrentHeader(), nil
|
|
||||||
case rpc.FinalizedBlockNumber:
|
|
||||||
return fb.bc.CurrentFinalBlock(), nil
|
|
||||||
case rpc.SafeBlockNumber:
|
|
||||||
return fb.bc.CurrentSafeBlock(), nil
|
|
||||||
default:
|
|
||||||
return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
|
||||||
return fb.bc.GetHeaderByHash(hash), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
|
|
||||||
if body := fb.bc.GetBody(hash); body != nil {
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("block body not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
|
|
||||||
return fb.backend.pendingBlock, fb.backend.pendingReceipts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
|
||||||
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
|
||||||
logs := rawdb.ReadLogs(fb.db, hash, number, fb.bc.Config())
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
|
||||||
return nullSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
|
||||||
return fb.bc.SubscribeChainEvent(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
|
||||||
return fb.bc.SubscribeRemovedLogsEvent(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return fb.bc.SubscribeLogsEvent(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return nullSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
|
|
||||||
|
|
||||||
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) ChainConfig() *params.ChainConfig {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) CurrentHeader() *types.Header {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func nullSubscription() event.Subscription {
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
<-quit
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,7 @@ type CallOpts struct {
|
|||||||
Pending bool // Whether to operate on the pending state or the last known one
|
Pending bool // Whether to operate on the pending state or the last known one
|
||||||
From common.Address // Optional the sender address, otherwise the first account is used
|
From common.Address // Optional the sender address, otherwise the first account is used
|
||||||
BlockNumber *big.Int // Optional the block number on which the call should be performed
|
BlockNumber *big.Int // Optional the block number on which the call should be performed
|
||||||
|
BlockHash common.Hash // Optional the block hash on which the call should be performed
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +190,23 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
|||||||
return ErrNoCode
|
return ErrNoCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if opts.BlockHash != (common.Hash{}) {
|
||||||
|
bh, ok := c.caller.(BlockHashContractCaller)
|
||||||
|
if !ok {
|
||||||
|
return ErrNoBlockHashState
|
||||||
|
}
|
||||||
|
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
|
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(code) == 0 {
|
||||||
|
return ErrNoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -220,7 +238,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// todo(rjl493456442) check the method is payable or not,
|
// todo(rjl493456442) check whether the method is payable or not,
|
||||||
// reject invalid transaction at the first place
|
// reject invalid transaction at the first place
|
||||||
return c.transact(opts, &c.address, input)
|
return c.transact(opts, &c.address, input)
|
||||||
}
|
}
|
||||||
@ -228,7 +246,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
|
|||||||
// RawTransact initiates a transaction with the given raw calldata as the input.
|
// RawTransact initiates a transaction with the given raw calldata as the input.
|
||||||
// It's usually used to initiate transactions for invoking **Fallback** function.
|
// It's usually used to initiate transactions for invoking **Fallback** function.
|
||||||
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
|
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
|
||||||
// todo(rjl493456442) check the method is payable or not,
|
// todo(rjl493456442) check whether the method is payable or not,
|
||||||
// reject invalid transaction at the first place
|
// reject invalid transaction at the first place
|
||||||
return c.transact(opts, &c.address, calldata)
|
return c.transact(opts, &c.address, calldata)
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,28 @@ func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ether
|
|||||||
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockBlockHashCaller struct {
|
||||||
|
*mockCaller
|
||||||
|
codeAtHashBytes []byte
|
||||||
|
codeAtHashErr error
|
||||||
|
codeAtHashCalled bool
|
||||||
|
callContractAtHashCalled bool
|
||||||
|
callContractAtHashBytes []byte
|
||||||
|
callContractAtHashErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.codeAtHashCalled = true
|
||||||
|
return mc.codeAtHashBytes, mc.codeAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.callContractAtHashCalled = true
|
||||||
|
return mc.callContractAtHashBytes, mc.callContractAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
func TestPassingBlockNumber(t *testing.T) {
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mc := &mockPendingCaller{
|
mc := &mockPendingCaller{
|
||||||
mockCaller: &mockCaller{
|
mockCaller: &mockCaller{
|
||||||
codeAtBytes: []byte{1, 2, 3},
|
codeAtBytes: []byte{1, 2, 3},
|
||||||
@ -166,6 +187,7 @@ func TestPassingBlockNumber(t *testing.T) {
|
|||||||
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
|
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
|
||||||
|
|
||||||
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
hash := crypto.Keccak256Hash([]byte("testName"))
|
hash := crypto.Keccak256Hash([]byte("testName"))
|
||||||
topics := []common.Hash{
|
topics := []common.Hash{
|
||||||
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
|
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
|
||||||
@ -187,6 +209,7 @@ func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockLog := newMockLog(nil, common.HexToHash("0x0"))
|
mockLog := newMockLog(nil, common.HexToHash("0x0"))
|
||||||
|
|
||||||
abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]`
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]`
|
||||||
@ -204,6 +227,7 @@ func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
|
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -229,6 +253,7 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
|
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -254,6 +279,7 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
|
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
|
||||||
addrBytes := mockAddress.Bytes()
|
addrBytes := mockAddress.Bytes()
|
||||||
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
|
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
|
||||||
@ -280,6 +306,7 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bytes := []byte{1, 2, 3, 4, 5}
|
bytes := []byte{1, 2, 3, 4, 5}
|
||||||
hash := crypto.Keccak256Hash(bytes)
|
hash := crypto.Keccak256Hash(bytes)
|
||||||
topics := []common.Hash{
|
topics := []common.Hash{
|
||||||
@ -302,6 +329,7 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactGasFee(t *testing.T) {
|
func TestTransactGasFee(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
// GasTipCap and GasFeeCap
|
// GasTipCap and GasFeeCap
|
||||||
@ -377,6 +405,7 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCall(t *testing.T) {
|
func TestCall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var method, methodWithArg = "something", "somethingArrrrg"
|
var method, methodWithArg = "something", "somethingArrrrg"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name, method string
|
name, method string
|
||||||
@ -400,6 +429,15 @@ func TestCall(t *testing.T) {
|
|||||||
Pending: true,
|
Pending: true,
|
||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "ok hash",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashBytes: []byte{0},
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
}, {
|
}, {
|
||||||
name: "pack error, no method",
|
name: "pack error, no method",
|
||||||
mc: new(mockCaller),
|
mc: new(mockCaller),
|
||||||
@ -413,6 +451,14 @@ func TestCall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
wantErrExact: bind.ErrNoPendingState,
|
wantErrExact: bind.ErrNoPendingState,
|
||||||
|
}, {
|
||||||
|
name: "interface error, blockHash but not a BlockHashContractCaller",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoBlockHashState,
|
||||||
}, {
|
}, {
|
||||||
name: "pending call canceled",
|
name: "pending call canceled",
|
||||||
mc: &mockPendingCaller{
|
mc: &mockPendingCaller{
|
||||||
@ -460,6 +506,34 @@ func TestCall(t *testing.T) {
|
|||||||
mc: new(mockCaller),
|
mc: new(mockCaller),
|
||||||
method: method,
|
method: method,
|
||||||
wantErrExact: bind.ErrNoCode,
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "call contract at hash error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
callContractAtHashErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "code at error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashErr: errors.New(""),
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no code at hash",
|
||||||
|
mc: new(mockBlockHashCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
}, {
|
}, {
|
||||||
name: "unpack error missing arg",
|
name: "unpack error missing arg",
|
||||||
mc: &mockCaller{
|
mc: &mockCaller{
|
||||||
@ -507,6 +581,7 @@ func TestCall(t *testing.T) {
|
|||||||
|
|
||||||
// TestCrashers contains some strings which previously caused the abi codec to crash.
|
// TestCrashers contains some strings which previously caused the abi codec to crash.
|
||||||
func TestCrashers(t *testing.T) {
|
func TestCrashers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
|
||||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
|
||||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
|
||||||
|
@ -79,7 +79,7 @@ func isKeyWord(arg string) bool {
|
|||||||
|
|
||||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||||
// to be used as is in client code, but rather as an intermediate struct which
|
// to be used as is in client code, but rather as an intermediate struct which
|
||||||
// enforces compile time type safety and naming convention opposed to having to
|
// enforces compile time type safety and naming convention as opposed to having to
|
||||||
// manually maintain hard coded strings that break on runtime.
|
// manually maintain hard coded strings that break on runtime.
|
||||||
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
||||||
var (
|
var (
|
||||||
@ -133,12 +133,19 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||||
normalized := original
|
normalized := original
|
||||||
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||||
|
|
||||||
// Ensure there is no duplicated identifier
|
// Ensure there is no duplicated identifier
|
||||||
var identifiers = callIdentifiers
|
var identifiers = callIdentifiers
|
||||||
if !original.IsConstant() {
|
if !original.IsConstant() {
|
||||||
identifiers = transactIdentifiers
|
identifiers = transactIdentifiers
|
||||||
}
|
}
|
||||||
|
// Name shouldn't start with a digit. It will make the generated code invalid.
|
||||||
|
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
|
||||||
|
normalizedName = fmt.Sprintf("M%s", normalizedName)
|
||||||
|
normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool {
|
||||||
|
_, ok := identifiers[name]
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
}
|
||||||
if identifiers[normalizedName] {
|
if identifiers[normalizedName] {
|
||||||
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||||
}
|
}
|
||||||
@ -182,6 +189,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||||||
|
|
||||||
// Ensure there is no duplicated identifier
|
// Ensure there is no duplicated identifier
|
||||||
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||||
|
// Name shouldn't start with a digit. It will make the generated code invalid.
|
||||||
|
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
|
||||||
|
normalizedName = fmt.Sprintf("E%s", normalizedName)
|
||||||
|
normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool {
|
||||||
|
_, ok := eventIdentifiers[name]
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
}
|
||||||
if eventIdentifiers[normalizedName] {
|
if eventIdentifiers[normalizedName] {
|
||||||
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||||
}
|
}
|
||||||
@ -348,7 +363,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
|||||||
// parameters that are not value types i.e. arrays and structs are not
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
// stored directly but instead a keccak256-hash of an encoding is stored.
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
//
|
//
|
||||||
// We only convert stringS and bytes to hash, still need to deal with
|
// We only convert strings and bytes to hash, still need to deal with
|
||||||
// array(both fixed-size and dynamic-size) and struct.
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
if bound == "string" || bound == "[]byte" {
|
if bound == "string" || bound == "[]byte" {
|
||||||
bound = "common.Hash"
|
bound = "common.Hash"
|
||||||
|
@ -289,7 +289,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -297,7 +297,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy an interaction tester contract and call a transaction on it
|
// Deploy an interaction tester contract and call a transaction on it
|
||||||
@ -305,6 +305,7 @@ var bindTests = []struct {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to deploy interactor contract: %v", err)
|
t.Fatalf("Failed to deploy interactor contract: %v", err)
|
||||||
}
|
}
|
||||||
|
sim.Commit()
|
||||||
if _, err := interactor.Transact(auth, "Transact string"); err != nil {
|
if _, err := interactor.Transact(auth, "Transact string"); err != nil {
|
||||||
t.Fatalf("Failed to transact with interactor contract: %v", err)
|
t.Fatalf("Failed to transact with interactor contract: %v", err)
|
||||||
}
|
}
|
||||||
@ -344,7 +345,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -352,7 +353,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a tuple tester contract and execute a structured call on it
|
// Deploy a tuple tester contract and execute a structured call on it
|
||||||
@ -390,7 +391,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -398,7 +399,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a tuple tester contract and execute a structured call on it
|
// Deploy a tuple tester contract and execute a structured call on it
|
||||||
@ -448,7 +449,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -456,7 +457,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a slice tester contract and execute a n array call on it
|
// Deploy a slice tester contract and execute a n array call on it
|
||||||
@ -496,7 +497,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -504,7 +505,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a default method invoker contract and execute its default method
|
// Deploy a default method invoker contract and execute its default method
|
||||||
@ -512,6 +513,7 @@ var bindTests = []struct {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to deploy defaulter contract: %v", err)
|
t.Fatalf("Failed to deploy defaulter contract: %v", err)
|
||||||
}
|
}
|
||||||
|
sim.Commit()
|
||||||
if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil {
|
if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil {
|
||||||
t.Fatalf("Failed to invoke default method: %v", err)
|
t.Fatalf("Failed to invoke default method: %v", err)
|
||||||
}
|
}
|
||||||
@ -562,7 +564,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -570,7 +572,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a structs method invoker contract and execute its default method
|
// Deploy a structs method invoker contract and execute its default method
|
||||||
@ -608,12 +610,12 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Create a simulator and wrap a non-deployed contract
|
// Create a simulator and wrap a non-deployed contract
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000))
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
nonexistent, err := NewNonExistent(common.Address{}, sim)
|
nonexistent, err := NewNonExistent(common.Address{}, sim)
|
||||||
@ -647,12 +649,12 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Create a simulator and wrap a non-deployed contract
|
// Create a simulator and wrap a non-deployed contract
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000))
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
nonexistent, err := NewNonExistentStruct(common.Address{}, sim)
|
nonexistent, err := NewNonExistentStruct(common.Address{}, sim)
|
||||||
@ -694,7 +696,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -702,7 +704,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a funky gas pattern contract
|
// Deploy a funky gas pattern contract
|
||||||
@ -744,7 +746,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -752,7 +754,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a sender tester contract and execute a structured call on it
|
// Deploy a sender tester contract and execute a structured call on it
|
||||||
@ -819,7 +821,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -827,7 +829,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a underscorer tester contract and execute a structured call on it
|
// Deploy a underscorer tester contract and execute a structured call on it
|
||||||
@ -913,7 +915,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -921,7 +923,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy an eventer contract
|
// Deploy an eventer contract
|
||||||
@ -1103,7 +1105,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -1111,7 +1113,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
//deploy the test contract
|
//deploy the test contract
|
||||||
@ -1238,7 +1240,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
|
|
||||||
@ -1246,7 +1248,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
_, _, contract, err := DeployTuple(auth, sim)
|
_, _, contract, err := DeployTuple(auth, sim)
|
||||||
@ -1380,7 +1382,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -1388,7 +1390,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
//deploy the test contract
|
//deploy the test contract
|
||||||
@ -1446,14 +1448,14 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Initialize test accounts
|
// Initialize test accounts
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// deploy the test contract
|
// deploy the test contract
|
||||||
@ -1535,7 +1537,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Initialize test accounts
|
// Initialize test accounts
|
||||||
@ -1543,7 +1545,7 @@ var bindTests = []struct {
|
|||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
// Deploy registrar contract
|
// Deploy registrar contract
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
@ -1598,14 +1600,14 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
// Deploy registrar contract
|
// Deploy registrar contract
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
@ -1659,7 +1661,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@ -1667,7 +1669,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a tester contract and execute a structured call on it
|
// Deploy a tester contract and execute a structured call on it
|
||||||
@ -1677,7 +1679,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
// This test the existence of the free retreiver call for view and pure functions
|
// This test the existence of the free retriever call for view and pure functions
|
||||||
if num, err := pav.PureFunc(nil); err != nil {
|
if num, err := pav.PureFunc(nil); err != nil {
|
||||||
t.Fatalf("Failed to call anonymous field retriever: %v", err)
|
t.Fatalf("Failed to call anonymous field retriever: %v", err)
|
||||||
} else if num.Cmp(big.NewInt(42)) != 0 {
|
} else if num.Cmp(big.NewInt(42)) != 0 {
|
||||||
@ -1720,14 +1722,14 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
@ -1808,7 +1810,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@ -1816,7 +1818,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@ -1874,11 +1876,12 @@ var bindTests = []struct {
|
|||||||
[]string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"},
|
[]string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"},
|
||||||
[]string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
[]string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
||||||
`
|
`
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@ -1886,7 +1889,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@ -1895,7 +1898,7 @@ var bindTests = []struct {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
_, err = bind.WaitDeployed(nil, sim, tx)
|
_, err = bind.WaitDeployed(context.Background(), sim, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -1926,11 +1929,12 @@ var bindTests = []struct {
|
|||||||
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
|
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
|
||||||
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
|
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
|
||||||
imports: `
|
imports: `
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@ -1938,7 +1942,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@ -1948,7 +1952,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
|
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||||
t.Logf("Deployment tx: %+v", tx)
|
t.Logf("Deployment tx: %+v", tx)
|
||||||
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
||||||
}
|
}
|
||||||
@ -1974,11 +1978,12 @@ var bindTests = []struct {
|
|||||||
bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"},
|
bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"},
|
||||||
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
|
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
|
||||||
imports: `
|
imports: `
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@ -1986,7 +1991,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@ -1996,7 +2001,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
|
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||||
t.Logf("Deployment tx: %+v", tx)
|
t.Logf("Deployment tx: %+v", tx)
|
||||||
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
||||||
}
|
}
|
||||||
@ -2014,11 +2019,12 @@ var bindTests = []struct {
|
|||||||
bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"},
|
bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"},
|
||||||
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
||||||
imports: `
|
imports: `
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@ -2026,7 +2032,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
_, tx, _, err := DeployRangeKeyword(user, sim)
|
_, tx, _, err := DeployRangeKeyword(user, sim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2034,16 +2040,40 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
|
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||||
t.Errorf("error deploying the contract: %v", err)
|
t.Errorf("error deploying the contract: %v", err)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
}, {
|
||||||
|
name: "NumericMethodName",
|
||||||
|
contract: `
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
pragma solidity >=0.4.22 <0.9.0;
|
||||||
|
|
||||||
|
contract NumericMethodName {
|
||||||
|
event _1TestEvent(address _param);
|
||||||
|
function _1test() public pure {}
|
||||||
|
function __1test() public pure {}
|
||||||
|
function __2test() public pure {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
bytecode: []string{"0x6080604052348015600f57600080fd5b5060958061001e6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80639d993132146041578063d02767c7146049578063ffa02795146051575b600080fd5b60476059565b005b604f605b565b005b6057605d565b005b565b565b56fea26469706673582212200382ca602dff96a7e2ba54657985e2b4ac423a56abe4a1f0667bc635c4d4371f64736f6c63430008110033"},
|
||||||
|
abi: []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_param","type":"address"}],"name":"_1TestEvent","type":"event"},{"inputs":[],"name":"_1test","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"__1test","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"__2test","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
||||||
|
imports: `
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
`,
|
||||||
|
tester: `
|
||||||
|
if b, err := NewNumericMethodName(common.Address{}, nil); b == nil || err != nil {
|
||||||
|
t.Fatalf("combined binding (%v) nil or error (%v) not nil", b, nil)
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that packages generated by the binder can be successfully compiled and
|
// Tests that packages generated by the binder can be successfully compiled and
|
||||||
// the requested tester run against it.
|
// the requested tester run against it.
|
||||||
func TestGolangBindings(t *testing.T) {
|
func TestGolangBindings(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Skip the test if no Go command can be found
|
// Skip the test if no Go command can be found
|
||||||
gocmd := runtime.GOROOT() + "/bin/go"
|
gocmd := runtime.GOROOT() + "/bin/go"
|
||||||
if !common.FileExist(gocmd) {
|
if !common.FileExist(gocmd) {
|
||||||
|
@ -24,11 +24,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"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/ethclient/simulated"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
@ -53,21 +53,21 @@ var waitDeployedTests = map[string]struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitDeployed(t *testing.T) {
|
func TestWaitDeployed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for name, test := range waitDeployedTests {
|
for name, test := range waitDeployedTests {
|
||||||
backend := backends.NewSimulatedBackend(
|
backend := simulated.NewBackend(
|
||||||
core.GenesisAlloc{
|
types.GenesisAlloc{
|
||||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
||||||
},
|
},
|
||||||
10000000,
|
|
||||||
)
|
)
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
|
|
||||||
// Create the transaction
|
// Create the transaction
|
||||||
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei))
|
||||||
|
|
||||||
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code))
|
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code))
|
||||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey)
|
||||||
|
|
||||||
// Wait for it to get mined in the background.
|
// Wait for it to get mined in the background.
|
||||||
var (
|
var (
|
||||||
@ -77,12 +77,12 @@ func TestWaitDeployed(t *testing.T) {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
address, err = bind.WaitDeployed(ctx, backend, tx)
|
address, err = bind.WaitDeployed(ctx, backend.Client(), tx)
|
||||||
close(mined)
|
close(mined)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Send and mine the transaction.
|
// Send and mine the transaction.
|
||||||
backend.SendTransaction(ctx, tx)
|
backend.Client().SendTransaction(ctx, tx)
|
||||||
backend.Commit()
|
backend.Commit()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -100,41 +100,40 @@ func TestWaitDeployed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitDeployedCornerCases(t *testing.T) {
|
func TestWaitDeployedCornerCases(t *testing.T) {
|
||||||
backend := backends.NewSimulatedBackend(
|
backend := simulated.NewBackend(
|
||||||
core.GenesisAlloc{
|
types.GenesisAlloc{
|
||||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
||||||
},
|
},
|
||||||
10000000,
|
|
||||||
)
|
)
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
|
|
||||||
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||||
|
|
||||||
// Create a transaction to an account.
|
// Create a transaction to an account.
|
||||||
code := "6060604052600a8060106000396000f360606040526008565b00"
|
code := "6060604052600a8060106000396000f360606040526008565b00"
|
||||||
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
backend.SendTransaction(ctx, tx)
|
backend.Client().SendTransaction(ctx, tx)
|
||||||
backend.Commit()
|
backend.Commit()
|
||||||
notContentCreation := errors.New("tx is not contract creation")
|
notContractCreation := errors.New("tx is not contract creation")
|
||||||
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() {
|
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() {
|
||||||
t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err)
|
t.Errorf("error mismatch: want %q, got %q, ", notContractCreation, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a transaction that is not mined.
|
// Create a transaction that is not mined.
|
||||||
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
contextCanceled := errors.New("context canceled")
|
contextCanceled := errors.New("context canceled")
|
||||||
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() {
|
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != contextCanceled.Error() {
|
||||||
t.Errorf("error missmatch: want %q, got %q, ", contextCanceled, err)
|
t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
backend.SendTransaction(ctx, tx)
|
backend.Client().SendTransaction(ctx, tx)
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package abi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -78,16 +77,16 @@ func NewError(name string, inputs Arguments) Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) String() string {
|
func (e Error) String() string {
|
||||||
return e.str
|
return e.str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Unpack(data []byte) (interface{}, error) {
|
func (e *Error) Unpack(data []byte) (interface{}, error) {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", fmt.Errorf("insufficient data for unpacking: have %d, want at least 4", len(data))
|
||||||
}
|
}
|
||||||
if !bytes.Equal(data[:4], e.ID[:4]) {
|
if !bytes.Equal(data[:4], e.ID[:4]) {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", fmt.Errorf("invalid identifier, have %#x want %#x", data[:4], e.ID[:4])
|
||||||
}
|
}
|
||||||
return e.Inputs.Unpack(data[4:])
|
return e.Inputs.Unpack(data[4:])
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa
|
|||||||
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||||
|
|
||||||
func TestEventId(t *testing.T) {
|
func TestEventId(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
expectations map[string]common.Hash
|
expectations map[string]common.Hash
|
||||||
@ -112,6 +113,7 @@ func TestEventId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEventString(t *testing.T) {
|
func TestEventString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
expectations map[string]string
|
expectations map[string]string
|
||||||
@ -146,6 +148,7 @@ func TestEventString(t *testing.T) {
|
|||||||
|
|
||||||
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||||
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -161,6 +164,7 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEventTupleUnpack(t *testing.T) {
|
func TestEventTupleUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type EventTransfer struct {
|
type EventTransfer struct {
|
||||||
Value *big.Int
|
Value *big.Int
|
||||||
}
|
}
|
||||||
@ -351,6 +355,7 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass
|
|||||||
|
|
||||||
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
||||||
func TestEventUnpackIndexed(t *testing.T) {
|
func TestEventUnpackIndexed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Value1 uint8 // indexed
|
Value1 uint8 // indexed
|
||||||
@ -368,6 +373,7 @@ func TestEventUnpackIndexed(t *testing.T) {
|
|||||||
|
|
||||||
// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input.
|
// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input.
|
||||||
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Value1 [2]uint8 // indexed
|
Value1 [2]uint8 // indexed
|
||||||
|
@ -117,24 +117,23 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str
|
|||||||
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
|
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
|
||||||
id = crypto.Keccak256([]byte(sig))[:4]
|
id = crypto.Keccak256([]byte(sig))[:4]
|
||||||
}
|
}
|
||||||
// Extract meaningful state mutability of solidity method.
|
|
||||||
// If it's default value, never print it.
|
|
||||||
state := mutability
|
|
||||||
if state == "nonpayable" {
|
|
||||||
state = ""
|
|
||||||
}
|
|
||||||
if state != "" {
|
|
||||||
state = state + " "
|
|
||||||
}
|
|
||||||
identity := fmt.Sprintf("function %v", rawName)
|
identity := fmt.Sprintf("function %v", rawName)
|
||||||
if funType == Fallback {
|
switch funType {
|
||||||
|
case Fallback:
|
||||||
identity = "fallback"
|
identity = "fallback"
|
||||||
} else if funType == Receive {
|
case Receive:
|
||||||
identity = "receive"
|
identity = "receive"
|
||||||
} else if funType == Constructor {
|
case Constructor:
|
||||||
identity = "constructor"
|
identity = "constructor"
|
||||||
}
|
}
|
||||||
str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", "))
|
var str string
|
||||||
|
// Extract meaningful state mutability of solidity method.
|
||||||
|
// If it's empty string or default value "nonpayable", never print it.
|
||||||
|
if mutability == "" || mutability == "nonpayable" {
|
||||||
|
str = fmt.Sprintf("%v(%v) returns(%v)", identity, strings.Join(inputNames, ", "), strings.Join(outputNames, ", "))
|
||||||
|
} else {
|
||||||
|
str = fmt.Sprintf("%v(%v) %s returns(%v)", identity, strings.Join(inputNames, ", "), mutability, strings.Join(outputNames, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return Method{
|
return Method{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -35,6 +35,7 @@ const methoddata = `
|
|||||||
]`
|
]`
|
||||||
|
|
||||||
func TestMethodString(t *testing.T) {
|
func TestMethodString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
method string
|
method string
|
||||||
expectation string
|
expectation string
|
||||||
@ -84,11 +85,12 @@ func TestMethodString(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
var got string
|
var got string
|
||||||
if test.method == "fallback" {
|
switch test.method {
|
||||||
|
case "fallback":
|
||||||
got = abi.Fallback.String()
|
got = abi.Fallback.String()
|
||||||
} else if test.method == "receive" {
|
case "receive":
|
||||||
got = abi.Receive.String()
|
got = abi.Receive.String()
|
||||||
} else {
|
default:
|
||||||
got = abi.Methods[test.method].String()
|
got = abi.Methods[test.method].String()
|
||||||
}
|
}
|
||||||
if got != test.expectation {
|
if got != test.expectation {
|
||||||
@ -98,6 +100,7 @@ func TestMethodString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodSig(t *testing.T) {
|
func TestMethodSig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var cases = []struct {
|
var cases = []struct {
|
||||||
method string
|
method string
|
||||||
expect string
|
expect string
|
||||||
|
@ -57,7 +57,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
|||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
if reflectValue.Type() != reflect.TypeOf([]byte{}) {
|
if reflectValue.Type() != reflect.TypeOf([]byte{}) {
|
||||||
return []byte{}, errors.New("Bytes type is neither slice nor array")
|
return []byte{}, errors.New("bytes type is neither slice nor array")
|
||||||
}
|
}
|
||||||
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
|
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
|
||||||
case FixedBytesTy, FunctionTy:
|
case FixedBytesTy, FunctionTy:
|
||||||
@ -66,7 +66,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
|
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
|
||||||
default:
|
default:
|
||||||
return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T)
|
return []byte{}, fmt.Errorf("could not pack element, unknown type: %v", t.T)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +32,11 @@ import (
|
|||||||
|
|
||||||
// TestPack tests the general pack/unpack tests in packing_test.go
|
// TestPack tests the general pack/unpack tests in packing_test.go
|
||||||
func TestPack(t *testing.T) {
|
func TestPack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range packUnpackTests {
|
for i, test := range packUnpackTests {
|
||||||
|
i, test := i, test
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
encb, err := hex.DecodeString(test.packed)
|
encb, err := hex.DecodeString(test.packed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("invalid hex %s: %v", test.packed, err)
|
t.Fatalf("invalid hex %s: %v", test.packed, err)
|
||||||
@ -57,6 +60,7 @@ func TestPack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodPack(t *testing.T) {
|
func TestMethodPack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -177,6 +181,7 @@ func TestMethodPack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPackNumber(t *testing.T) {
|
func TestPackNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
value reflect.Value
|
value reflect.Value
|
||||||
packed []byte
|
packed []byte
|
||||||
|
@ -134,7 +134,7 @@ func setSlice(dst, src reflect.Value) error {
|
|||||||
dst.Set(slice)
|
dst.Set(slice)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("Cannot set slice, destination not settable")
|
return errors.New("cannot set slice, destination not settable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setArray(dst, src reflect.Value) error {
|
func setArray(dst, src reflect.Value) error {
|
||||||
@ -155,7 +155,7 @@ func setArray(dst, src reflect.Value) error {
|
|||||||
dst.Set(array)
|
dst.Set(array)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("Cannot set array, destination not settable")
|
return errors.New("cannot set array, destination not settable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setStruct(dst, src reflect.Value) error {
|
func setStruct(dst, src reflect.Value) error {
|
||||||
@ -163,7 +163,7 @@ func setStruct(dst, src reflect.Value) error {
|
|||||||
srcField := src.Field(i)
|
srcField := src.Field(i)
|
||||||
dstField := dst.Field(i)
|
dstField := dst.Field(i)
|
||||||
if !dstField.IsValid() || !srcField.IsValid() {
|
if !dstField.IsValid() || !srcField.IsValid() {
|
||||||
return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField)
|
return fmt.Errorf("could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField)
|
||||||
}
|
}
|
||||||
if err := set(dstField, srcField); err != nil {
|
if err := set(dstField, srcField); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -228,7 +228,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
|
|||||||
structFieldName := ToCamelCase(argName)
|
structFieldName := ToCamelCase(argName)
|
||||||
|
|
||||||
if structFieldName == "" {
|
if structFieldName == "" {
|
||||||
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
return nil, errors.New("abi: purely underscored output cannot unpack to struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
||||||
|
@ -170,8 +170,11 @@ var reflectTests = []reflectTest{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReflectNameToStruct(t *testing.T) {
|
func TestReflectNameToStruct(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for _, test := range reflectTests {
|
for _, test := range reflectTests {
|
||||||
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
||||||
if len(test.err) > 0 {
|
if len(test.err) > 0 {
|
||||||
if err == nil || err.Error() != test.err {
|
if err == nil || err.Error() != test.err {
|
||||||
@ -192,6 +195,7 @@ func TestReflectNameToStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertType(t *testing.T) {
|
func TestConvertType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Test Basic Struct
|
// Test Basic Struct
|
||||||
type T struct {
|
type T struct {
|
||||||
X *big.Int
|
X *big.Int
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ func isIdentifierSymbol(c byte) bool {
|
|||||||
|
|
||||||
func parseToken(unescapedSelector string, isIdent bool) (string, string, error) {
|
func parseToken(unescapedSelector string, isIdent bool) (string, string, error) {
|
||||||
if len(unescapedSelector) == 0 {
|
if len(unescapedSelector) == 0 {
|
||||||
return "", "", fmt.Errorf("empty token")
|
return "", "", errors.New("empty token")
|
||||||
}
|
}
|
||||||
firstChar := unescapedSelector[0]
|
firstChar := unescapedSelector[0]
|
||||||
position := 1
|
position := 1
|
||||||
@ -110,7 +111,7 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error)
|
|||||||
|
|
||||||
func parseType(unescapedSelector string) (interface{}, string, error) {
|
func parseType(unescapedSelector string) (interface{}, string, error) {
|
||||||
if len(unescapedSelector) == 0 {
|
if len(unescapedSelector) == 0 {
|
||||||
return nil, "", fmt.Errorf("empty type")
|
return nil, "", errors.New("empty type")
|
||||||
}
|
}
|
||||||
if unescapedSelector[0] == '(' {
|
if unescapedSelector[0] == '(' {
|
||||||
return parseCompositeType(unescapedSelector)
|
return parseCompositeType(unescapedSelector)
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestParseSelector(t *testing.T) {
|
func TestParseSelector(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mkType := func(types ...interface{}) []ArgumentMarshaling {
|
mkType := func(types ...interface{}) []ArgumentMarshaling {
|
||||||
var result []ArgumentMarshaling
|
var result []ArgumentMarshaling
|
||||||
for i, typeOrComponents := range types {
|
for i, typeOrComponents := range types {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,8 +42,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
|||||||
case common.Address:
|
case common.Address:
|
||||||
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
||||||
case *big.Int:
|
case *big.Int:
|
||||||
blob := rule.Bytes()
|
copy(topic[:], math.U256Bytes(rule))
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case bool:
|
case bool:
|
||||||
if rule {
|
if rule {
|
||||||
topic[common.HashLength-1] = 1
|
topic[common.HashLength-1] = 1
|
||||||
@ -75,7 +75,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
|||||||
copy(topic[:], hash[:])
|
copy(topic[:], hash[:])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// todo(rjl493456442) according solidity documentation, indexed event
|
// todo(rjl493456442) according to solidity documentation, indexed event
|
||||||
// parameters that are not value types i.e. arrays and structs are not
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
// stored directly but instead a keccak256-hash of an encoding is stored.
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
//
|
//
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@ -26,6 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMakeTopics(t *testing.T) {
|
func TestMakeTopics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type args struct {
|
type args struct {
|
||||||
query [][]interface{}
|
query [][]interface{}
|
||||||
}
|
}
|
||||||
@ -54,9 +56,27 @@ func TestMakeTopics(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"support *big.Int types in topics",
|
"support positive *big.Int types in topics",
|
||||||
args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}},
|
args{[][]interface{}{
|
||||||
[][]common.Hash{{common.Hash{128}}},
|
{big.NewInt(1)},
|
||||||
|
{big.NewInt(1).Lsh(big.NewInt(2), 254)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||||
|
{common.Hash{128}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support negative *big.Int types in topics",
|
||||||
|
args{[][]interface{}{
|
||||||
|
{big.NewInt(-1)},
|
||||||
|
{big.NewInt(math.MinInt64)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.MaxHash},
|
||||||
|
{common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")},
|
||||||
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -117,7 +137,9 @@ func TestMakeTopics(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
got, err := MakeTopics(tt.args.query...)
|
got, err := MakeTopics(tt.args.query...)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
@ -347,10 +369,13 @@ func setupTopicsTests() []topicTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTopics(t *testing.T) {
|
func TestParseTopics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := setupTopicsTests()
|
tests := setupTopicsTests()
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
createObj := tt.args.createObj()
|
createObj := tt.args.createObj()
|
||||||
if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
@ -364,10 +389,13 @@ func TestParseTopics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTopicsIntoMap(t *testing.T) {
|
func TestParseTopicsIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := setupTopicsTests()
|
tests := setupTopicsTests()
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
outMap := make(map[string]interface{})
|
outMap := make(map[string]interface{})
|
||||||
if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
@ -70,7 +70,7 @@ var (
|
|||||||
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
// check that array brackets are equal if they exist
|
// check that array brackets are equal if they exist
|
||||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
return Type{}, fmt.Errorf("invalid arg type in abi")
|
return Type{}, errors.New("invalid arg type in abi")
|
||||||
}
|
}
|
||||||
typ.stringKind = t
|
typ.stringKind = t
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
|||||||
}
|
}
|
||||||
typ.stringKind = embeddedType.stringKind + sliced
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
} else {
|
} else {
|
||||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
return Type{}, errors.New("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
return typ, err
|
return typ, err
|
||||||
}
|
}
|
||||||
@ -348,7 +348,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireLengthPrefix returns whether the type requires any sort of length
|
// requiresLengthPrefix returns whether the type requires any sort of length
|
||||||
// prefixing.
|
// prefixing.
|
||||||
func (t Type) requiresLengthPrefix() bool {
|
func (t Type) requiresLengthPrefix() bool {
|
||||||
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||||
|
@ -31,6 +31,7 @@ type typeWithoutStringer Type
|
|||||||
|
|
||||||
// Tests that all allowed types get recognized by the type parser.
|
// Tests that all allowed types get recognized by the type parser.
|
||||||
func TestTypeRegexp(t *testing.T) {
|
func TestTypeRegexp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
blob string
|
blob string
|
||||||
components []ArgumentMarshaling
|
components []ArgumentMarshaling
|
||||||
@ -117,6 +118,7 @@ func TestTypeRegexp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTypeCheck(t *testing.T) {
|
func TestTypeCheck(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
typ string
|
typ string
|
||||||
components []ArgumentMarshaling
|
components []ArgumentMarshaling
|
||||||
@ -308,6 +310,7 @@ func TestTypeCheck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInternalType(t *testing.T) {
|
func TestInternalType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
|
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
|
||||||
internalType := "struct a.b[]"
|
internalType := "struct a.b[]"
|
||||||
kind := Type{
|
kind := Type{
|
||||||
@ -332,6 +335,7 @@ func TestInternalType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTypeSize(t *testing.T) {
|
func TestGetTypeSize(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
typ string
|
typ string
|
||||||
components []ArgumentMarshaling
|
components []ArgumentMarshaling
|
||||||
@ -368,6 +372,7 @@ func TestGetTypeSize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFixedBytesOver32(t *testing.T) {
|
func TestNewFixedBytesOver32(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := NewType("bytes4096", "", nil)
|
_, err := NewType("bytes4096", "", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("fixed bytes with size over 32 is not spec'd")
|
t.Errorf("fixed bytes with size over 32 is not spec'd")
|
||||||
|
@ -18,6 +18,7 @@ package abi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -125,7 +126,7 @@ func readBool(word []byte) (bool, error) {
|
|||||||
// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
// readFunctionType 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) {
|
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
if t.T != FunctionTy {
|
if t.T != FunctionTy {
|
||||||
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
|
return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array")
|
||||||
}
|
}
|
||||||
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||||
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||||
@ -138,7 +139,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
|||||||
// ReadFixedBytes uses reflection to create a fixed array to be read from.
|
// ReadFixedBytes uses reflection to create a fixed array to be read from.
|
||||||
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||||
if t.T != FixedBytesTy {
|
if t.T != FixedBytesTy {
|
||||||
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
|
return nil, errors.New("abi: invalid type in call to make fixed byte array")
|
||||||
}
|
}
|
||||||
// convert
|
// convert
|
||||||
array := reflect.New(t.GetType()).Elem()
|
array := reflect.New(t.GetType()).Elem()
|
||||||
@ -159,14 +160,15 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
|
|||||||
// this value will become our slice or our array, depending on the type
|
// this value will become our slice or our array, depending on the type
|
||||||
var refSlice reflect.Value
|
var refSlice reflect.Value
|
||||||
|
|
||||||
if t.T == SliceTy {
|
switch t.T {
|
||||||
|
case SliceTy:
|
||||||
// declare our slice
|
// declare our slice
|
||||||
refSlice = reflect.MakeSlice(t.GetType(), size, size)
|
refSlice = reflect.MakeSlice(t.GetType(), size, size)
|
||||||
} else if t.T == ArrayTy {
|
case ArrayTy:
|
||||||
// declare our array
|
// declare our array
|
||||||
refSlice = reflect.New(t.GetType()).Elem()
|
refSlice = reflect.New(t.GetType()).Elem()
|
||||||
} else {
|
default:
|
||||||
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrays have packed elements, resulting in longer unpack steps.
|
// Arrays have packed elements, resulting in longer unpack steps.
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
|
|
||||||
// TestUnpack tests the general pack/unpack tests in packing_test.go
|
// TestUnpack tests the general pack/unpack tests in packing_test.go
|
||||||
func TestUnpack(t *testing.T) {
|
func TestUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range packUnpackTests {
|
for i, test := range packUnpackTests {
|
||||||
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
|
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
|
||||||
//Unpack
|
//Unpack
|
||||||
@ -206,13 +207,13 @@ var unpackTests = []unpackTest{
|
|||||||
def: `[{"type":"bool"}]`,
|
def: `[{"type":"bool"}]`,
|
||||||
enc: "",
|
enc: "",
|
||||||
want: false,
|
want: false,
|
||||||
err: "abi: attempting to unmarshall an empty string while arguments are expected",
|
err: "abi: attempting to unmarshal an empty string while arguments are expected",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`,
|
def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`,
|
||||||
enc: "",
|
enc: "",
|
||||||
want: false,
|
want: false,
|
||||||
err: "abi: attempting to unmarshall an empty string while arguments are expected",
|
err: "abi: attempting to unmarshal an empty string while arguments are expected",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`,
|
def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`,
|
||||||
@ -224,6 +225,7 @@ var unpackTests = []unpackTest{
|
|||||||
// TestLocalUnpackTests runs test specially designed only for unpacking.
|
// TestLocalUnpackTests runs test specially designed only for unpacking.
|
||||||
// All test cases that can be used to test packing and unpacking should move to packing_test.go
|
// All test cases that can be used to test packing and unpacking should move to packing_test.go
|
||||||
func TestLocalUnpackTests(t *testing.T) {
|
func TestLocalUnpackTests(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range unpackTests {
|
for i, test := range unpackTests {
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
//Unpack
|
//Unpack
|
||||||
@ -251,6 +253,7 @@ func TestLocalUnpackTests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) {
|
func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
|
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -321,6 +324,7 @@ func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOut
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodMultiReturn(t *testing.T) {
|
func TestMethodMultiReturn(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type reversed struct {
|
type reversed struct {
|
||||||
String string
|
String string
|
||||||
Int *big.Int
|
Int *big.Int
|
||||||
@ -400,6 +404,7 @@ func TestMethodMultiReturn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithArray(t *testing.T) {
|
func TestMultiReturnWithArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -423,6 +428,7 @@ func TestMultiReturnWithArray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithStringArray(t *testing.T) {
|
func TestMultiReturnWithStringArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
|
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -453,6 +459,7 @@ func TestMultiReturnWithStringArray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithStringSlice(t *testing.T) {
|
func TestMultiReturnWithStringSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
|
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -485,6 +492,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Similar to TestMultiReturnWithArray, but with a special case in mind:
|
// Similar to TestMultiReturnWithArray, but with a special case in mind:
|
||||||
// values of nested static arrays count towards the size as well, and any element following
|
// values of nested static arrays count towards the size as well, and any element following
|
||||||
// after such nested array argument should be read with the correct offset,
|
// after such nested array argument should be read with the correct offset,
|
||||||
@ -525,6 +533,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
func TestUnmarshal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
|
{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
|
||||||
{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
|
{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
|
||||||
@ -774,6 +783,7 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackTuple(t *testing.T) {
|
func TestUnpackTuple(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
|
const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
|
||||||
abi, err := JSON(strings.NewReader(simpleTuple))
|
abi, err := JSON(strings.NewReader(simpleTuple))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -876,6 +886,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOOMMaliciousInput(t *testing.T) {
|
func TestOOMMaliciousInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
oomTests := []unpackTest{
|
oomTests := []unpackTest{
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint8[]"}]`,
|
def: `[{"type": "uint8[]"}]`,
|
||||||
@ -946,6 +957,7 @@ func TestOOMMaliciousInput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
|
func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var encodeABI Arguments
|
var encodeABI Arguments
|
||||||
uint256Ty, err := NewType("uint256", "", nil)
|
uint256Ty, err := NewType("uint256", "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestTextHash(t *testing.T) {
|
func TestTextHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
hash := TextHash([]byte("Hello Joe"))
|
hash := TextHash([]byte("Hello Joe"))
|
||||||
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
||||||
if !bytes.Equal(hash, want) {
|
if !bytes.Equal(hash, want) {
|
||||||
|
13
accounts/external/backend.go
vendored
13
accounts/external/backend.go
vendored
@ -17,6 +17,7 @@
|
|||||||
package external
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
@ -98,11 +99,11 @@ func (api *ExternalSigner) Status() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Open(passphrase string) error {
|
func (api *ExternalSigner) Open(passphrase string) error {
|
||||||
return fmt.Errorf("operation not supported on external signers")
|
return errors.New("operation not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Close() error {
|
func (api *ExternalSigner) Close() error {
|
||||||
return fmt.Errorf("operation not supported on external signers")
|
return errors.New("operation not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Accounts() []accounts.Account {
|
func (api *ExternalSigner) Accounts() []accounts.Account {
|
||||||
@ -145,7 +146,7 @@ func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||||
return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
|
return accounts.Account{}, errors.New("operation not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
@ -242,14 +243,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
return []byte{}, fmt.Errorf("password-operations not supported on external signers")
|
return []byte{}, errors.New("password-operations not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
return nil, fmt.Errorf("password-operations not supported on external signers")
|
return nil, errors.New("password-operations not supported on external signers")
|
||||||
}
|
}
|
||||||
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("password-operations not supported on external signers")
|
return nil, errors.New("password-operations not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
||||||
// representation.
|
// representation.
|
||||||
func TestHDPathParsing(t *testing.T) {
|
func TestHDPathParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
output DerivationPath
|
output DerivationPath
|
||||||
@ -89,6 +90,7 @@ func testDerive(t *testing.T, next func() DerivationPath, expected []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHdPathIteration(t *testing.T) {
|
func TestHdPathIteration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
|
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
|
||||||
[]string{
|
[]string{
|
||||||
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",
|
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
||||||
@ -38,11 +39,10 @@ import (
|
|||||||
// exist yet, the code will attempt to create a watcher at most this often.
|
// exist yet, the code will attempt to create a watcher at most this often.
|
||||||
const minReloadInterval = 2 * time.Second
|
const minReloadInterval = 2 * time.Second
|
||||||
|
|
||||||
type accountsByURL []accounts.Account
|
// byURL defines the sorting order for accounts.
|
||||||
|
func byURL(a, b accounts.Account) int {
|
||||||
func (s accountsByURL) Len() int { return len(s) }
|
return a.URL.Cmp(b.URL)
|
||||||
func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
|
}
|
||||||
func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
|
|
||||||
// AmbiguousAddrError is returned when attempting to unlock
|
// AmbiguousAddrError is returned when attempting to unlock
|
||||||
// an address for which more than one file exists.
|
// an address for which more than one file exists.
|
||||||
@ -67,7 +67,7 @@ type accountCache struct {
|
|||||||
keydir string
|
keydir string
|
||||||
watcher *watcher
|
watcher *watcher
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
all accountsByURL
|
all []accounts.Account
|
||||||
byAddr map[common.Address][]accounts.Account
|
byAddr map[common.Address][]accounts.Account
|
||||||
throttle *time.Timer
|
throttle *time.Timer
|
||||||
notify chan struct{}
|
notify chan struct{}
|
||||||
@ -194,7 +194,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
|
|||||||
default:
|
default:
|
||||||
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
||||||
copy(err.Matches, matches)
|
copy(err.Matches, matches)
|
||||||
sort.Sort(accountsByURL(err.Matches))
|
slices.SortFunc(err.Matches, byURL)
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -67,14 +68,14 @@ func waitWatcherStart(ks *KeyStore) bool {
|
|||||||
|
|
||||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
var list []accounts.Account
|
var list []accounts.Account
|
||||||
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(200 * time.Millisecond) {
|
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||||
list = ks.Accounts()
|
list = ks.Accounts()
|
||||||
if reflect.DeepEqual(list, wantAccounts) {
|
if reflect.DeepEqual(list, wantAccounts) {
|
||||||
// ks should have also received change notifications
|
// ks should have also received change notifications
|
||||||
select {
|
select {
|
||||||
case <-ks.changes:
|
case <-ks.changes:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("wasn't notified of new accounts")
|
return errors.New("wasn't notified of new accounts")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -151,6 +152,7 @@ func TestWatchNoDir(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheInitialReload(t *testing.T) {
|
func TestCacheInitialReload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cache, _ := newAccountCache(cachetestDir)
|
cache, _ := newAccountCache(cachetestDir)
|
||||||
accounts := cache.accounts()
|
accounts := cache.accounts()
|
||||||
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
||||||
@ -159,6 +161,7 @@ func TestCacheInitialReload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheAddDeleteOrder(t *testing.T) {
|
func TestCacheAddDeleteOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cache, _ := newAccountCache("testdata/no-such-dir")
|
cache, _ := newAccountCache("testdata/no-such-dir")
|
||||||
cache.watcher.running = true // prevent unexpected reloads
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
|
|
||||||
@ -202,7 +205,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
|||||||
// Check that the account list is sorted by filename.
|
// Check that the account list is sorted by filename.
|
||||||
wantAccounts := make([]accounts.Account, len(accs))
|
wantAccounts := make([]accounts.Account, len(accs))
|
||||||
copy(wantAccounts, accs)
|
copy(wantAccounts, accs)
|
||||||
sort.Sort(accountsByURL(wantAccounts))
|
slices.SortFunc(wantAccounts, byURL)
|
||||||
list := cache.accounts()
|
list := cache.accounts()
|
||||||
if !reflect.DeepEqual(list, wantAccounts) {
|
if !reflect.DeepEqual(list, wantAccounts) {
|
||||||
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
||||||
@ -243,6 +246,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheFind(t *testing.T) {
|
func TestCacheFind(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := filepath.Join("testdata", "dir")
|
dir := filepath.Join("testdata", "dir")
|
||||||
cache, _ := newAccountCache(dir)
|
cache, _ := newAccountCache(dir)
|
||||||
cache.watcher.running = true // prevent unexpected reloads
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
@ -349,7 +353,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
time.Sleep(time.Second)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents
|
// Now replace file contents
|
||||||
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||||
@ -365,7 +369,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
time.Sleep(time.Second)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents again
|
// Now replace file contents again
|
||||||
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||||
@ -381,7 +385,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after os.WriteFile
|
// needed so that modTime of `file` is different to its current value after os.WriteFile
|
||||||
time.Sleep(time.Second)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents with crap
|
// Now replace file contents with crap
|
||||||
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
|
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
|
||||||
|
34
accounts/keystore/keystore_fuzzing_test.go
Normal file
34
accounts/keystore/keystore_fuzzing_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2023 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 keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzPassword(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, password string) {
|
||||||
|
ks := NewKeyStore(t.TempDir(), LightScryptN, LightScryptP)
|
||||||
|
a, err := ks.NewAccount(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ks.Unlock(a, password); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -20,7 +20,6 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -31,11 +30,13 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testSigData = make([]byte, 32)
|
var testSigData = make([]byte, 32)
|
||||||
|
|
||||||
func TestKeyStore(t *testing.T) {
|
func TestKeyStore(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir, ks := tmpKeyStore(t, true)
|
dir, ks := tmpKeyStore(t, true)
|
||||||
|
|
||||||
a, err := ks.NewAccount("foo")
|
a, err := ks.NewAccount("foo")
|
||||||
@ -70,6 +71,7 @@ func TestKeyStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSign(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t, true)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
@ -86,6 +88,7 @@ func TestSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSignWithPassphrase(t *testing.T) {
|
func TestSignWithPassphrase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t, true)
|
||||||
|
|
||||||
pass := "passwd"
|
pass := "passwd"
|
||||||
@ -280,6 +283,7 @@ type walletEvent struct {
|
|||||||
// Tests that wallet notifications and correctly fired when accounts are added
|
// Tests that wallet notifications and correctly fired when accounts are added
|
||||||
// or deleted from the keystore.
|
// or deleted from the keystore.
|
||||||
func TestWalletNotifications(t *testing.T) {
|
func TestWalletNotifications(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t, false)
|
||||||
|
|
||||||
// Subscribe to the wallet feed and collect events.
|
// Subscribe to the wallet feed and collect events.
|
||||||
@ -341,6 +345,7 @@ func TestWalletNotifications(t *testing.T) {
|
|||||||
|
|
||||||
// TestImportExport tests the import functionality of a keystore.
|
// TestImportExport tests the import functionality of a keystore.
|
||||||
func TestImportECDSA(t *testing.T) {
|
func TestImportECDSA(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t, true)
|
||||||
key, err := crypto.GenerateKey()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -359,6 +364,7 @@ func TestImportECDSA(t *testing.T) {
|
|||||||
|
|
||||||
// TestImportECDSA tests the import and export functionality of a keystore.
|
// TestImportECDSA tests the import and export functionality of a keystore.
|
||||||
func TestImportExport(t *testing.T) {
|
func TestImportExport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t, true)
|
||||||
acc, err := ks.NewAccount("old")
|
acc, err := ks.NewAccount("old")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -387,6 +393,7 @@ func TestImportExport(t *testing.T) {
|
|||||||
// TestImportRace tests the keystore on races.
|
// TestImportRace tests the keystore on races.
|
||||||
// This test should fail under -race if importing races.
|
// This test should fail under -race if importing races.
|
||||||
func TestImportRace(t *testing.T) {
|
func TestImportRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t, true)
|
||||||
acc, err := ks.NewAccount("old")
|
acc, err := ks.NewAccount("old")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -397,19 +404,19 @@ func TestImportRace(t *testing.T) {
|
|||||||
t.Fatalf("failed to export account: %v", acc)
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
}
|
}
|
||||||
_, ks2 := tmpKeyStore(t, true)
|
_, ks2 := tmpKeyStore(t, true)
|
||||||
var atom uint32
|
var atom atomic.Uint32
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if _, err := ks2.Import(json, "new", "new"); err != nil {
|
if _, err := ks2.Import(json, "new", "new"); err != nil {
|
||||||
atomic.AddUint32(&atom, 1)
|
atom.Add(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if atom != 1 {
|
if atom.Load() != 1 {
|
||||||
t.Errorf("Import is racy")
|
t.Errorf("Import is racy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,7 +431,7 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle
|
|||||||
for _, account := range live {
|
for _, account := range live {
|
||||||
liveList = append(liveList, account)
|
liveList = append(liveList, account)
|
||||||
}
|
}
|
||||||
sort.Sort(accountsByURL(liveList))
|
slices.SortFunc(liveList, byURL)
|
||||||
for j, wallet := range wallets {
|
for j, wallet := range wallets {
|
||||||
if accs := wallet.Accounts(); len(accs) != 1 {
|
if accs := wallet.Accounts(); len(accs) != 1 {
|
||||||
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
||||||
|
@ -136,7 +136,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
|
|||||||
return filepath.Join(ks.keysDirPath, filename)
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encryptdata encrypts the data given as 'data' with the password 'auth'.
|
// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'.
|
||||||
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
||||||
salt := make([]byte, 32)
|
salt := make([]byte, 32)
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
@ -225,10 +225,13 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key := crypto.ToECDSAUnsafe(keyBytes)
|
key, err := crypto.ToECDSA(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid key: %w", err)
|
||||||
|
}
|
||||||
id, err := uuid.FromBytes(keyId)
|
id, err := uuid.FromBytes(keyId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("invalid UUID: %w", err)
|
||||||
}
|
}
|
||||||
return &Key{
|
return &Key{
|
||||||
Id: id,
|
Id: id,
|
||||||
|
@ -30,6 +30,7 @@ const (
|
|||||||
|
|
||||||
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
||||||
func TestKeyEncryptDecrypt(t *testing.T) {
|
func TestKeyEncryptDecrypt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
|
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -54,7 +55,7 @@ func TestKeyEncryptDecrypt(t *testing.T) {
|
|||||||
// Recrypt with a new password and start over
|
// Recrypt with a new password and start over
|
||||||
password += "new data appended" // nolint: gosec
|
password += "new data appended" // nolint: gosec
|
||||||
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
||||||
t.Errorf("test %d: failed to recrypt key %v", i, err)
|
t.Errorf("test %d: failed to re-encrypt key %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePlain(t *testing.T) {
|
func TestKeyStorePlain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStoreIface(t, false)
|
_, ks := tmpKeyStoreIface(t, false)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
@ -60,6 +61,7 @@ func TestKeyStorePlain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePassphrase(t *testing.T) {
|
func TestKeyStorePassphrase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStoreIface(t, true)
|
_, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
@ -80,6 +82,7 @@ func TestKeyStorePassphrase(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStoreIface(t, true)
|
_, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
@ -93,6 +96,7 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImportPreSaleKey(t *testing.T) {
|
func TestImportPreSaleKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir, ks := tmpKeyStoreIface(t, true)
|
dir, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
// file content of a presale key file generated with:
|
// file content of a presale key file generated with:
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
@ -77,7 +78,9 @@ func (w *watcher) loop() {
|
|||||||
}
|
}
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
if err := watcher.Add(w.ac.keydir); err != nil {
|
if err := watcher.Add(w.ac.keydir); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
logger.Warn("Failed to watch keystore folder", "err", err)
|
logger.Warn("Failed to watch keystore folder", "err", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +125,7 @@ func (w *watcher) loop() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Filsystem watcher error", "err", err)
|
log.Info("Filesystem watcher error", "err", err)
|
||||||
case <-debounce.C:
|
case <-debounce.C:
|
||||||
w.ac.scanAccounts()
|
w.ac.scanAccounts()
|
||||||
rescanTriggered = false
|
rescanTriggered = false
|
||||||
|
@ -98,6 +98,9 @@ func NewManager(config *Config, backends ...Backend) *Manager {
|
|||||||
|
|
||||||
// Close terminates the account manager's internal notification processes.
|
// Close terminates the account manager's internal notification processes.
|
||||||
func (am *Manager) Close() error {
|
func (am *Manager) Close() error {
|
||||||
|
for _, w := range am.wallets {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
errc := make(chan error)
|
errc := make(chan error)
|
||||||
am.quit <- errc
|
am.quit <- errc
|
||||||
return <-errc
|
return <-errc
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## Preparing the smartcard
|
## Preparing the smartcard
|
||||||
|
|
||||||
**WARNING: FOILLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
|
**WARNING: FOLLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
|
||||||
|
|
||||||
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)
|
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ func (hub *Hub) refreshWallets() {
|
|||||||
card.Disconnect(pcsc.LeaveCard)
|
card.Disconnect(pcsc.LeaveCard)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Card connected, start tracking in amongs the wallets
|
// Card connected, start tracking among the wallets
|
||||||
hub.wallets[reader] = wallet
|
hub.wallets[reader] = wallet
|
||||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
@ -125,7 +126,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
|
|||||||
// Unpair disestablishes an existing pairing.
|
// Unpair disestablishes an existing pairing.
|
||||||
func (s *SecureChannelSession) Unpair() error {
|
func (s *SecureChannelSession) Unpair() error {
|
||||||
if s.PairingKey == nil {
|
if s.PairingKey == nil {
|
||||||
return fmt.Errorf("cannot unpair: not paired")
|
return errors.New("cannot unpair: not paired")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
|
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
|
||||||
@ -141,7 +142,7 @@ func (s *SecureChannelSession) Unpair() error {
|
|||||||
// Open initializes the secure channel.
|
// Open initializes the secure channel.
|
||||||
func (s *SecureChannelSession) Open() error {
|
func (s *SecureChannelSession) Open() error {
|
||||||
if s.iv != nil {
|
if s.iv != nil {
|
||||||
return fmt.Errorf("session already opened")
|
return errors.New("session already opened")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.open()
|
response, err := s.open()
|
||||||
@ -215,7 +216,7 @@ func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error
|
|||||||
// transmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
// transmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
||||||
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
|
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
|
||||||
if s.iv == nil {
|
if s.iv == nil {
|
||||||
return nil, fmt.Errorf("channel not open")
|
return nil, errors.New("channel not open")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := s.encryptAPDU(data)
|
data, err := s.encryptAPDU(data)
|
||||||
@ -254,7 +255,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !bytes.Equal(s.iv, rmac) {
|
if !bytes.Equal(s.iv, rmac) {
|
||||||
return nil, fmt.Errorf("invalid MAC in response")
|
return nil, errors.New("invalid MAC in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
rapdu := &responseAPDU{}
|
rapdu := &responseAPDU{}
|
||||||
@ -319,7 +320,7 @@ func unpad(data []byte, terminator byte) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
|
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("expected end of padding, got 0")
|
return nil, errors.New("expected end of padding, got 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateIV is an internal method that updates the initialization vector after
|
// updateIV is an internal method that updates the initialization vector after
|
||||||
|
@ -252,7 +252,7 @@ func (w *Wallet) release() error {
|
|||||||
// with the wallet.
|
// with the wallet.
|
||||||
func (w *Wallet) pair(puk []byte) error {
|
func (w *Wallet) pair(puk []byte) error {
|
||||||
if w.session.paired() {
|
if w.session.paired() {
|
||||||
return fmt.Errorf("wallet already paired")
|
return errors.New("wallet already paired")
|
||||||
}
|
}
|
||||||
pairing, err := w.session.pair(puk)
|
pairing, err := w.session.pair(puk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -776,16 +776,16 @@ func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationP
|
|||||||
return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme)
|
return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(account.URL.Path, "/", 2)
|
url, path, found := strings.Cut(account.URL.Path, "/")
|
||||||
if len(parts) != 2 {
|
if !found {
|
||||||
return nil, fmt.Errorf("invalid URL format: %s", account.URL)
|
return nil, fmt.Errorf("invalid URL format: %s", account.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if parts[0] != fmt.Sprintf("%x", w.PublicKey[1:3]) {
|
if url != fmt.Sprintf("%x", w.PublicKey[1:3]) {
|
||||||
return nil, fmt.Errorf("URL %s is not for this wallet", account.URL)
|
return nil, fmt.Errorf("URL %s is not for this wallet", account.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts.ParseDerivationPath(parts[1])
|
return accounts.ParseDerivationPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session represents a secured communication session with the wallet.
|
// Session represents a secured communication session with the wallet.
|
||||||
@ -813,7 +813,7 @@ func (s *Session) pair(secret []byte) (smartcardPairing, error) {
|
|||||||
// unpair deletes an existing pairing.
|
// unpair deletes an existing pairing.
|
||||||
func (s *Session) unpair() error {
|
func (s *Session) unpair() error {
|
||||||
if !s.verified {
|
if !s.verified {
|
||||||
return fmt.Errorf("unpair requires that the PIN be verified")
|
return errors.New("unpair requires that the PIN be verified")
|
||||||
}
|
}
|
||||||
return s.Channel.Unpair()
|
return s.Channel.Unpair()
|
||||||
}
|
}
|
||||||
@ -907,7 +907,7 @@ func (s *Session) initialize(seed []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status == "Online" {
|
if status == "Online" {
|
||||||
return fmt.Errorf("card is already initialized, cowardly refusing to proceed")
|
return errors.New("card is already initialized, cowardly refusing to proceed")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Wallet.lock.Lock()
|
s.Wallet.lock.Lock()
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestURLParsing(t *testing.T) {
|
func TestURLParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url, err := parseURL("https://ethereum.org")
|
url, err := parseURL("https://ethereum.org")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
@ -40,6 +41,7 @@ func TestURLParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLString(t *testing.T) {
|
func TestURLString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url := URL{Scheme: "https", Path: "ethereum.org"}
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
if url.String() != "https://ethereum.org" {
|
if url.String() != "https://ethereum.org" {
|
||||||
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
||||||
@ -52,10 +54,11 @@ func TestURLString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLMarshalJSON(t *testing.T) {
|
func TestURLMarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url := URL{Scheme: "https", Path: "ethereum.org"}
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
json, err := url.MarshalJSON()
|
json, err := url.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpcted error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if string(json) != "\"https://ethereum.org\"" {
|
if string(json) != "\"https://ethereum.org\"" {
|
||||||
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
||||||
@ -63,10 +66,11 @@ func TestURLMarshalJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLUnmarshalJSON(t *testing.T) {
|
func TestURLUnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url := &URL{}
|
url := &URL{}
|
||||||
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpcted error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if url.Scheme != "https" {
|
if url.Scheme != "https" {
|
||||||
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
@ -77,6 +81,7 @@ func TestURLUnmarshalJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLComparison(t *testing.T) {
|
func TestURLComparison(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
urlA URL
|
urlA URL
|
||||||
urlB URL
|
urlB URL
|
||||||
|
@ -65,7 +65,7 @@ type Hub struct {
|
|||||||
// TODO(karalabe): remove if hotplug lands on Windows
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
commsPend int // Number of operations blocking enumeration
|
commsPend int // Number of operations blocking enumeration
|
||||||
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
||||||
enumFails uint32 // Number of times enumeration has failed
|
enumFails atomic.Uint32 // Number of times enumeration has failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||||
@ -151,7 +151,7 @@ func (hub *Hub) refreshWallets() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If USB enumeration is continually failing, don't keep trying indefinitely
|
// If USB enumeration is continually failing, don't keep trying indefinitely
|
||||||
if atomic.LoadUint32(&hub.enumFails) > 2 {
|
if hub.enumFails.Load() > 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Retrieve the current list of USB wallet devices
|
// Retrieve the current list of USB wallet devices
|
||||||
@ -172,7 +172,7 @@ func (hub *Hub) refreshWallets() {
|
|||||||
}
|
}
|
||||||
infos, err := usb.Enumerate(hub.vendorID, 0)
|
infos, err := usb.Enumerate(hub.vendorID, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failcount := atomic.AddUint32(&hub.enumFails, 1)
|
failcount := hub.enumFails.Add(1)
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// See rationale before the enumeration why this is needed and only on Linux.
|
// See rationale before the enumeration why this is needed and only on Linux.
|
||||||
hub.commsLock.Unlock()
|
hub.commsLock.Unlock()
|
||||||
@ -181,7 +181,7 @@ func (hub *Hub) refreshWallets() {
|
|||||||
"vendor", hub.vendorID, "failcount", failcount, "err", err)
|
"vendor", hub.vendorID, "failcount", failcount, "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
atomic.StoreUint32(&hub.enumFails, 0)
|
hub.enumFails.Store(0)
|
||||||
|
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
for _, id := range hub.productIDs {
|
for _, id := range hub.productIDs {
|
||||||
|
@ -279,7 +279,7 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
|
|||||||
}
|
}
|
||||||
hexstr := reply[1 : 1+int(reply[0])]
|
hexstr := reply[1 : 1+int(reply[0])]
|
||||||
|
|
||||||
// Decode the hex sting into an Ethereum address and return
|
// Decode the hex string into an Ethereum address and return
|
||||||
var address common.Address
|
var address common.Address
|
||||||
if _, err = hex.Decode(address[:], hexstr); err != nil {
|
if _, err = hex.Decode(address[:], hexstr); err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
// This file contains the implementation for interacting with the Trezor hardware
|
// This file contains the implementation for interacting with the Trezor hardware
|
||||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||||
// https://wiki.trezor.io/Developers_guide-Message_Workflows
|
// https://docs.trezor.io/trezor-firmware/common/message-workflows.html
|
||||||
|
|
||||||
// !!! STAHP !!!
|
// !!! STAHP !!!
|
||||||
//
|
//
|
||||||
|
@ -483,6 +483,10 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
|
|||||||
w.stateLock.Lock()
|
w.stateLock.Lock()
|
||||||
defer w.stateLock.Unlock()
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
if w.device == nil {
|
||||||
|
return accounts.Account{}, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := w.paths[address]; !ok {
|
if _, ok := w.paths[address]; !ok {
|
||||||
w.accounts = append(w.accounts, account)
|
w.accounts = append(w.accounts, account)
|
||||||
w.paths[address] = make(accounts.DerivationPath, len(path))
|
w.paths[address] = make(accounts.DerivationPath, len(path))
|
||||||
@ -624,7 +628,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
|
|||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||||
// data is not supported for Ledger wallets, so this method will always return
|
// data is not supported for Ledger wallets, so this method will always return
|
||||||
// an error.
|
// an error.
|
||||||
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
@ -54,4 +54,4 @@ for:
|
|||||||
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||||
test_script:
|
test_script:
|
||||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC%
|
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short
|
||||||
|
@ -80,6 +80,7 @@ var (
|
|||||||
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
|
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
|
||||||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||||
|
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||||
|
|
||||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||||
|
@ -20,12 +20,14 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
|||||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||||
}
|
}
|
||||||
var enc PayloadAttributes
|
var enc PayloadAttributes
|
||||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||||
enc.Random = p.Random
|
enc.Random = p.Random
|
||||||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||||
enc.Withdrawals = p.Withdrawals
|
enc.Withdrawals = p.Withdrawals
|
||||||
|
enc.BeaconRoot = p.BeaconRoot
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||||
}
|
}
|
||||||
var dec PayloadAttributes
|
var dec PayloadAttributes
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
@ -56,5 +59,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||||||
if dec.Withdrawals != nil {
|
if dec.Withdrawals != nil {
|
||||||
p.Withdrawals = dec.Withdrawals
|
p.Withdrawals = dec.Withdrawals
|
||||||
}
|
}
|
||||||
|
if dec.BeaconRoot != nil {
|
||||||
|
p.BeaconRoot = dec.BeaconRoot
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
}
|
}
|
||||||
var enc ExecutableData
|
var enc ExecutableData
|
||||||
enc.ParentHash = e.ParentHash
|
enc.ParentHash = e.ParentHash
|
||||||
@ -54,6 +56,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
enc.Withdrawals = e.Withdrawals
|
enc.Withdrawals = e.Withdrawals
|
||||||
|
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||||
|
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +79,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
}
|
}
|
||||||
var dec ExecutableData
|
var dec ExecutableData
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
@ -142,5 +148,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||||||
if dec.Withdrawals != nil {
|
if dec.Withdrawals != nil {
|
||||||
e.Withdrawals = dec.Withdrawals
|
e.Withdrawals = dec.Withdrawals
|
||||||
}
|
}
|
||||||
|
if dec.BlobGasUsed != nil {
|
||||||
|
e.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
|
||||||
|
}
|
||||||
|
if dec.ExcessBlobGas != nil {
|
||||||
|
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,14 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
|
|||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||||
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
|
Override bool `json:"shouldOverrideBuilder"`
|
||||||
}
|
}
|
||||||
var enc ExecutionPayloadEnvelope
|
var enc ExecutionPayloadEnvelope
|
||||||
enc.ExecutionPayload = e.ExecutionPayload
|
enc.ExecutionPayload = e.ExecutionPayload
|
||||||
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
||||||
|
enc.BlobsBundle = e.BlobsBundle
|
||||||
|
enc.Override = e.Override
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +33,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||||
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
|
Override *bool `json:"shouldOverrideBuilder"`
|
||||||
}
|
}
|
||||||
var dec ExecutionPayloadEnvelope
|
var dec ExecutionPayloadEnvelope
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
@ -42,5 +48,11 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||||||
return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
|
return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
|
||||||
}
|
}
|
||||||
e.BlockValue = (*big.Int)(dec.BlockValue)
|
e.BlockValue = (*big.Int)(dec.BlockValue)
|
||||||
|
if dec.BlobsBundle != nil {
|
||||||
|
e.BlobsBundle = dec.BlobsBundle
|
||||||
|
}
|
||||||
|
if dec.Override != nil {
|
||||||
|
e.Override = *dec.Override
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,16 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PayloadVersion denotes the version of PayloadAttributes used to request the
|
||||||
|
// building of the payload to commence.
|
||||||
|
type PayloadVersion byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
PayloadV1 PayloadVersion = 0x1
|
||||||
|
PayloadV2 PayloadVersion = 0x2
|
||||||
|
PayloadV3 PayloadVersion = 0x3
|
||||||
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||||
|
|
||||||
// PayloadAttributes describes the environment context in which a block should
|
// PayloadAttributes describes the environment context in which a block should
|
||||||
@ -35,6 +45,7 @@ type PayloadAttributes struct {
|
|||||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for PayloadAttributes.
|
// JSON type overrides for PayloadAttributes.
|
||||||
@ -61,6 +72,8 @@ type ExecutableData struct {
|
|||||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||||
|
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for executableData.
|
// JSON type overrides for executableData.
|
||||||
@ -73,6 +86,8 @@ type executableDataMarshaling struct {
|
|||||||
ExtraData hexutil.Bytes
|
ExtraData hexutil.Bytes
|
||||||
LogsBloom hexutil.Bytes
|
LogsBloom hexutil.Bytes
|
||||||
Transactions []hexutil.Bytes
|
Transactions []hexutil.Bytes
|
||||||
|
BlobGasUsed *hexutil.Uint64
|
||||||
|
ExcessBlobGas *hexutil.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
|
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
|
||||||
@ -80,6 +95,14 @@ type executableDataMarshaling struct {
|
|||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||||
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
||||||
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
|
Override bool `json:"shouldOverrideBuilder"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlobsBundleV1 struct {
|
||||||
|
Commitments []hexutil.Bytes `json:"commitments"`
|
||||||
|
Proofs []hexutil.Bytes `json:"proofs"`
|
||||||
|
Blobs []hexutil.Bytes `json:"blobs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for ExecutionPayloadEnvelope.
|
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||||
@ -102,6 +125,21 @@ type TransitionConfigurationV1 struct {
|
|||||||
// PayloadID is an identifier of the payload build process
|
// PayloadID is an identifier of the payload build process
|
||||||
type PayloadID [8]byte
|
type PayloadID [8]byte
|
||||||
|
|
||||||
|
// Version returns the payload version associated with the identifier.
|
||||||
|
func (b PayloadID) Version() PayloadVersion {
|
||||||
|
return PayloadVersion(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is returns whether the identifier matches any of provided payload versions.
|
||||||
|
func (b PayloadID) Is(versions ...PayloadVersion) bool {
|
||||||
|
for _, v := range versions {
|
||||||
|
if v == b.Version() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (b PayloadID) String() string {
|
func (b PayloadID) String() string {
|
||||||
return hexutil.Encode(b[:])
|
return hexutil.Encode(b[:])
|
||||||
}
|
}
|
||||||
@ -155,11 +193,12 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
|||||||
// len(extraData) <= 32
|
// len(extraData) <= 32
|
||||||
// uncleHash = emptyUncleHash
|
// uncleHash = emptyUncleHash
|
||||||
// difficulty = 0
|
// difficulty = 0
|
||||||
|
// if versionedHashes != nil, versionedHashes match to blob transactions
|
||||||
//
|
//
|
||||||
// and that the blockhash of the constructed block matches the parameters. Nil
|
// and that the blockhash of the constructed block matches the parameters. Nil
|
||||||
// Withdrawals value will propagate through the returned block. Empty
|
// Withdrawals value will propagate through the returned block. Empty
|
||||||
// Withdrawals value must be passed via non-nil, length 0 value in params.
|
// Withdrawals value must be passed via non-nil, length 0 value in params.
|
||||||
func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
|
||||||
txs, err := decodeTransactions(params.Transactions)
|
txs, err := decodeTransactions(params.Transactions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -174,6 +213,18 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
|||||||
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
||||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
||||||
}
|
}
|
||||||
|
var blobHashes []common.Hash
|
||||||
|
for _, tx := range txs {
|
||||||
|
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||||
|
}
|
||||||
|
if len(blobHashes) != len(versionedHashes) {
|
||||||
|
return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(blobHashes); i++ {
|
||||||
|
if blobHashes[i] != versionedHashes[i] {
|
||||||
|
return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHashes: %v", i, versionedHashes, blobHashes)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Only set withdrawalsRoot if it is non-nil. This allows CLs to use
|
// Only set withdrawalsRoot if it is non-nil. This allows CLs to use
|
||||||
// ExecutableData before withdrawals are enabled by marshaling
|
// ExecutableData before withdrawals are enabled by marshaling
|
||||||
// Withdrawals as the json null value.
|
// Withdrawals as the json null value.
|
||||||
@ -199,6 +250,9 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
|||||||
Extra: params.ExtraData,
|
Extra: params.ExtraData,
|
||||||
MixDigest: params.Random,
|
MixDigest: params.Random,
|
||||||
WithdrawalsHash: withdrawalsRoot,
|
WithdrawalsHash: withdrawalsRoot,
|
||||||
|
ExcessBlobGas: params.ExcessBlobGas,
|
||||||
|
BlobGasUsed: params.BlobGasUsed,
|
||||||
|
ParentBeaconRoot: beaconRoot,
|
||||||
}
|
}
|
||||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
|
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
|
||||||
if block.Hash() != params.BlockHash {
|
if block.Hash() != params.BlockHash {
|
||||||
@ -209,7 +263,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
|||||||
|
|
||||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||||
// fields from the given block. It assumes the given block is post-merge block.
|
// fields from the given block. It assumes the given block is post-merge block.
|
||||||
func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadEnvelope {
|
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope {
|
||||||
data := &ExecutableData{
|
data := &ExecutableData{
|
||||||
BlockHash: block.Hash(),
|
BlockHash: block.Hash(),
|
||||||
ParentHash: block.ParentHash(),
|
ParentHash: block.ParentHash(),
|
||||||
@ -226,8 +280,22 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
|
|||||||
Random: block.MixDigest(),
|
Random: block.MixDigest(),
|
||||||
ExtraData: block.Extra(),
|
ExtraData: block.Extra(),
|
||||||
Withdrawals: block.Withdrawals(),
|
Withdrawals: block.Withdrawals(),
|
||||||
|
BlobGasUsed: block.BlobGasUsed(),
|
||||||
|
ExcessBlobGas: block.ExcessBlobGas(),
|
||||||
}
|
}
|
||||||
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
|
bundle := BlobsBundleV1{
|
||||||
|
Commitments: make([]hexutil.Bytes, 0),
|
||||||
|
Blobs: make([]hexutil.Bytes, 0),
|
||||||
|
Proofs: make([]hexutil.Bytes, 0),
|
||||||
|
}
|
||||||
|
for _, sidecar := range sidecars {
|
||||||
|
for j := range sidecar.Blobs {
|
||||||
|
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
|
||||||
|
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
|
||||||
|
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
|
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
|
||||||
@ -235,3 +303,21 @@ type ExecutionPayloadBodyV1 struct {
|
|||||||
TransactionData []hexutil.Bytes `json:"transactions"`
|
TransactionData []hexutil.Bytes `json:"transactions"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client identifiers to support ClientVersionV1.
|
||||||
|
const (
|
||||||
|
ClientCode = "GE"
|
||||||
|
ClientName = "go-ethereum"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientVersionV1 contains information which identifies a client implementation.
|
||||||
|
type ClientVersionV1 struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"clientName"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ClientVersionV1) String() string {
|
||||||
|
return fmt.Sprintf("%s-%s-%s-%s", v.Code, v.Name, v.Version, v.Commit)
|
||||||
|
}
|
||||||
|
125
beacon/light/canonical.go
Normal file
125
beacon/light/canonical.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2023 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"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// canonicalStore stores instances of the given type in a database and caches
|
||||||
|
// them in memory, associated with a continuous range of period numbers.
|
||||||
|
// Note: canonicalStore is not thread safe and it is the caller's responsibility
|
||||||
|
// to avoid concurrent access.
|
||||||
|
type canonicalStore[T any] struct {
|
||||||
|
keyPrefix []byte
|
||||||
|
periods periodRange
|
||||||
|
cache *lru.Cache[uint64, T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCanonicalStore creates a new canonicalStore and loads all keys associated
|
||||||
|
// with the keyPrefix in order to determine the ranges available in the database.
|
||||||
|
func newCanonicalStore[T any](db ethdb.Iteratee, keyPrefix []byte) (*canonicalStore[T], error) {
|
||||||
|
cs := &canonicalStore[T]{
|
||||||
|
keyPrefix: keyPrefix,
|
||||||
|
cache: lru.NewCache[uint64, T](100),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
iter = db.NewIterator(keyPrefix, nil)
|
||||||
|
kl = len(keyPrefix)
|
||||||
|
first = true
|
||||||
|
)
|
||||||
|
defer iter.Release()
|
||||||
|
|
||||||
|
for iter.Next() {
|
||||||
|
if len(iter.Key()) != kl+8 {
|
||||||
|
log.Warn("Invalid key length in the canonical chain database", "key", fmt.Sprintf("%#x", iter.Key()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8])
|
||||||
|
if first {
|
||||||
|
cs.periods.Start = period
|
||||||
|
} else if cs.periods.End != period {
|
||||||
|
return nil, fmt.Errorf("gap in the canonical chain database between periods %d and %d", cs.periods.End, period-1)
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
cs.periods.End = period + 1
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// databaseKey returns the database key belonging to the given period.
|
||||||
|
func (cs *canonicalStore[T]) databaseKey(period uint64) []byte {
|
||||||
|
return binary.BigEndian.AppendUint64(append([]byte{}, cs.keyPrefix...), period)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds the given item to the database. It also ensures that the range remains
|
||||||
|
// continuous. Can be used either with a batch or database backend.
|
||||||
|
func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error {
|
||||||
|
if !cs.periods.canExpand(period) {
|
||||||
|
return fmt.Errorf("period expansion is not allowed, first: %d, next: %d, period: %d", cs.periods.Start, cs.periods.End, period)
|
||||||
|
}
|
||||||
|
enc, err := rlp.EncodeToBytes(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := backend.Put(cs.databaseKey(period), enc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cs.cache.Add(period, value)
|
||||||
|
cs.periods.expand(period)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteFrom removes items starting from the given period.
|
||||||
|
func (cs *canonicalStore[T]) deleteFrom(db ethdb.KeyValueWriter, fromPeriod uint64) (deleted periodRange) {
|
||||||
|
keepRange, deleteRange := cs.periods.split(fromPeriod)
|
||||||
|
deleteRange.each(func(period uint64) {
|
||||||
|
db.Delete(cs.databaseKey(period))
|
||||||
|
cs.cache.Remove(period)
|
||||||
|
})
|
||||||
|
cs.periods = keepRange
|
||||||
|
return deleteRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// get returns the item at the given period or the null value of the given type
|
||||||
|
// if no item is present.
|
||||||
|
func (cs *canonicalStore[T]) get(backend ethdb.KeyValueReader, period uint64) (T, bool) {
|
||||||
|
var null, value T
|
||||||
|
if !cs.periods.contains(period) {
|
||||||
|
return null, false
|
||||||
|
}
|
||||||
|
if value, ok := cs.cache.Get(period); ok {
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
enc, err := backend.Get(cs.databaseKey(period))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Canonical store value not found", "period", period, "start", cs.periods.Start, "end", cs.periods.End)
|
||||||
|
return null, false
|
||||||
|
}
|
||||||
|
if err := rlp.DecodeBytes(enc, &value); err != nil {
|
||||||
|
log.Error("Error decoding canonical store value", "error", err)
|
||||||
|
return null, false
|
||||||
|
}
|
||||||
|
cs.cache.Add(period, value)
|
||||||
|
return value, true
|
||||||
|
}
|
514
beacon/light/committee_chain.go
Normal file
514
beacon/light/committee_chain.go
Normal file
@ -0,0 +1,514 @@
|
|||||||
|
// Copyright 2023 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"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNeedCommittee = errors.New("sync committee required")
|
||||||
|
ErrInvalidUpdate = errors.New("invalid committee update")
|
||||||
|
ErrInvalidPeriod = errors.New("invalid update period")
|
||||||
|
ErrWrongCommitteeRoot = errors.New("wrong committee root")
|
||||||
|
ErrCannotReorg = errors.New("can not reorg committee chain")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommitteeChain is a passive data structure that can validate, hold and update
|
||||||
|
// a chain of beacon light sync committees and updates. It requires at least one
|
||||||
|
// externally set fixed committee root at the beginning of the chain which can
|
||||||
|
// be set either based on a BootstrapData or a trusted source (a local beacon
|
||||||
|
// full node). This makes the structure useful for both light client and light
|
||||||
|
// server setups.
|
||||||
|
//
|
||||||
|
// It always maintains the following consistency constraints:
|
||||||
|
// - a committee can only be present if its root hash matches an existing fixed
|
||||||
|
// root or if it is proven by an update at the previous period
|
||||||
|
// - an update can only be present if a committee is present at the same period
|
||||||
|
// and the update signature is valid and has enough participants.
|
||||||
|
// The committee at the next period (proven by the update) should also be
|
||||||
|
// present (note that this means they can only be added together if neither
|
||||||
|
// is present yet). If a fixed root is present at the next period then the
|
||||||
|
// update can only be present if it proves the same committee root.
|
||||||
|
//
|
||||||
|
// Once synced to the current sync period, CommitteeChain can also validate
|
||||||
|
// signed beacon headers.
|
||||||
|
type CommitteeChain struct {
|
||||||
|
// chainmu guards against concurrent access to the canonicalStore structures
|
||||||
|
// (updates, committees, fixedCommitteeRoots) and ensures that they stay consistent
|
||||||
|
// with each other and with committeeCache.
|
||||||
|
chainmu sync.RWMutex
|
||||||
|
db ethdb.KeyValueStore
|
||||||
|
updates *canonicalStore[*types.LightClientUpdate]
|
||||||
|
committees *canonicalStore[*types.SerializedSyncCommittee]
|
||||||
|
fixedCommitteeRoots *canonicalStore[common.Hash]
|
||||||
|
committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
||||||
|
|
||||||
|
clock mclock.Clock // monotonic clock (simulated clock in tests)
|
||||||
|
unixNano func() int64 // system clock (simulated clock in tests)
|
||||||
|
sigVerifier committeeSigVerifier // BLS sig verifier (dummy verifier in tests)
|
||||||
|
|
||||||
|
config *types.ChainConfig
|
||||||
|
signerThreshold int
|
||||||
|
minimumUpdateScore types.UpdateScore
|
||||||
|
enforceTime bool // enforceTime specifies whether the age of a signed header should be checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommitteeChain creates a new CommitteeChain.
|
||||||
|
func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool) *CommitteeChain {
|
||||||
|
return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() })
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCommitteeChain creates a new CommitteeChain with the option of replacing the
|
||||||
|
// clock source and signature verification for testing purposes.
|
||||||
|
func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain {
|
||||||
|
s := &CommitteeChain{
|
||||||
|
committeeCache: lru.NewCache[uint64, syncCommittee](10),
|
||||||
|
db: db,
|
||||||
|
sigVerifier: sigVerifier,
|
||||||
|
clock: clock,
|
||||||
|
unixNano: unixNano,
|
||||||
|
config: config,
|
||||||
|
signerThreshold: signerThreshold,
|
||||||
|
enforceTime: enforceTime,
|
||||||
|
minimumUpdateScore: types.UpdateScore{
|
||||||
|
SignerCount: uint32(signerThreshold),
|
||||||
|
SubPeriodIndex: params.SyncPeriodLength / 16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var err1, err2, err3 error
|
||||||
|
if s.fixedCommitteeRoots, err1 = newCanonicalStore[common.Hash](db, rawdb.FixedCommitteeRootKey); err1 != nil {
|
||||||
|
log.Error("Error creating fixed committee root store", "error", err1)
|
||||||
|
}
|
||||||
|
if s.committees, err2 = newCanonicalStore[*types.SerializedSyncCommittee](db, rawdb.SyncCommitteeKey); err2 != nil {
|
||||||
|
log.Error("Error creating committee store", "error", err2)
|
||||||
|
}
|
||||||
|
if s.updates, err3 = newCanonicalStore[*types.LightClientUpdate](db, rawdb.BestUpdateKey); err3 != nil {
|
||||||
|
log.Error("Error creating update store", "error", err3)
|
||||||
|
}
|
||||||
|
if err1 != nil || err2 != nil || err3 != nil || !s.checkConstraints() {
|
||||||
|
log.Info("Resetting invalid committee chain")
|
||||||
|
s.Reset()
|
||||||
|
}
|
||||||
|
// roll back invalid updates (might be necessary if forks have been changed since last time)
|
||||||
|
for !s.updates.periods.isEmpty() {
|
||||||
|
update, ok := s.updates.get(s.db, s.updates.periods.End-1)
|
||||||
|
if !ok {
|
||||||
|
log.Error("Sync committee update missing", "period", s.updates.periods.End-1)
|
||||||
|
s.Reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if valid, err := s.verifyUpdate(update); err != nil {
|
||||||
|
log.Error("Error validating update", "period", s.updates.periods.End-1, "error", err)
|
||||||
|
} else if valid {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := s.rollback(s.updates.periods.End); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !s.committees.periods.isEmpty() {
|
||||||
|
log.Trace("Sync committee chain loaded", "first period", s.committees.periods.Start, "last period", s.committees.periods.End-1)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConstraints checks committee chain validity constraints
|
||||||
|
func (s *CommitteeChain) checkConstraints() bool {
|
||||||
|
isNotInFixedCommitteeRootRange := func(r periodRange) bool {
|
||||||
|
return s.fixedCommitteeRoots.periods.isEmpty() ||
|
||||||
|
r.Start < s.fixedCommitteeRoots.periods.Start ||
|
||||||
|
r.Start >= s.fixedCommitteeRoots.periods.End
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := true
|
||||||
|
if !s.updates.periods.isEmpty() {
|
||||||
|
if isNotInFixedCommitteeRootRange(s.updates.periods) {
|
||||||
|
log.Error("Start update is not in the fixed roots range")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if s.committees.periods.Start > s.updates.periods.Start || s.committees.periods.End <= s.updates.periods.End {
|
||||||
|
log.Error("Missing committees in update range")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !s.committees.periods.isEmpty() {
|
||||||
|
if isNotInFixedCommitteeRootRange(s.committees.periods) {
|
||||||
|
log.Error("Start committee is not in the fixed roots range")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if s.committees.periods.End > s.fixedCommitteeRoots.periods.End && s.committees.periods.End > s.updates.periods.End+1 {
|
||||||
|
log.Error("Last committee is neither in the fixed roots range nor proven by updates")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the committee chain.
|
||||||
|
func (s *CommitteeChain) Reset() {
|
||||||
|
s.chainmu.Lock()
|
||||||
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
|
if err := s.rollback(0); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckpointInit initializes a CommitteeChain based on the checkpoint.
|
||||||
|
// Note: if the chain is already initialized and the committees proven by the
|
||||||
|
// checkpoint do match the existing chain then the chain is retained and the
|
||||||
|
// new checkpoint becomes fixed.
|
||||||
|
func (s *CommitteeChain) CheckpointInit(bootstrap *types.BootstrapData) error {
|
||||||
|
s.chainmu.Lock()
|
||||||
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
|
if err := bootstrap.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
period := bootstrap.Header.SyncPeriod()
|
||||||
|
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil {
|
||||||
|
s.Reset()
|
||||||
|
if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.addCommittee(period, bootstrap.Committee); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFixedCommitteeRoot sets a fixed committee root at the given period.
|
||||||
|
// Note that the period where the first committee is added has to have a fixed
|
||||||
|
// root which can either come from a BootstrapData or a trusted source.
|
||||||
|
func (s *CommitteeChain) addFixedCommitteeRoot(period uint64, root common.Hash) error {
|
||||||
|
if root == (common.Hash{}) {
|
||||||
|
return ErrWrongCommitteeRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
oldRoot := s.getCommitteeRoot(period)
|
||||||
|
if !s.fixedCommitteeRoots.periods.canExpand(period) {
|
||||||
|
// Note: the fixed committee root range should always be continuous and
|
||||||
|
// therefore the expected syncing method is to forward sync and optionally
|
||||||
|
// backward sync periods one by one, starting from a checkpoint. The only
|
||||||
|
// case when a root that is not adjacent to the already fixed ones can be
|
||||||
|
// fixed is when the same root has already been proven by an update chain.
|
||||||
|
// In this case the all roots in between can and should be fixed.
|
||||||
|
// This scenario makes sense when a new trusted checkpoint is added to an
|
||||||
|
// existing chain, ensuring that it will not be rolled back (might be
|
||||||
|
// important in case of low signer participation rate).
|
||||||
|
if root != oldRoot {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
// if the old root exists and matches the new one then it is guaranteed
|
||||||
|
// that the given period is after the existing fixed range and the roots
|
||||||
|
// in between can also be fixed.
|
||||||
|
for p := s.fixedCommitteeRoots.periods.End; p < period; p++ {
|
||||||
|
if err := s.fixedCommitteeRoots.add(batch, p, s.getCommitteeRoot(p)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if oldRoot != (common.Hash{}) && (oldRoot != root) {
|
||||||
|
// existing old root was different, we have to reorg the chain
|
||||||
|
if err := s.rollback(period); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.fixedCommitteeRoots.add(batch, period, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteFixedCommitteeRootsFrom deletes fixed roots starting from the given period.
|
||||||
|
// It also maintains chain consistency, meaning that it also deletes updates and
|
||||||
|
// committees if they are no longer supported by a valid update chain.
|
||||||
|
func (s *CommitteeChain) deleteFixedCommitteeRootsFrom(period uint64) error {
|
||||||
|
if period >= s.fixedCommitteeRoots.periods.End {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
s.fixedCommitteeRoots.deleteFrom(batch, period)
|
||||||
|
if s.updates.periods.isEmpty() || period <= s.updates.periods.Start {
|
||||||
|
// Note: the first period of the update chain should always be fixed so if
|
||||||
|
// the fixed root at the first update is removed then the entire update chain
|
||||||
|
// and the proven committees have to be removed. Earlier committees in the
|
||||||
|
// remaining fixed root range can stay.
|
||||||
|
s.updates.deleteFrom(batch, period)
|
||||||
|
s.deleteCommitteesFrom(batch, period)
|
||||||
|
} else {
|
||||||
|
// The update chain stays intact, some previously fixed committee roots might
|
||||||
|
// get unfixed but are still proven by the update chain. If there were
|
||||||
|
// committees present after the range proven by updates, those should be
|
||||||
|
// removed if the belonging fixed roots are also removed.
|
||||||
|
fromPeriod := s.updates.periods.End + 1 // not proven by updates
|
||||||
|
if period > fromPeriod {
|
||||||
|
fromPeriod = period // also not justified by fixed roots
|
||||||
|
}
|
||||||
|
s.deleteCommitteesFrom(batch, fromPeriod)
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteCommitteesFrom deletes committees starting from the given period.
|
||||||
|
func (s *CommitteeChain) deleteCommitteesFrom(batch ethdb.Batch, period uint64) {
|
||||||
|
deleted := s.committees.deleteFrom(batch, period)
|
||||||
|
for period := deleted.Start; period < deleted.End; period++ {
|
||||||
|
s.committeeCache.Remove(period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCommittee adds a committee at the given period if possible.
|
||||||
|
func (s *CommitteeChain) addCommittee(period uint64, committee *types.SerializedSyncCommittee) error {
|
||||||
|
if !s.committees.periods.canExpand(period) {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
root := s.getCommitteeRoot(period)
|
||||||
|
if root == (common.Hash{}) {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
if root != committee.Root() {
|
||||||
|
return ErrWrongCommitteeRoot
|
||||||
|
}
|
||||||
|
if !s.committees.periods.contains(period) {
|
||||||
|
if err := s.committees.add(s.db, period, committee); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.committeeCache.Remove(period)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertUpdate adds a new update if possible.
|
||||||
|
func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
|
||||||
|
s.chainmu.Lock()
|
||||||
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
|
period := update.AttestedHeader.Header.SyncPeriod()
|
||||||
|
if !s.updates.periods.canExpand(period) || !s.committees.periods.contains(period) {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
if s.minimumUpdateScore.BetterThan(update.Score()) {
|
||||||
|
return ErrInvalidUpdate
|
||||||
|
}
|
||||||
|
oldRoot := s.getCommitteeRoot(period + 1)
|
||||||
|
reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot
|
||||||
|
if oldUpdate, ok := s.updates.get(s.db, period); ok && !update.Score().BetterThan(oldUpdate.Score()) {
|
||||||
|
// a better or equal update already exists; no changes, only fail if new one tried to reorg
|
||||||
|
if reorg {
|
||||||
|
return ErrCannotReorg
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.fixedCommitteeRoots.periods.contains(period+1) && reorg {
|
||||||
|
return ErrCannotReorg
|
||||||
|
}
|
||||||
|
if ok, err := s.verifyUpdate(update); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return ErrInvalidUpdate
|
||||||
|
}
|
||||||
|
addCommittee := !s.committees.periods.contains(period+1) || reorg
|
||||||
|
if addCommittee {
|
||||||
|
if nextCommittee == nil {
|
||||||
|
return ErrNeedCommittee
|
||||||
|
}
|
||||||
|
if nextCommittee.Root() != update.NextSyncCommitteeRoot {
|
||||||
|
return ErrWrongCommitteeRoot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reorg {
|
||||||
|
if err := s.rollback(period + 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
if addCommittee {
|
||||||
|
if err := s.committees.add(batch, period+1, nextCommittee); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.committeeCache.Remove(period + 1)
|
||||||
|
}
|
||||||
|
if err := s.updates.add(batch, period, update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Inserted new committee update", "period", period, "next committee root", update.NextSyncCommitteeRoot)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextSyncPeriod returns the next period where an update can be added and also
|
||||||
|
// whether the chain is initialized at all.
|
||||||
|
func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) {
|
||||||
|
s.chainmu.RLock()
|
||||||
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
|
if s.committees.periods.isEmpty() {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if !s.updates.periods.isEmpty() {
|
||||||
|
return s.updates.periods.End, true
|
||||||
|
}
|
||||||
|
return s.committees.periods.End - 1, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollback removes all committees and fixed roots from the given period and updates
|
||||||
|
// starting from the previous period.
|
||||||
|
func (s *CommitteeChain) rollback(period uint64) error {
|
||||||
|
max := s.updates.periods.End + 1
|
||||||
|
if s.committees.periods.End > max {
|
||||||
|
max = s.committees.periods.End
|
||||||
|
}
|
||||||
|
if s.fixedCommitteeRoots.periods.End > max {
|
||||||
|
max = s.fixedCommitteeRoots.periods.End
|
||||||
|
}
|
||||||
|
for max > period {
|
||||||
|
max--
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
s.deleteCommitteesFrom(batch, max)
|
||||||
|
s.fixedCommitteeRoots.deleteFrom(batch, max)
|
||||||
|
if max > 0 {
|
||||||
|
s.updates.deleteFrom(batch, max-1)
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCommitteeRoot returns the committee root at the given period, either fixed,
|
||||||
|
// proven by a previous update or both. It returns an empty hash if the committee
|
||||||
|
// root is unknown.
|
||||||
|
func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash {
|
||||||
|
if root, ok := s.fixedCommitteeRoots.get(s.db, period); ok || period == 0 {
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
if update, ok := s.updates.get(s.db, period-1); ok {
|
||||||
|
return update.NextSyncCommitteeRoot
|
||||||
|
}
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSyncCommittee returns the deserialized sync committee at the given period.
|
||||||
|
func (s *CommitteeChain) getSyncCommittee(period uint64) (syncCommittee, error) {
|
||||||
|
if c, ok := s.committeeCache.Get(period); ok {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
if sc, ok := s.committees.get(s.db, period); ok {
|
||||||
|
c, err := s.sigVerifier.deserializeSyncCommittee(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Sync committee #%d deserialization error: %v", period, err)
|
||||||
|
}
|
||||||
|
s.committeeCache.Add(period, c)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Missing serialized sync committee #%d", period)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySignedHeader returns true if the given signed header has a valid signature
|
||||||
|
// according to the local committee chain. The caller should ensure that the
|
||||||
|
// committees advertised by the same source where the signed header came from are
|
||||||
|
// synced before verifying the signature.
|
||||||
|
// The age of the header is also returned (the time elapsed since the beginning
|
||||||
|
// of the given slot, according to the local system clock). If enforceTime is
|
||||||
|
// true then negative age (future) headers are rejected.
|
||||||
|
func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) {
|
||||||
|
s.chainmu.RLock()
|
||||||
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
|
return s.verifySignedHeader(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) {
|
||||||
|
var age time.Duration
|
||||||
|
now := s.unixNano()
|
||||||
|
if head.Header.Slot < (uint64(now-math.MinInt64)/uint64(time.Second)-s.config.GenesisTime)/12 {
|
||||||
|
age = time.Duration(now - int64(time.Second)*int64(s.config.GenesisTime+head.Header.Slot*12))
|
||||||
|
} else {
|
||||||
|
age = time.Duration(math.MinInt64)
|
||||||
|
}
|
||||||
|
if s.enforceTime && age < 0 {
|
||||||
|
return false, age, nil
|
||||||
|
}
|
||||||
|
committee, err := s.getSyncCommittee(types.SyncPeriod(head.SignatureSlot))
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
if committee == nil {
|
||||||
|
return false, age, nil
|
||||||
|
}
|
||||||
|
if signingRoot, err := s.config.Forks.SigningRoot(head.Header); err == nil {
|
||||||
|
return s.sigVerifier.verifySignature(committee, signingRoot, &head.Signature), age, nil
|
||||||
|
}
|
||||||
|
return false, age, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyUpdate checks whether the header signature is correct and the update
|
||||||
|
// fits into the specified constraints (assumes that the update has been
|
||||||
|
// successfully validated previously)
|
||||||
|
func (s *CommitteeChain) verifyUpdate(update *types.LightClientUpdate) (bool, error) {
|
||||||
|
// Note: SignatureSlot determines the sync period of the committee used for signature
|
||||||
|
// verification. Though in reality SignatureSlot is always bigger than update.Header.Slot,
|
||||||
|
// setting them as equal here enforces the rule that they have to be in the same sync
|
||||||
|
// period in order for the light client update proof to be meaningful.
|
||||||
|
ok, age, err := s.verifySignedHeader(update.AttestedHeader)
|
||||||
|
if age < 0 {
|
||||||
|
log.Warn("Future committee update received", "age", age)
|
||||||
|
}
|
||||||
|
return ok, err
|
||||||
|
}
|
356
beacon/light/committee_chain_test.go
Normal file
356
beacon/light/committee_chain_test.go
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testGenesis = newTestGenesis()
|
||||||
|
testGenesis2 = newTestGenesis()
|
||||||
|
|
||||||
|
tfBase = newTestForks(testGenesis, types.Forks{
|
||||||
|
&types.Fork{Epoch: 0, Version: []byte{0}},
|
||||||
|
})
|
||||||
|
tfAlternative = newTestForks(testGenesis, types.Forks{
|
||||||
|
&types.Fork{Epoch: 0, Version: []byte{0}},
|
||||||
|
&types.Fork{Epoch: 0x700, Version: []byte{1}},
|
||||||
|
})
|
||||||
|
tfAnotherGenesis = newTestForks(testGenesis2, types.Forks{
|
||||||
|
&types.Fork{Epoch: 0, Version: []byte{0}},
|
||||||
|
})
|
||||||
|
|
||||||
|
tcBase = newTestCommitteeChain(nil, tfBase, true, 0, 10, 400, false)
|
||||||
|
tcBaseWithInvalidUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 200, false) // signer count too low
|
||||||
|
tcBaseWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 440, false)
|
||||||
|
tcReorgWithWorseUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, false)
|
||||||
|
tcReorgWithWorseUpdates2 = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 380, false)
|
||||||
|
tcReorgWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 420, false)
|
||||||
|
tcReorgWithFinalizedUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, true)
|
||||||
|
tcFork = newTestCommitteeChain(tcBase, tfAlternative, true, 7, 10, 400, false)
|
||||||
|
tcAnotherGenesis = newTestCommitteeChain(nil, tfAnotherGenesis, true, 0, 10, 400, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommitteeChainFixedCommitteeRoots(t *testing.T) {
|
||||||
|
for _, reload := range []bool{false, true} {
|
||||||
|
c := newCommitteeChainTest(t, tfBase, 300, true)
|
||||||
|
c.setClockPeriod(7)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 4, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 5, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 6, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 8, ErrInvalidPeriod) // range has to be continuous
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 3, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 2, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.addCommittee(tcBase, 4, nil)
|
||||||
|
c.addCommittee(tcBase, 6, ErrInvalidPeriod) // range has to be continuous
|
||||||
|
c.addCommittee(tcBase, 5, nil)
|
||||||
|
c.addCommittee(tcBase, 6, nil)
|
||||||
|
c.addCommittee(tcAnotherGenesis, 3, ErrWrongCommitteeRoot)
|
||||||
|
c.addCommittee(tcBase, 3, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitteeChainCheckpointSync(t *testing.T) {
|
||||||
|
for _, enforceTime := range []bool{false, true} {
|
||||||
|
for _, reload := range []bool{false, true} {
|
||||||
|
c := newCommitteeChainTest(t, tfBase, 300, enforceTime)
|
||||||
|
if enforceTime {
|
||||||
|
c.setClockPeriod(6)
|
||||||
|
}
|
||||||
|
c.insertUpdate(tcBase, 3, true, ErrInvalidPeriod)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 3, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 4, nil)
|
||||||
|
c.insertUpdate(tcBase, 4, true, ErrInvalidPeriod) // still no committee
|
||||||
|
c.addCommittee(tcBase, 3, nil)
|
||||||
|
c.addCommittee(tcBase, 4, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 4)
|
||||||
|
c.insertUpdate(tcBase, 3, false, nil) // update can be added without committee here
|
||||||
|
c.insertUpdate(tcBase, 4, false, ErrNeedCommittee) // but not here as committee 5 is not there yet
|
||||||
|
c.insertUpdate(tcBase, 4, true, nil)
|
||||||
|
c.verifyRange(tcBase, 3, 5)
|
||||||
|
c.insertUpdate(tcBaseWithInvalidUpdates, 5, true, ErrInvalidUpdate) // signer count too low
|
||||||
|
c.insertUpdate(tcBase, 5, true, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
if enforceTime {
|
||||||
|
c.insertUpdate(tcBase, 6, true, ErrInvalidUpdate) // future update rejected
|
||||||
|
c.setClockPeriod(7)
|
||||||
|
}
|
||||||
|
c.insertUpdate(tcBase, 6, true, nil) // when the time comes it's accepted
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
if enforceTime {
|
||||||
|
c.verifyRange(tcBase, 3, 6) // committee 7 is there but still in the future
|
||||||
|
c.setClockPeriod(8)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 7) // now period 7 can also be verified
|
||||||
|
// try reverse syncing an update
|
||||||
|
c.insertUpdate(tcBase, 2, false, ErrInvalidPeriod) // fixed committee is needed first
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 2, nil)
|
||||||
|
c.addCommittee(tcBase, 2, nil)
|
||||||
|
c.insertUpdate(tcBase, 2, false, nil)
|
||||||
|
c.verifyRange(tcBase, 2, 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitteeChainReorg(t *testing.T) {
|
||||||
|
for _, reload := range []bool{false, true} {
|
||||||
|
for _, addBetterUpdates := range []bool{false, true} {
|
||||||
|
c := newCommitteeChainTest(t, tfBase, 300, true)
|
||||||
|
c.setClockPeriod(11)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 3, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 4, nil)
|
||||||
|
c.addCommittee(tcBase, 3, nil)
|
||||||
|
for period := uint64(3); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcBase, period, true, nil)
|
||||||
|
}
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 10)
|
||||||
|
c.insertUpdate(tcReorgWithWorseUpdates, 5, true, ErrCannotReorg)
|
||||||
|
c.insertUpdate(tcReorgWithWorseUpdates2, 5, true, ErrCannotReorg)
|
||||||
|
if addBetterUpdates {
|
||||||
|
// add better updates for the base chain and expect first reorg to fail
|
||||||
|
// (only add updates as committees should be the same)
|
||||||
|
for period := uint64(5); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcBaseWithBetterUpdates, period, false, nil)
|
||||||
|
}
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 10) // still on the same chain
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, 5, true, ErrCannotReorg)
|
||||||
|
} else {
|
||||||
|
// reorg with better updates
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, 5, false, ErrNeedCommittee)
|
||||||
|
c.verifyRange(tcBase, 3, 10) // no success yet, still on the base chain
|
||||||
|
c.verifyRange(tcReorgWithBetterUpdates, 3, 5)
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, 5, true, nil)
|
||||||
|
// successful reorg, base chain should only match before the reorg period
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 5)
|
||||||
|
c.verifyRange(tcReorgWithBetterUpdates, 3, 6)
|
||||||
|
for period := uint64(6); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcReorgWithBetterUpdates, 3, 10)
|
||||||
|
}
|
||||||
|
// reorg with finalized updates; should succeed even if base chain updates
|
||||||
|
// have been improved because a finalized update beats everything else
|
||||||
|
c.insertUpdate(tcReorgWithFinalizedUpdates, 5, false, ErrNeedCommittee)
|
||||||
|
c.insertUpdate(tcReorgWithFinalizedUpdates, 5, true, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcReorgWithFinalizedUpdates, 3, 6)
|
||||||
|
for period := uint64(6); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcReorgWithFinalizedUpdates, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcReorgWithFinalizedUpdates, 3, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitteeChainFork(t *testing.T) {
|
||||||
|
c := newCommitteeChainTest(t, tfAlternative, 300, true)
|
||||||
|
c.setClockPeriod(11)
|
||||||
|
// trying to sync a chain on an alternative fork with the base chain data
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 0, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 1, nil)
|
||||||
|
c.addCommittee(tcBase, 0, nil)
|
||||||
|
// shared section should sync without errors
|
||||||
|
for period := uint64(0); period < 7; period++ {
|
||||||
|
c.insertUpdate(tcBase, period, true, nil)
|
||||||
|
}
|
||||||
|
c.insertUpdate(tcBase, 7, true, ErrInvalidUpdate) // wrong fork
|
||||||
|
// committee root #7 is still the same but signatures are already signed with
|
||||||
|
// a different fork id so period 7 should only verify on the alternative fork
|
||||||
|
c.verifyRange(tcBase, 0, 6)
|
||||||
|
c.verifyRange(tcFork, 0, 7)
|
||||||
|
for period := uint64(7); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcFork, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcFork, 0, 10)
|
||||||
|
// reload the chain while switching to the base fork
|
||||||
|
c.config = tfBase
|
||||||
|
c.reloadChain()
|
||||||
|
// updates 7..9 should be rolled back now
|
||||||
|
c.verifyRange(tcFork, 0, 6) // again, period 7 only verifies on the right fork
|
||||||
|
c.verifyRange(tcBase, 0, 7)
|
||||||
|
c.insertUpdate(tcFork, 7, true, ErrInvalidUpdate) // wrong fork
|
||||||
|
for period := uint64(7); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcBase, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
type committeeChainTest struct {
|
||||||
|
t *testing.T
|
||||||
|
db *memorydb.Database
|
||||||
|
clock *mclock.Simulated
|
||||||
|
config types.ChainConfig
|
||||||
|
signerThreshold int
|
||||||
|
enforceTime bool
|
||||||
|
chain *CommitteeChain
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommitteeChainTest(t *testing.T, config types.ChainConfig, signerThreshold int, enforceTime bool) *committeeChainTest {
|
||||||
|
c := &committeeChainTest{
|
||||||
|
t: t,
|
||||||
|
db: memorydb.New(),
|
||||||
|
clock: &mclock.Simulated{},
|
||||||
|
config: config,
|
||||||
|
signerThreshold: signerThreshold,
|
||||||
|
enforceTime: enforceTime,
|
||||||
|
}
|
||||||
|
c.chain = newCommitteeChain(c.db, &config, signerThreshold, enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) })
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) reloadChain() {
|
||||||
|
c.chain = newCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) setClockPeriod(period float64) {
|
||||||
|
target := mclock.AbsTime(period * float64(time.Second*12*params.SyncPeriodLength))
|
||||||
|
wait := time.Duration(target - c.clock.Now())
|
||||||
|
if wait < 0 {
|
||||||
|
c.t.Fatalf("Invalid setClockPeriod")
|
||||||
|
}
|
||||||
|
c.clock.Run(wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) addFixedCommitteeRoot(tc *testCommitteeChain, period uint64, expErr error) {
|
||||||
|
if err := c.chain.addFixedCommitteeRoot(period, tc.periods[period].committee.Root()); err != expErr {
|
||||||
|
c.t.Errorf("Incorrect error output from addFixedCommitteeRoot at period %d (expected %v, got %v)", period, expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) addCommittee(tc *testCommitteeChain, period uint64, expErr error) {
|
||||||
|
if err := c.chain.addCommittee(period, tc.periods[period].committee); err != expErr {
|
||||||
|
c.t.Errorf("Incorrect error output from addCommittee at period %d (expected %v, got %v)", period, expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) insertUpdate(tc *testCommitteeChain, period uint64, addCommittee bool, expErr error) {
|
||||||
|
var committee *types.SerializedSyncCommittee
|
||||||
|
if addCommittee {
|
||||||
|
committee = tc.periods[period+1].committee
|
||||||
|
}
|
||||||
|
if err := c.chain.InsertUpdate(tc.periods[period].update, committee); err != expErr {
|
||||||
|
c.t.Errorf("Incorrect error output from InsertUpdate at period %d (expected %v, got %v)", period, expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) verifySignedHeader(tc *testCommitteeChain, period float64, expOk bool) {
|
||||||
|
slot := uint64(period * float64(params.SyncPeriodLength))
|
||||||
|
signedHead := GenerateTestSignedHeader(types.Header{Slot: slot}, &tc.config, tc.periods[types.SyncPeriod(slot)].committee, slot+1, 400)
|
||||||
|
if ok, _, _ := c.chain.VerifySignedHeader(signedHead); ok != expOk {
|
||||||
|
c.t.Errorf("Incorrect output from VerifySignedHeader at period %f (expected %v, got %v)", period, expOk, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) verifyRange(tc *testCommitteeChain, begin, end uint64) {
|
||||||
|
if begin > 0 {
|
||||||
|
c.verifySignedHeader(tc, float64(begin)-0.5, false)
|
||||||
|
}
|
||||||
|
for period := begin; period <= end; period++ {
|
||||||
|
c.verifySignedHeader(tc, float64(period)+0.5, true)
|
||||||
|
}
|
||||||
|
c.verifySignedHeader(tc, float64(end)+1.5, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestGenesis() types.ChainConfig {
|
||||||
|
var config types.ChainConfig
|
||||||
|
rand.Read(config.GenesisValidatorsRoot[:])
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestForks(config types.ChainConfig, forks types.Forks) types.ChainConfig {
|
||||||
|
for _, fork := range forks {
|
||||||
|
config.AddFork(fork.Name, fork.Epoch, fork.Version)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestCommitteeChain(parent *testCommitteeChain, config types.ChainConfig, newCommittees bool, begin, end int, signerCount int, finalizedHeader bool) *testCommitteeChain {
|
||||||
|
tc := &testCommitteeChain{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
if parent != nil {
|
||||||
|
tc.periods = make([]testPeriod, len(parent.periods))
|
||||||
|
copy(tc.periods, parent.periods)
|
||||||
|
}
|
||||||
|
if newCommittees {
|
||||||
|
if begin == 0 {
|
||||||
|
tc.fillCommittees(begin, end+1)
|
||||||
|
} else {
|
||||||
|
tc.fillCommittees(begin+1, end+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tc.fillUpdates(begin, end, signerCount, finalizedHeader)
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
|
type testPeriod struct {
|
||||||
|
committee *types.SerializedSyncCommittee
|
||||||
|
update *types.LightClientUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCommitteeChain struct {
|
||||||
|
periods []testPeriod
|
||||||
|
config types.ChainConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *testCommitteeChain) fillCommittees(begin, end int) {
|
||||||
|
if len(tc.periods) <= end {
|
||||||
|
tc.periods = append(tc.periods, make([]testPeriod, end+1-len(tc.periods))...)
|
||||||
|
}
|
||||||
|
for i := begin; i <= end; i++ {
|
||||||
|
tc.periods[i].committee = GenerateTestCommittee()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *testCommitteeChain) fillUpdates(begin, end int, signerCount int, finalizedHeader bool) {
|
||||||
|
for i := begin; i <= end; i++ {
|
||||||
|
tc.periods[i].update = GenerateTestUpdate(&tc.config, uint64(i), tc.periods[i].committee, tc.periods[i+1].committee, signerCount, finalizedHeader)
|
||||||
|
}
|
||||||
|
}
|
78
beacon/light/range.go
Normal file
78
beacon/light/range.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2023 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
|
||||||
|
|
||||||
|
// periodRange represents a (possibly zero-length) range of integers (sync periods).
|
||||||
|
type periodRange struct {
|
||||||
|
Start, End uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmpty returns true if the length of the range is zero.
|
||||||
|
func (a periodRange) isEmpty() bool {
|
||||||
|
return a.End == a.Start
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains returns true if the range includes the given period.
|
||||||
|
func (a periodRange) contains(period uint64) bool {
|
||||||
|
return period >= a.Start && period < a.End
|
||||||
|
}
|
||||||
|
|
||||||
|
// canExpand returns true if the range includes or can be expanded with the given
|
||||||
|
// period (either the range is empty or the given period is inside, right before or
|
||||||
|
// right after the range).
|
||||||
|
func (a periodRange) canExpand(period uint64) bool {
|
||||||
|
return a.isEmpty() || (period+1 >= a.Start && period <= a.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand expands the range with the given period.
|
||||||
|
// This method assumes that canExpand returned true: otherwise this is a no-op.
|
||||||
|
func (a *periodRange) expand(period uint64) {
|
||||||
|
if a.isEmpty() {
|
||||||
|
a.Start, a.End = period, period+1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if a.Start == period+1 {
|
||||||
|
a.Start--
|
||||||
|
}
|
||||||
|
if a.End == period {
|
||||||
|
a.End++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// split splits the range into two ranges. The 'fromPeriod' will be the first
|
||||||
|
// element in the second range (if present).
|
||||||
|
// The original range is unchanged by this operation
|
||||||
|
func (a *periodRange) split(fromPeriod uint64) (periodRange, periodRange) {
|
||||||
|
if fromPeriod <= a.Start {
|
||||||
|
// First range empty, everything in second range,
|
||||||
|
return periodRange{}, *a
|
||||||
|
}
|
||||||
|
if fromPeriod >= a.End {
|
||||||
|
// Second range empty, everything in first range,
|
||||||
|
return *a, periodRange{}
|
||||||
|
}
|
||||||
|
x := periodRange{a.Start, fromPeriod}
|
||||||
|
y := periodRange{fromPeriod, a.End}
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
// each invokes the supplied function fn once per period in range
|
||||||
|
func (a *periodRange) each(fn func(uint64)) {
|
||||||
|
for p := a.Start; p < a.End; p++ {
|
||||||
|
fn(p)
|
||||||
|
}
|
||||||
|
}
|
152
beacon/light/test_helpers.go
Normal file
152
beacon/light/test_helpers.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
mrand "math/rand"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateTestCommittee() *types.SerializedSyncCommittee {
|
||||||
|
s := new(types.SerializedSyncCommittee)
|
||||||
|
rand.Read(s[:32])
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateTestUpdate(config *types.ChainConfig, period uint64, committee, nextCommittee *types.SerializedSyncCommittee, signerCount int, finalizedHeader bool) *types.LightClientUpdate {
|
||||||
|
update := new(types.LightClientUpdate)
|
||||||
|
update.NextSyncCommitteeRoot = nextCommittee.Root()
|
||||||
|
var attestedHeader types.Header
|
||||||
|
if finalizedHeader {
|
||||||
|
update.FinalizedHeader = new(types.Header)
|
||||||
|
*update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot))
|
||||||
|
attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock, merkle.Value(update.FinalizedHeader.Hash()))
|
||||||
|
} else {
|
||||||
|
attestedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+2000, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot))
|
||||||
|
}
|
||||||
|
update.AttestedHeader = GenerateTestSignedHeader(attestedHeader, config, committee, attestedHeader.Slot+1, signerCount)
|
||||||
|
return update
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateTestSignedHeader(header types.Header, config *types.ChainConfig, committee *types.SerializedSyncCommittee, signatureSlot uint64, signerCount int) types.SignedHeader {
|
||||||
|
bitmask := makeBitmask(signerCount)
|
||||||
|
signingRoot, _ := config.Forks.SigningRoot(header)
|
||||||
|
c, _ := dummyVerifier{}.deserializeSyncCommittee(committee)
|
||||||
|
return types.SignedHeader{
|
||||||
|
Header: header,
|
||||||
|
Signature: types.SyncAggregate{
|
||||||
|
Signers: bitmask,
|
||||||
|
Signature: makeDummySignature(c.(dummySyncCommittee), signingRoot, bitmask),
|
||||||
|
},
|
||||||
|
SignatureSlot: signatureSlot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateTestCheckpoint(period uint64, committee *types.SerializedSyncCommittee) *types.BootstrapData {
|
||||||
|
header, branch := makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexSyncCommittee, merkle.Value(committee.Root()))
|
||||||
|
return &types.BootstrapData{
|
||||||
|
Header: header,
|
||||||
|
Committee: committee,
|
||||||
|
CommitteeRoot: committee.Root(),
|
||||||
|
CommitteeBranch: branch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBitmask(signerCount int) (bitmask [params.SyncCommitteeBitmaskSize]byte) {
|
||||||
|
for i := 0; i < params.SyncCommitteeSize; i++ {
|
||||||
|
if mrand.Intn(params.SyncCommitteeSize-i) < signerCount {
|
||||||
|
bitmask[i/8] += byte(1) << (i & 7)
|
||||||
|
signerCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestHeaderWithMerkleProof(slot, index uint64, value merkle.Value) (types.Header, merkle.Values) {
|
||||||
|
var branch merkle.Values
|
||||||
|
hasher := sha256.New()
|
||||||
|
for index > 1 {
|
||||||
|
var proofHash merkle.Value
|
||||||
|
rand.Read(proofHash[:])
|
||||||
|
hasher.Reset()
|
||||||
|
if index&1 == 0 {
|
||||||
|
hasher.Write(value[:])
|
||||||
|
hasher.Write(proofHash[:])
|
||||||
|
} else {
|
||||||
|
hasher.Write(proofHash[:])
|
||||||
|
hasher.Write(value[:])
|
||||||
|
}
|
||||||
|
hasher.Sum(value[:0])
|
||||||
|
index >>= 1
|
||||||
|
branch = append(branch, proofHash)
|
||||||
|
}
|
||||||
|
return types.Header{Slot: slot, StateRoot: common.Hash(value)}, branch
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncCommittee holds either a blsSyncCommittee or a fake dummySyncCommittee used for testing
|
||||||
|
type syncCommittee interface{}
|
||||||
|
|
||||||
|
// committeeSigVerifier verifies sync committee signatures (either proper BLS
|
||||||
|
// signatures or fake signatures used for testing)
|
||||||
|
type committeeSigVerifier interface {
|
||||||
|
deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error)
|
||||||
|
verifySignature(committee syncCommittee, signedRoot common.Hash, aggregate *types.SyncAggregate) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// blsVerifier implements committeeSigVerifier
|
||||||
|
type blsVerifier struct{}
|
||||||
|
|
||||||
|
// deserializeSyncCommittee implements committeeSigVerifier
|
||||||
|
func (blsVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) {
|
||||||
|
return s.Deserialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifySignature implements committeeSigVerifier
|
||||||
|
func (blsVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool {
|
||||||
|
return committee.(*types.SyncCommittee).VerifySignature(signingRoot, aggregate)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummySyncCommittee [32]byte
|
||||||
|
|
||||||
|
// dummyVerifier implements committeeSigVerifier
|
||||||
|
type dummyVerifier struct{}
|
||||||
|
|
||||||
|
// deserializeSyncCommittee implements committeeSigVerifier
|
||||||
|
func (dummyVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) {
|
||||||
|
var sc dummySyncCommittee
|
||||||
|
copy(sc[:], s[:32])
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifySignature implements committeeSigVerifier
|
||||||
|
func (dummyVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool {
|
||||||
|
return aggregate.Signature == makeDummySignature(committee.(dummySyncCommittee), signingRoot, aggregate.Signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDummySignature(committee dummySyncCommittee, signingRoot common.Hash, bitmask [params.SyncCommitteeBitmaskSize]byte) (sig [params.BLSSignatureSize]byte) {
|
||||||
|
for i, b := range committee[:] {
|
||||||
|
sig[i] = b ^ signingRoot[i]
|
||||||
|
}
|
||||||
|
copy(sig[32:], bitmask[:])
|
||||||
|
return
|
||||||
|
}
|
67
beacon/merkle/merkle.go
Normal file
67
beacon/merkle/merkle.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package merkle implements proof verifications in binary merkle trees.
|
||||||
|
package merkle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value represents either a 32 byte leaf value or hash node in a binary merkle tree/partial proof.
|
||||||
|
type Value [32]byte
|
||||||
|
|
||||||
|
// Values represent a series of merkle tree leaves/nodes.
|
||||||
|
type Values []Value
|
||||||
|
|
||||||
|
var valueT = reflect.TypeOf(Value{})
|
||||||
|
|
||||||
|
// UnmarshalJSON parses a merkle value in hex syntax.
|
||||||
|
func (m *Value) UnmarshalJSON(input []byte) error {
|
||||||
|
return hexutil.UnmarshalFixedJSON(valueT, input, m[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyProof verifies a Merkle proof branch for a single value in a
|
||||||
|
// binary Merkle tree (index is a generalized tree index).
|
||||||
|
func VerifyProof(root common.Hash, index uint64, branch Values, value Value) error {
|
||||||
|
hasher := sha256.New()
|
||||||
|
for _, sibling := range branch {
|
||||||
|
hasher.Reset()
|
||||||
|
if index&1 == 0 {
|
||||||
|
hasher.Write(value[:])
|
||||||
|
hasher.Write(sibling[:])
|
||||||
|
} else {
|
||||||
|
hasher.Write(sibling[:])
|
||||||
|
hasher.Write(value[:])
|
||||||
|
}
|
||||||
|
hasher.Sum(value[:0])
|
||||||
|
if index >>= 1; index == 0 {
|
||||||
|
return errors.New("branch has extra items")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if index != 1 {
|
||||||
|
return errors.New("branch is missing items")
|
||||||
|
}
|
||||||
|
if common.Hash(value) != root {
|
||||||
|
return errors.New("root mismatch")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 The go-ethereum Authors
|
// Copyright 2022 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
@ -14,23 +14,31 @@
|
|||||||
// You should have received a copy of the GNU Lesser General Public License
|
// 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/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package snapshot
|
package params
|
||||||
|
|
||||||
import (
|
const (
|
||||||
"bytes"
|
EpochLength = 32
|
||||||
|
SyncPeriodLength = 8192
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
BLSSignatureSize = 96
|
||||||
|
BLSPubkeySize = 48
|
||||||
|
|
||||||
|
SyncCommitteeSize = 512
|
||||||
|
SyncCommitteeBitmaskSize = SyncCommitteeSize / 8
|
||||||
|
SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// hashes is a helper to implement sort.Interface.
|
const (
|
||||||
type hashes []common.Hash
|
StateIndexGenesisTime = 32
|
||||||
|
StateIndexGenesisValidators = 33
|
||||||
// Len is the number of elements in the collection.
|
StateIndexForkVersion = 141
|
||||||
func (hs hashes) Len() int { return len(hs) }
|
StateIndexLatestHeader = 36
|
||||||
|
StateIndexBlockRoots = 37
|
||||||
// Less reports whether the element with index i should sort before the element
|
StateIndexStateRoots = 38
|
||||||
// with index j.
|
StateIndexHistoricRoots = 39
|
||||||
func (hs hashes) Less(i, j int) bool { return bytes.Compare(hs[i][:], hs[j][:]) < 0 }
|
StateIndexFinalBlock = 105
|
||||||
|
StateIndexSyncCommittee = 54
|
||||||
// Swap swaps the elements with indexes i and j.
|
StateIndexNextSyncCommittee = 55
|
||||||
func (hs hashes) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] }
|
StateIndexExecPayload = 56
|
||||||
|
StateIndexExecHead = 908
|
||||||
|
)
|
191
beacon/types/committee.go
Normal file
191
beacon/types/committee.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// Copyright 2023 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 types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
bls "github.com/protolambda/bls12-381-util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SerializedSyncCommitteeSize is the size of the sync committee plus the
|
||||||
|
// aggregate public key.
|
||||||
|
const SerializedSyncCommitteeSize = (params.SyncCommitteeSize + 1) * params.BLSPubkeySize
|
||||||
|
|
||||||
|
// SerializedSyncCommittee is the serialized version of a sync committee
|
||||||
|
// plus the aggregate public key.
|
||||||
|
type SerializedSyncCommittee [SerializedSyncCommitteeSize]byte
|
||||||
|
|
||||||
|
// jsonSyncCommittee is the JSON representation of a sync committee.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate
|
||||||
|
type jsonSyncCommittee struct {
|
||||||
|
Pubkeys []hexutil.Bytes `json:"pubkeys"`
|
||||||
|
Aggregate hexutil.Bytes `json:"aggregate_pubkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler.
|
||||||
|
func (s *SerializedSyncCommittee) MarshalJSON() ([]byte, error) {
|
||||||
|
sc := jsonSyncCommittee{Pubkeys: make([]hexutil.Bytes, params.SyncCommitteeSize)}
|
||||||
|
for i := range sc.Pubkeys {
|
||||||
|
sc.Pubkeys[i] = make(hexutil.Bytes, params.BLSPubkeySize)
|
||||||
|
copy(sc.Pubkeys[i][:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize])
|
||||||
|
}
|
||||||
|
sc.Aggregate = make(hexutil.Bytes, params.BLSPubkeySize)
|
||||||
|
copy(sc.Aggregate[:], s[params.SyncCommitteeSize*params.BLSPubkeySize:])
|
||||||
|
return json.Marshal(&sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Marshaler.
|
||||||
|
func (s *SerializedSyncCommittee) UnmarshalJSON(input []byte) error {
|
||||||
|
var sc jsonSyncCommittee
|
||||||
|
if err := json.Unmarshal(input, &sc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(sc.Pubkeys) != params.SyncCommitteeSize {
|
||||||
|
return fmt.Errorf("invalid number of pubkeys %d", len(sc.Pubkeys))
|
||||||
|
}
|
||||||
|
for i, key := range sc.Pubkeys {
|
||||||
|
if len(key) != params.BLSPubkeySize {
|
||||||
|
return fmt.Errorf("pubkey %d has invalid size %d", i, len(key))
|
||||||
|
}
|
||||||
|
copy(s[i*params.BLSPubkeySize:], key[:])
|
||||||
|
}
|
||||||
|
if len(sc.Aggregate) != params.BLSPubkeySize {
|
||||||
|
return fmt.Errorf("invalid aggregate pubkey size %d", len(sc.Aggregate))
|
||||||
|
}
|
||||||
|
copy(s[params.SyncCommitteeSize*params.BLSPubkeySize:], sc.Aggregate[:])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root calculates the root hash of the binary tree representation of a sync
|
||||||
|
// committee provided in serialized format.
|
||||||
|
//
|
||||||
|
// TODO(zsfelfoldi): Get rid of this when SSZ encoding lands.
|
||||||
|
func (s *SerializedSyncCommittee) Root() common.Hash {
|
||||||
|
var (
|
||||||
|
hasher = sha256.New()
|
||||||
|
padding [64 - params.BLSPubkeySize]byte
|
||||||
|
data [params.SyncCommitteeSize]common.Hash
|
||||||
|
l = params.SyncCommitteeSize
|
||||||
|
)
|
||||||
|
for i := range data {
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(s[i*params.BLSPubkeySize : (i+1)*params.BLSPubkeySize])
|
||||||
|
hasher.Write(padding[:])
|
||||||
|
hasher.Sum(data[i][:0])
|
||||||
|
}
|
||||||
|
for l > 1 {
|
||||||
|
for i := 0; i < l/2; i++ {
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(data[i*2][:])
|
||||||
|
hasher.Write(data[i*2+1][:])
|
||||||
|
hasher.Sum(data[i][:0])
|
||||||
|
}
|
||||||
|
l /= 2
|
||||||
|
}
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(s[SerializedSyncCommitteeSize-params.BLSPubkeySize : SerializedSyncCommitteeSize])
|
||||||
|
hasher.Write(padding[:])
|
||||||
|
hasher.Sum(data[1][:0])
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(data[0][:])
|
||||||
|
hasher.Write(data[1][:])
|
||||||
|
hasher.Sum(data[0][:0])
|
||||||
|
return data[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize splits open the pubkeys into proper BLS key types.
|
||||||
|
func (s *SerializedSyncCommittee) Deserialize() (*SyncCommittee, error) {
|
||||||
|
sc := new(SyncCommittee)
|
||||||
|
for i := 0; i <= params.SyncCommitteeSize; i++ {
|
||||||
|
key := new(bls.Pubkey)
|
||||||
|
|
||||||
|
var bytes [params.BLSPubkeySize]byte
|
||||||
|
copy(bytes[:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize])
|
||||||
|
|
||||||
|
if err := key.Deserialize(&bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if i < params.SyncCommitteeSize {
|
||||||
|
sc.keys[i] = key
|
||||||
|
} else {
|
||||||
|
sc.aggregate = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncCommittee is a set of sync committee signer pubkeys and the aggregate key.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate
|
||||||
|
type SyncCommittee struct {
|
||||||
|
keys [params.SyncCommitteeSize]*bls.Pubkey
|
||||||
|
aggregate *bls.Pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySignature returns true if the given sync aggregate is a valid signature
|
||||||
|
// or the given hash.
|
||||||
|
func (sc *SyncCommittee) VerifySignature(signingRoot common.Hash, signature *SyncAggregate) bool {
|
||||||
|
var (
|
||||||
|
sig bls.Signature
|
||||||
|
keys = make([]*bls.Pubkey, 0, params.SyncCommitteeSize)
|
||||||
|
)
|
||||||
|
if err := sig.Deserialize(&signature.Signature); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, key := range sc.keys {
|
||||||
|
if signature.Signers[i/8]&(byte(1)<<(i%8)) != 0 {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bls.FastAggregateVerify(keys, signingRoot[:], &sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type SyncAggregate -field-override syncAggregateMarshaling -out gen_syncaggregate_json.go
|
||||||
|
|
||||||
|
// SyncAggregate represents an aggregated BLS signature with Signers referring
|
||||||
|
// to a subset of the corresponding sync committee.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate
|
||||||
|
type SyncAggregate struct {
|
||||||
|
Signers [params.SyncCommitteeBitmaskSize]byte `gencodec:"required" json:"sync_committee_bits"`
|
||||||
|
Signature [params.BLSSignatureSize]byte `gencodec:"required" json:"sync_committee_signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncAggregateMarshaling struct {
|
||||||
|
Signers hexutil.Bytes
|
||||||
|
Signature hexutil.Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignerCount returns the number of signers in the aggregate signature.
|
||||||
|
func (s *SyncAggregate) SignerCount() int {
|
||||||
|
var count int
|
||||||
|
for _, v := range s.Signers {
|
||||||
|
count += bits.OnesCount8(v)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
176
beacon/types/config.go
Normal file
176
beacon/types/config.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syncCommitteeDomain specifies the signatures specific use to avoid clashes
|
||||||
|
// across signing different data structures.
|
||||||
|
const syncCommitteeDomain = 7
|
||||||
|
|
||||||
|
// Fork describes a single beacon chain fork and also stores the calculated
|
||||||
|
// signature domain used after this fork.
|
||||||
|
type Fork struct {
|
||||||
|
// Name of the fork in the chain config (config.yaml) file{
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Epoch when given fork version is activated
|
||||||
|
Epoch uint64
|
||||||
|
|
||||||
|
// Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types
|
||||||
|
Version []byte
|
||||||
|
|
||||||
|
// calculated by computeDomain, based on fork version and genesis validators root
|
||||||
|
domain merkle.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeDomain returns the signature domain based on the given fork version
|
||||||
|
// and genesis validator set root.
|
||||||
|
func (f *Fork) computeDomain(genesisValidatorsRoot common.Hash) {
|
||||||
|
var (
|
||||||
|
hasher = sha256.New()
|
||||||
|
forkVersion32 merkle.Value
|
||||||
|
forkDataRoot merkle.Value
|
||||||
|
)
|
||||||
|
copy(forkVersion32[:], f.Version)
|
||||||
|
hasher.Write(forkVersion32[:])
|
||||||
|
hasher.Write(genesisValidatorsRoot[:])
|
||||||
|
hasher.Sum(forkDataRoot[:0])
|
||||||
|
|
||||||
|
f.domain[0] = syncCommitteeDomain
|
||||||
|
copy(f.domain[4:], forkDataRoot[:28])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forks is the list of all beacon chain forks in the chain configuration.
|
||||||
|
type Forks []*Fork
|
||||||
|
|
||||||
|
// domain returns the signature domain for the given epoch (assumes that domains
|
||||||
|
// have already been calculated).
|
||||||
|
func (f Forks) domain(epoch uint64) (merkle.Value, error) {
|
||||||
|
for i := len(f) - 1; i >= 0; i-- {
|
||||||
|
if epoch >= f[i].Epoch {
|
||||||
|
return f[i].domain, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merkle.Value{}, fmt.Errorf("unknown fork for epoch %d", epoch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningRoot calculates the signing root of the given header.
|
||||||
|
func (f Forks) SigningRoot(header Header) (common.Hash, error) {
|
||||||
|
domain, err := f.domain(header.Epoch())
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
signingRoot common.Hash
|
||||||
|
headerHash = header.Hash()
|
||||||
|
hasher = sha256.New()
|
||||||
|
)
|
||||||
|
hasher.Write(headerHash[:])
|
||||||
|
hasher.Write(domain[:])
|
||||||
|
hasher.Sum(signingRoot[:0])
|
||||||
|
|
||||||
|
return signingRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Forks) Len() int { return len(f) }
|
||||||
|
func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
|
func (f Forks) Less(i, j int) bool { return f[i].Epoch < f[j].Epoch }
|
||||||
|
|
||||||
|
// ChainConfig contains the beacon chain configuration.
|
||||||
|
type ChainConfig struct {
|
||||||
|
GenesisTime uint64 // Unix timestamp of slot 0
|
||||||
|
GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation
|
||||||
|
Forks Forks
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFork adds a new item to the list of forks.
|
||||||
|
func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig {
|
||||||
|
fork := &Fork{
|
||||||
|
Name: name,
|
||||||
|
Epoch: epoch,
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
fork.computeDomain(c.GenesisValidatorsRoot)
|
||||||
|
|
||||||
|
c.Forks = append(c.Forks, fork)
|
||||||
|
sort.Sort(c.Forks)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadForks parses the beacon chain configuration file (config.yaml) and extracts
|
||||||
|
// the list of forks.
|
||||||
|
func (c *ChainConfig) LoadForks(path string) error {
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read beacon chain config file: %v", err)
|
||||||
|
}
|
||||||
|
config := make(map[string]string)
|
||||||
|
if err := yaml.Unmarshal(file, &config); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse beacon chain config file: %v", err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
versions = make(map[string][]byte)
|
||||||
|
epochs = make(map[string]uint64)
|
||||||
|
)
|
||||||
|
epochs["GENESIS"] = 0
|
||||||
|
|
||||||
|
for key, value := range config {
|
||||||
|
if strings.HasSuffix(key, "_FORK_VERSION") {
|
||||||
|
name := key[:len(key)-len("_FORK_VERSION")]
|
||||||
|
if v, err := hexutil.Decode(value); err == nil {
|
||||||
|
versions[name] = v
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", value, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(key, "_FORK_EPOCH") {
|
||||||
|
name := key[:len(key)-len("_FORK_EPOCH")]
|
||||||
|
if v, err := strconv.ParseUint(value, 10, 64); err == nil {
|
||||||
|
epochs[name] = v
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", value, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, epoch := range epochs {
|
||||||
|
if version, ok := versions[name]; ok {
|
||||||
|
delete(versions, name)
|
||||||
|
c.AddFork(name, epoch, version)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("fork id missing for %q in beacon chain config file", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name := range versions {
|
||||||
|
return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name)
|
||||||
|
}
|
||||||
|
sort.Sort(c.Forks)
|
||||||
|
return nil
|
||||||
|
}
|
66
beacon/types/gen_header_json.go
Normal file
66
beacon/types/gen_header_json.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*headerMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
|
type Header struct {
|
||||||
|
Slot common.Decimal `gencodec:"required" json:"slot"`
|
||||||
|
ProposerIndex common.Decimal `gencodec:"required" json:"proposer_index"`
|
||||||
|
ParentRoot common.Hash `gencodec:"required" json:"parent_root"`
|
||||||
|
StateRoot common.Hash `gencodec:"required" json:"state_root"`
|
||||||
|
BodyRoot common.Hash `gencodec:"required" json:"body_root"`
|
||||||
|
}
|
||||||
|
var enc Header
|
||||||
|
enc.Slot = common.Decimal(h.Slot)
|
||||||
|
enc.ProposerIndex = common.Decimal(h.ProposerIndex)
|
||||||
|
enc.ParentRoot = h.ParentRoot
|
||||||
|
enc.StateRoot = h.StateRoot
|
||||||
|
enc.BodyRoot = h.BodyRoot
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||||
|
type Header struct {
|
||||||
|
Slot *common.Decimal `gencodec:"required" json:"slot"`
|
||||||
|
ProposerIndex *common.Decimal `gencodec:"required" json:"proposer_index"`
|
||||||
|
ParentRoot *common.Hash `gencodec:"required" json:"parent_root"`
|
||||||
|
StateRoot *common.Hash `gencodec:"required" json:"state_root"`
|
||||||
|
BodyRoot *common.Hash `gencodec:"required" json:"body_root"`
|
||||||
|
}
|
||||||
|
var dec Header
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.Slot == nil {
|
||||||
|
return errors.New("missing required field 'slot' for Header")
|
||||||
|
}
|
||||||
|
h.Slot = uint64(*dec.Slot)
|
||||||
|
if dec.ProposerIndex == nil {
|
||||||
|
return errors.New("missing required field 'proposer_index' for Header")
|
||||||
|
}
|
||||||
|
h.ProposerIndex = uint64(*dec.ProposerIndex)
|
||||||
|
if dec.ParentRoot == nil {
|
||||||
|
return errors.New("missing required field 'parent_root' for Header")
|
||||||
|
}
|
||||||
|
h.ParentRoot = *dec.ParentRoot
|
||||||
|
if dec.StateRoot == nil {
|
||||||
|
return errors.New("missing required field 'state_root' for Header")
|
||||||
|
}
|
||||||
|
h.StateRoot = *dec.StateRoot
|
||||||
|
if dec.BodyRoot == nil {
|
||||||
|
return errors.New("missing required field 'body_root' for Header")
|
||||||
|
}
|
||||||
|
h.BodyRoot = *dec.BodyRoot
|
||||||
|
return nil
|
||||||
|
}
|
51
beacon/types/gen_syncaggregate_json.go
Normal file
51
beacon/types/gen_syncaggregate_json.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*syncAggregateMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (s SyncAggregate) MarshalJSON() ([]byte, error) {
|
||||||
|
type SyncAggregate struct {
|
||||||
|
Signers hexutil.Bytes `gencodec:"required" json:"sync_committee_bits"`
|
||||||
|
Signature hexutil.Bytes `gencodec:"required" json:"sync_committee_signature"`
|
||||||
|
}
|
||||||
|
var enc SyncAggregate
|
||||||
|
enc.Signers = s.Signers[:]
|
||||||
|
enc.Signature = s.Signature[:]
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (s *SyncAggregate) UnmarshalJSON(input []byte) error {
|
||||||
|
type SyncAggregate struct {
|
||||||
|
Signers *hexutil.Bytes `gencodec:"required" json:"sync_committee_bits"`
|
||||||
|
Signature *hexutil.Bytes `gencodec:"required" json:"sync_committee_signature"`
|
||||||
|
}
|
||||||
|
var dec SyncAggregate
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.Signers == nil {
|
||||||
|
return errors.New("missing required field 'sync_committee_bits' for SyncAggregate")
|
||||||
|
}
|
||||||
|
if len(*dec.Signers) != len(s.Signers) {
|
||||||
|
return errors.New("field 'sync_committee_bits' has wrong length, need 64 items")
|
||||||
|
}
|
||||||
|
copy(s.Signers[:], *dec.Signers)
|
||||||
|
if dec.Signature == nil {
|
||||||
|
return errors.New("missing required field 'sync_committee_signature' for SyncAggregate")
|
||||||
|
}
|
||||||
|
if len(*dec.Signature) != len(s.Signature) {
|
||||||
|
return errors.New("field 'sync_committee_signature' has wrong length, need 96 items")
|
||||||
|
}
|
||||||
|
copy(s.Signature[:], *dec.Signature)
|
||||||
|
return nil
|
||||||
|
}
|
121
beacon/types/header.go
Normal file
121
beacon/types/header.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package types implements a few types of the beacon chain for light client usage.
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerIndexSlot = 8
|
||||||
|
headerIndexProposerIndex = 9
|
||||||
|
headerIndexParentRoot = 10
|
||||||
|
headerIndexStateRoot = 11
|
||||||
|
headerIndexBodyRoot = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
// Header defines a beacon header.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
|
||||||
|
type Header struct {
|
||||||
|
// Monotonically increasing slot number for the beacon block (may be gapped)
|
||||||
|
Slot uint64 `gencodec:"required" json:"slot"`
|
||||||
|
|
||||||
|
// Index into the validator table who created the beacon block
|
||||||
|
ProposerIndex uint64 `gencodec:"required" json:"proposer_index"`
|
||||||
|
|
||||||
|
// SSZ hash of the parent beacon header
|
||||||
|
ParentRoot common.Hash `gencodec:"required" json:"parent_root"`
|
||||||
|
|
||||||
|
// SSZ hash of the beacon state (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beacon-state)
|
||||||
|
StateRoot common.Hash `gencodec:"required" json:"state_root"`
|
||||||
|
|
||||||
|
// SSZ hash of the beacon block body (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beaconblockbody)
|
||||||
|
BodyRoot common.Hash `gencodec:"required" json:"body_root"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerMarshaling is a field type overrides for gencodec.
|
||||||
|
type headerMarshaling struct {
|
||||||
|
Slot common.Decimal
|
||||||
|
ProposerIndex common.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash calculates the block root of the header.
|
||||||
|
//
|
||||||
|
// TODO(zsfelfoldi): Remove this when an SSZ encoder lands.
|
||||||
|
func (h *Header) Hash() common.Hash {
|
||||||
|
var values [16]merkle.Value // values corresponding to indices 8 to 15 of the beacon header tree
|
||||||
|
binary.LittleEndian.PutUint64(values[headerIndexSlot][:8], h.Slot)
|
||||||
|
binary.LittleEndian.PutUint64(values[headerIndexProposerIndex][:8], h.ProposerIndex)
|
||||||
|
values[headerIndexParentRoot] = merkle.Value(h.ParentRoot)
|
||||||
|
values[headerIndexStateRoot] = merkle.Value(h.StateRoot)
|
||||||
|
values[headerIndexBodyRoot] = merkle.Value(h.BodyRoot)
|
||||||
|
hasher := sha256.New()
|
||||||
|
for i := 7; i > 0; i-- {
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(values[i*2][:])
|
||||||
|
hasher.Write(values[i*2+1][:])
|
||||||
|
hasher.Sum(values[i][:0])
|
||||||
|
}
|
||||||
|
return common.Hash(values[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Epoch returns the epoch the header belongs to.
|
||||||
|
func (h *Header) Epoch() uint64 {
|
||||||
|
return h.Slot / params.EpochLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncPeriod returns the sync period the header belongs to.
|
||||||
|
func (h *Header) SyncPeriod() uint64 {
|
||||||
|
return SyncPeriod(h.Slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncPeriodStart returns the first slot of the given period.
|
||||||
|
func SyncPeriodStart(period uint64) uint64 {
|
||||||
|
return period * params.SyncPeriodLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncPeriod returns the sync period that the given slot belongs to.
|
||||||
|
func SyncPeriod(slot uint64) uint64 {
|
||||||
|
return slot / params.SyncPeriodLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedHeader represents a beacon header signed by a sync committee.
|
||||||
|
//
|
||||||
|
// This structure is created from either an optimistic update or an instant update:
|
||||||
|
// - https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||||
|
// - https://github.com/zsfelfoldi/beacon-APIs/blob/instant_update/apis/beacon/light_client/instant_update.yaml
|
||||||
|
type SignedHeader struct {
|
||||||
|
// Beacon header being signed
|
||||||
|
Header Header
|
||||||
|
|
||||||
|
// Sync committee BLS signature aggregate
|
||||||
|
Signature SyncAggregate
|
||||||
|
|
||||||
|
// Slot in which the signature has been created (newer than Header.Slot,
|
||||||
|
// determines the signing sync committee)
|
||||||
|
SignatureSlot uint64
|
||||||
|
}
|
136
beacon/types/light_sync.go
Normal file
136
beacon/types/light_sync.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BootstrapData contains a sync committee where light sync can be started,
|
||||||
|
// together with a proof through a beacon header and corresponding state.
|
||||||
|
// Note: BootstrapData is fetched from a server based on a known checkpoint hash.
|
||||||
|
type BootstrapData struct {
|
||||||
|
Header Header
|
||||||
|
CommitteeRoot common.Hash
|
||||||
|
Committee *SerializedSyncCommittee `rlp:"-"`
|
||||||
|
CommitteeBranch merkle.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate verifies the proof included in BootstrapData.
|
||||||
|
func (c *BootstrapData) Validate() error {
|
||||||
|
if c.CommitteeRoot != c.Committee.Root() {
|
||||||
|
return errors.New("wrong committee root")
|
||||||
|
}
|
||||||
|
return merkle.VerifyProof(c.Header.StateRoot, params.StateIndexSyncCommittee, c.CommitteeBranch, merkle.Value(c.CommitteeRoot))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LightClientUpdate is a proof of the next sync committee root based on a header
|
||||||
|
// signed by the sync committee of the given period. Optionally, the update can
|
||||||
|
// prove quasi-finality by the signed header referring to a previous, finalized
|
||||||
|
// header from the same period, and the finalized header referring to the next
|
||||||
|
// sync committee root.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
|
||||||
|
type LightClientUpdate struct {
|
||||||
|
AttestedHeader SignedHeader // Arbitrary header out of the period signed by the sync committee
|
||||||
|
NextSyncCommitteeRoot common.Hash // Sync committee of the next period advertised in the current one
|
||||||
|
NextSyncCommitteeBranch merkle.Values // Proof for the next period's sync committee
|
||||||
|
|
||||||
|
FinalizedHeader *Header `rlp:"nil"` // Optional header to announce a point of finality
|
||||||
|
FinalityBranch merkle.Values // Proof for the announced finality
|
||||||
|
|
||||||
|
score *UpdateScore // Weight of the update to compare between competing ones
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate verifies the validity of the update.
|
||||||
|
func (update *LightClientUpdate) Validate() error {
|
||||||
|
period := update.AttestedHeader.Header.SyncPeriod()
|
||||||
|
if SyncPeriod(update.AttestedHeader.SignatureSlot) != period {
|
||||||
|
return errors.New("signature slot and signed header are from different periods")
|
||||||
|
}
|
||||||
|
if update.FinalizedHeader != nil {
|
||||||
|
if update.FinalizedHeader.SyncPeriod() != period {
|
||||||
|
return errors.New("finalized header is from different period")
|
||||||
|
}
|
||||||
|
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexFinalBlock, update.FinalityBranch, merkle.Value(update.FinalizedHeader.Hash())); err != nil {
|
||||||
|
return fmt.Errorf("invalid finalized header proof: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexNextSyncCommittee, update.NextSyncCommitteeBranch, merkle.Value(update.NextSyncCommitteeRoot)); err != nil {
|
||||||
|
return fmt.Errorf("invalid next sync committee proof: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score returns the UpdateScore describing the proof strength of the update
|
||||||
|
// Note: thread safety can be ensured by always calling Score on a newly received
|
||||||
|
// or decoded update before making it potentially available for other threads
|
||||||
|
func (update *LightClientUpdate) Score() UpdateScore {
|
||||||
|
if update.score == nil {
|
||||||
|
update.score = &UpdateScore{
|
||||||
|
SignerCount: uint32(update.AttestedHeader.Signature.SignerCount()),
|
||||||
|
SubPeriodIndex: uint32(update.AttestedHeader.Header.Slot & 0x1fff),
|
||||||
|
FinalizedHeader: update.FinalizedHeader != nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *update.score
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScore allows the comparison between updates at the same period in order
|
||||||
|
// to find the best update chain that provides the strongest proof of being canonical.
|
||||||
|
//
|
||||||
|
// UpdateScores have a tightly packed binary encoding format for efficient p2p
|
||||||
|
// protocol transmission. Each UpdateScore is encoded in 3 bytes.
|
||||||
|
// When interpreted as a 24 bit little indian unsigned integer:
|
||||||
|
// - the lowest 10 bits contain the number of signers in the header signature aggregate
|
||||||
|
// - the next 13 bits contain the "sub-period index" which is he signed header's
|
||||||
|
// slot modulo params.SyncPeriodLength (which is correlated with the risk of the chain being
|
||||||
|
// re-orged before the previous period boundary in case of non-finalized updates)
|
||||||
|
// - the highest bit is set when the update is finalized (meaning that the finality
|
||||||
|
// header referenced by the signed header is in the same period as the signed
|
||||||
|
// header, making reorgs before the period boundary impossible
|
||||||
|
type UpdateScore struct {
|
||||||
|
SignerCount uint32 // number of signers in the header signature aggregate
|
||||||
|
SubPeriodIndex uint32 // signed header's slot modulo params.SyncPeriodLength
|
||||||
|
FinalizedHeader bool // update is considered finalized if has finalized header from the same period and 2/3 signatures
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalized returns true if the update has a header signed by at least 2/3 of
|
||||||
|
// the committee, referring to a finalized header that refers to the next sync
|
||||||
|
// committee. This condition is a close approximation of the actual finality
|
||||||
|
// condition that can only be verified by full beacon nodes.
|
||||||
|
func (u *UpdateScore) finalized() bool {
|
||||||
|
return u.FinalizedHeader && u.SignerCount >= params.SyncCommitteeSupermajority
|
||||||
|
}
|
||||||
|
|
||||||
|
// BetterThan returns true if update u is considered better than w.
|
||||||
|
func (u UpdateScore) BetterThan(w UpdateScore) bool {
|
||||||
|
var (
|
||||||
|
uFinalized = u.finalized()
|
||||||
|
wFinalized = w.finalized()
|
||||||
|
)
|
||||||
|
if uFinalized != wFinalized {
|
||||||
|
return uFinalized
|
||||||
|
}
|
||||||
|
return u.SignerCount > w.SignerCount
|
||||||
|
}
|
@ -1,46 +1,65 @@
|
|||||||
# This file contains sha256 checksums of optional build dependencies.
|
# This file contains sha256 checksums of optional build dependencies.
|
||||||
|
|
||||||
e447b498cde50215c4f7619e5124b0fc4e25fb5d16ea47271c47f278e7aa763a go1.20.3.src.tar.gz
|
# version:spec-tests 2.1.0
|
||||||
c1e1161d6d859deb576e6cfabeb40e3d042ceb1c6f444f617c3c9d76269c3565 go1.20.3.darwin-amd64.tar.gz
|
# https://github.com/ethereum/execution-spec-tests/releases
|
||||||
86b0ed0f2b2df50fa8036eea875d1cf2d76cefdacf247c44639a1464b7e36b95 go1.20.3.darwin-arm64.tar.gz
|
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
|
||||||
340e80abd047c597fdc0f50a6cc59617f06c297d62f7fc77f4a0164e2da6f7aa go1.20.3.freebsd-386.tar.gz
|
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
|
||||||
2169fcd8b6c94c5fbe07c0b470ccfb6001d343f6548ad49f3d9ab78e3b5753c7 go1.20.3.freebsd-amd64.tar.gz
|
|
||||||
e12384311403f1389d14cc1c1295bfb4e0dd5ab919403b80da429f671a223507 go1.20.3.linux-386.tar.gz
|
|
||||||
979694c2c25c735755bf26f4f45e19e64e4811d661dd07b8c010f7a8e18adfca go1.20.3.linux-amd64.tar.gz
|
|
||||||
eb186529f13f901e7a2c4438a05c2cd90d74706aaa0a888469b2a4a617b6ee54 go1.20.3.linux-arm64.tar.gz
|
|
||||||
b421e90469a83671641f81b6e20df6500f033e9523e89cbe7b7223704dd1035c go1.20.3.linux-armv6l.tar.gz
|
|
||||||
943c89aa1624ea544a022b31e3d6e16a037200e436370bdd5fd67f3fa60be282 go1.20.3.linux-ppc64le.tar.gz
|
|
||||||
126cf823a5634ef2544b866db107b9d351d3ea70d9e240b0bdcfb46f4dcae54b go1.20.3.linux-s390x.tar.gz
|
|
||||||
37e9146e1f9d681cfcaa6fee6c7b890c44c64bc50228c9588f3c4231346d33bd go1.20.3.windows-386.zip
|
|
||||||
143a2837821c7dbacf7744cbb1a8421c1f48307c6fdfaeffc5f8c2f69e1b7932 go1.20.3.windows-amd64.zip
|
|
||||||
158cb159e00bc979f473e0f5b5a561613129c5e51067967b72b8e072e5a4db81 go1.20.3.windows-arm64.zip
|
|
||||||
|
|
||||||
fba08acc4027f69f07cef48fbff70b8a7ecdfaa1c2aba9ad3fb31d60d9f5d4bc golangci-lint-1.51.1-darwin-amd64.tar.gz
|
# version:golang 1.21.6
|
||||||
75b8f0ff3a4e68147156be4161a49d4576f1be37a0b506473f8c482140c1e7f2 golangci-lint-1.51.1-darwin-arm64.tar.gz
|
# https://go.dev/dl/
|
||||||
e06b3459aaed356e1667580be00b05f41f3b2e29685d12cdee571c23e1edb414 golangci-lint-1.51.1-freebsd-386.tar.gz
|
124926a62e45f78daabbaedb9c011d97633186a33c238ffc1e25320c02046248 go1.21.6.src.tar.gz
|
||||||
623ce2d0fa4d35cc2e8d69fa7334227ab592380962a13b4d9cdc77cf41db2008 golangci-lint-1.51.1-freebsd-amd64.tar.gz
|
31d6ecca09010ab351e51343a5af81d678902061fee871f912bdd5ef4d778850 go1.21.6.darwin-amd64.tar.gz
|
||||||
131365feb0584cc2736c43192fa673ca50e5b6b765456990cb379ecfb787e568 golangci-lint-1.51.1-freebsd-armv6.tar.gz
|
0ff541fb37c38e5e5c5bcecc8f4f43c5ffd5e3a6c33a5d3e4003ded66fcfb331 go1.21.6.darwin-arm64.tar.gz
|
||||||
98fb627927cbb654f5bf85dcffc5f646666b2ce96ea0fed977c9fb28abd51532 golangci-lint-1.51.1-freebsd-armv7.tar.gz
|
a1d1a149b34bf0f53965a237682c6da1140acabb131bf0e597240e4a140b0e5e go1.21.6.freebsd-386.tar.gz
|
||||||
b36a99702fa762c15840261bc0fb41b4b1b16b8b19b8c0941bae98c85bb0f8b8 golangci-lint-1.51.1-linux-386.tar.gz
|
de59e1217e4398b1522eed8dddabab2fa1b97aecbdca3af08e34832b4f0e3f81 go1.21.6.freebsd-amd64.tar.gz
|
||||||
17aeb26c76820c22efa0e1838b0ab93e90cfedef43fbfc9a2f33f27eb9e5e070 golangci-lint-1.51.1-linux-amd64.tar.gz
|
05d09041b5a1193c14e4b2db3f7fcc649b236c567f5eb93305c537851b72dd95 go1.21.6.linux-386.tar.gz
|
||||||
9744bc34e7b8d82ca788b667bfb7155a39b4be9aef43bf9f10318b1372cea338 golangci-lint-1.51.1-linux-arm64.tar.gz
|
3f934f40ac360b9c01f616a9aa1796d227d8b0328bf64cb045c7b8c4ee9caea4 go1.21.6.linux-amd64.tar.gz
|
||||||
0dda8dbeb2ff7455a044ec8e347f2fc6d655d2e99d281b3b95e88167031c673d golangci-lint-1.51.1-linux-armv6.tar.gz
|
e2e8aa88e1b5170a0d495d7d9c766af2b2b6c6925a8f8956d834ad6b4cacbd9a go1.21.6.linux-arm64.tar.gz
|
||||||
0512f311b11d43b8b22989d929f0fe8a2e1e5ebe497f1eb0ff73a0fc3d188fd1 golangci-lint-1.51.1-linux-armv7.tar.gz
|
6a8eda6cc6a799ff25e74ce0c13fdc1a76c0983a0bb07c789a2a3454bf6ec9b2 go1.21.6.linux-armv6l.tar.gz
|
||||||
d767108dcf84a8eaa844df3454cb0f75a492f4e7102ecc2b0a3545cfe073a566 golangci-lint-1.51.1-linux-loong64.tar.gz
|
e872b1e9a3f2f08fd4554615a32ca9123a4ba877ab6d19d36abc3424f86bc07f go1.21.6.linux-ppc64le.tar.gz
|
||||||
3bd56c54daec16585b2668e0dfabb27af2c2b38cc0fdb46923e2521e1634846b golangci-lint-1.51.1-linux-mips64.tar.gz
|
92894d0f732d3379bc414ffdd617eaadad47e1d72610e10d69a1156db03fc052 go1.21.6.linux-s390x.tar.gz
|
||||||
f72f5adfa2219e15d2414c9a2966f86e74556cf17a85c727a7fb7770a16cf814 golangci-lint-1.51.1-linux-mips64le.tar.gz
|
65b38857135cf45c80e1d267e0ce4f80fe149326c68835217da4f2da9b7943fe go1.21.6.windows-386.zip
|
||||||
e605521dac98096d8737e1997c954f41f1d0d8275b8731f62783d410c23574b9 golangci-lint-1.51.1-linux-ppc64le.tar.gz
|
27ac9dd6e66fb3fd0acfa6792ff053c86e7d2c055b022f4b5d53bfddec9e3301 go1.21.6.windows-amd64.zip
|
||||||
2f683217b814339e74d61ca700922d8407f15addd6d4c5e8b156fbab79f26a87 golangci-lint-1.51.1-linux-riscv64.tar.gz
|
b93aff8f3c882c764c66a39b7a1483b0460e051e9992bf3435479129e5051bcd go1.21.6.windows-arm64.zip
|
||||||
d98528292b65971a3594e5880530e7624597dc9806fcfccdfbe39be411713d63 golangci-lint-1.51.1-linux-s390x.tar.gz
|
|
||||||
9bb2d0fe9e692ed0aea4f2537e3e6862b2f6768fe2849a84f4a6ad09da9fd971 golangci-lint-1.51.1-netbsd-386.tar.gz
|
# version:golangci 1.55.2
|
||||||
34cafdcd11ae73ae88d66c33eb8449f5c976fc3e37b44774dbe9c71caa95e592 golangci-lint-1.51.1-netbsd-amd64.tar.gz
|
# https://github.com/golangci/golangci-lint/releases/
|
||||||
f8b4e1e47ac17caafe8a5f32f975a2b6a7cb14c27c0f73c1fb15c20ca91c2e03 golangci-lint-1.51.1-netbsd-armv6.tar.gz
|
# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/
|
||||||
c4f58b7e227b9fd41f0e9310dc83f4a4e7d026598e2f6e95b78761081a6d9bd2 golangci-lint-1.51.1-netbsd-armv7.tar.gz
|
632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz
|
||||||
6710e2f5375dc75521c1a17980a6cbbe6ff76c2f8b852964a8af558899a97cf5 golangci-lint-1.51.1-windows-386.zip
|
234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz
|
||||||
722d7b87b9cdda0a3835d5030b3fc5385c2eba4c107f63f6391cfb2ac35f051d golangci-lint-1.51.1-windows-amd64.zip
|
2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz
|
||||||
eb57f9bcb56646f2e3d6ccaf02ec227815fb05077b2e0b1bf9e755805acdc2b9 golangci-lint-1.51.1-windows-arm64.zip
|
e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz
|
||||||
bce02f7232723cb727755ee11f168a700a00896a25d37f87c4b173bce55596b4 golangci-lint-1.51.1-windows-armv6.zip
|
5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz
|
||||||
cf6403f84707ce8c98664736772271bc8874f2e760c2fd0f00cf3e85963507e9 golangci-lint-1.51.1-windows-armv7.zip
|
7f21ab1008d05f32c954f99470fc86a83a059e530fe2add1d0b7d8ed4d8992a7 golangci-lint-1.55.2-freebsd-armv7.tar.gz
|
||||||
|
33ab06139b9219a28251f10821da94423db30285cc2af97494cbb2a281927de9 golangci-lint-1.55.2-illumos-amd64.tar.gz
|
||||||
|
57ce6f8ce3ad6ee45d7cc3d9a047545a851c2547637834a3fcb086c7b40b1e6b golangci-lint-1.55.2-linux-386.tar.gz
|
||||||
|
ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c golangci-lint-1.55.2-linux-amd64.tar.gz
|
||||||
|
8eb0cee9b1dbf0eaa49871798c7f8a5b35f2960c52d776a5f31eb7d886b92746 golangci-lint-1.55.2-linux-arm64.tar.gz
|
||||||
|
3195f3e0f37d353fd5bd415cabcd4e263f5c29d3d0ffb176c26ff3d2c75eb3bb golangci-lint-1.55.2-linux-armv6.tar.gz
|
||||||
|
c823ee36eb1a719e171de1f2f5ca3068033dce8d9817232fd10ed71fd6650406 golangci-lint-1.55.2-linux-armv7.tar.gz
|
||||||
|
758a5d2a356dc494bd13ed4c0d4bf5a54a4dc91267ea5ecdd87b86c7ca0624e7 golangci-lint-1.55.2-linux-loong64.tar.gz
|
||||||
|
2c7b9abdce7cae802a67d583cd7c6dca520bff6d0e17c8535a918e2f2b437aa0 golangci-lint-1.55.2-linux-mips64.tar.gz
|
||||||
|
024e0a15b85352cc27271285526e16a4ab66d3e67afbbe446c9808c06cb8dbed golangci-lint-1.55.2-linux-mips64le.tar.gz
|
||||||
|
6b00f89ba5506c1de1efdd9fa17c54093013a294fefd8b9b31534db626a672ee golangci-lint-1.55.2-linux-ppc64le.tar.gz
|
||||||
|
0faa0d047d9bf7b703ed3ea65b6117043c93504f9ca1de25ae929d3901c73d4a golangci-lint-1.55.2-linux-riscv64.tar.gz
|
||||||
|
30dec9b22e7d5bb4e9d5ccea96da20f71cd7db3c8cf30b8ddc7cb9174c4d742a golangci-lint-1.55.2-linux-s390x.tar.gz
|
||||||
|
5a0ede48f79ad707902fdb29be8cd2abd8302dc122b65ebae3fdfc86751c7698 golangci-lint-1.55.2-netbsd-386.tar.gz
|
||||||
|
95af20a2e617126dd5b08122ece7819101070e1582a961067ce8c41172f901ad golangci-lint-1.55.2-netbsd-amd64.tar.gz
|
||||||
|
94fb7dacb7527847cc95d7120904e19a2a0a81a0d50d61766c9e0251da72ab9d golangci-lint-1.55.2-netbsd-armv6.tar.gz
|
||||||
|
ca906bce5fee9619400e4a321c56476fe4a4efb6ac4fc989d340eb5563348873 golangci-lint-1.55.2-netbsd-armv7.tar.gz
|
||||||
|
45b442f69fc8915c4500201c0247b7f3f69544dbc9165403a61f9095f2c57355 golangci-lint-1.55.2-windows-386.zip
|
||||||
|
f57d434d231d43417dfa631587522f8c1991220b43c8ffadb9c7bd279508bf81 golangci-lint-1.55.2-windows-amd64.zip
|
||||||
|
fd7dc8f4c6829ee6fafb252a4d81d2155cd35da7833665cbb25d53ce7cecd990 golangci-lint-1.55.2-windows-arm64.zip
|
||||||
|
1892c3c24f9e7ef44b02f6750c703864b6dc350129f3ec39510300007b2376f1 golangci-lint-1.55.2-windows-armv6.zip
|
||||||
|
a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-1.55.2-windows-armv7.zip
|
||||||
|
|
||||||
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
|
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
|
||||||
|
#
|
||||||
|
# This version is fine to be old and full of security holes, we just use it
|
||||||
|
# to build the latest Go. Don't change it. If it ever becomes insufficient,
|
||||||
|
# we need to switch over to a recursive builder to jump across supported
|
||||||
|
# versions.
|
||||||
|
#
|
||||||
|
# version:ppa-builder 1.19.6
|
||||||
|
# https://go.dev/dl/
|
||||||
d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz
|
d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz
|
||||||
|
103
build/ci.go
103
build/ci.go
@ -120,15 +120,15 @@ var (
|
|||||||
// Distros for which packages are created.
|
// Distros for which packages are created.
|
||||||
// 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: the following Ubuntu releases have been officially deprecated on Launchpad:
|
// Note: the following Ubuntu releases have been officially deprecated on Launchpad:
|
||||||
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish
|
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish,
|
||||||
|
// kinetic, lunar
|
||||||
debDistroGoBoots = map[string]string{
|
debDistroGoBoots = map[string]string{
|
||||||
"trusty": "golang-1.11", // EOL: 04/2024
|
"trusty": "golang-1.11", // 14.04, EOL: 04/2024
|
||||||
"xenial": "golang-go", // EOL: 04/2026
|
"xenial": "golang-go", // 16.04, EOL: 04/2026
|
||||||
"bionic": "golang-go", // EOL: 04/2028
|
"bionic": "golang-go", // 18.04, EOL: 04/2028
|
||||||
"focal": "golang-go", // EOL: 04/2030
|
"focal": "golang-go", // 20.04, EOL: 04/2030
|
||||||
"jammy": "golang-go", // EOL: 04/2032
|
"jammy": "golang-go", // 22.04, EOL: 04/2032
|
||||||
"kinetic": "golang-go", // EOL: 07/2023
|
"mantic": "golang-go", // 23.10, EOL: 07/2024
|
||||||
"lunar": "golang-go", // EOL: 01/2024
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debGoBootPaths = map[string]string{
|
debGoBootPaths = map[string]string{
|
||||||
@ -136,18 +136,8 @@ var (
|
|||||||
"golang-go": "/usr/lib/go",
|
"golang-go": "/usr/lib/go",
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the version of Go that will be downloaded by
|
// This is where the tests should be unpacked.
|
||||||
//
|
executionSpecTestsDir = "tests/spec-tests"
|
||||||
// go run ci.go install -dlgo
|
|
||||||
dlgoVersion = "1.20.3"
|
|
||||||
|
|
||||||
// This is the version of Go that will be used to bootstrap the PPA builder.
|
|
||||||
//
|
|
||||||
// This version is fine to be old and full of security holes, we just use it
|
|
||||||
// to build the latest Go. Don't change it. If it ever becomes insufficient,
|
|
||||||
// we need to switch over to a recursive builder to jumpt across supported
|
|
||||||
// versions.
|
|
||||||
gobootVersion = "1.19.6"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
@ -185,6 +175,8 @@ func main() {
|
|||||||
doWindowsInstaller(os.Args[2:])
|
doWindowsInstaller(os.Args[2:])
|
||||||
case "purge":
|
case "purge":
|
||||||
doPurge(os.Args[2:])
|
doPurge(os.Args[2:])
|
||||||
|
case "sanitycheck":
|
||||||
|
doSanityCheck()
|
||||||
default:
|
default:
|
||||||
log.Fatal("unknown command ", os.Args[1])
|
log.Fatal("unknown command ", os.Args[1])
|
||||||
}
|
}
|
||||||
@ -200,19 +192,23 @@ func doInstall(cmdline []string) {
|
|||||||
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
|
env := build.Env()
|
||||||
|
|
||||||
// Configure the toolchain.
|
// Configure the toolchain.
|
||||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||||
if *dlgo {
|
if *dlgo {
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
tc.Root = build.DownloadGo(csdb, dlgoVersion)
|
tc.Root = build.DownloadGo(csdb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable CLI markdown doc generation in release builds.
|
// Disable CLI markdown doc generation in release builds.
|
||||||
buildTags := []string{"urfave_cli_no_docs"}
|
buildTags := []string{"urfave_cli_no_docs"}
|
||||||
|
|
||||||
|
// Enable linking the CKZG library since we can make it work with additional flags.
|
||||||
|
if env.UbuntuVersion != "trusty" {
|
||||||
|
buildTags = append(buildTags, "ckzg")
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the build.
|
// Configure the build.
|
||||||
env := build.Env()
|
|
||||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
||||||
|
|
||||||
// arm64 CI builders are memory-constrained and can't handle concurrent builds,
|
// arm64 CI builders are memory-constrained and can't handle concurrent builds,
|
||||||
@ -221,7 +217,6 @@ func doInstall(cmdline []string) {
|
|||||||
if env.CI && runtime.GOARCH == "arm64" {
|
if env.CI && runtime.GOARCH == "arm64" {
|
||||||
gobuild.Args = append(gobuild.Args, "-p", "1")
|
gobuild.Args = append(gobuild.Args, "-p", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use -trimpath to avoid leaking local paths into the built executables.
|
// We use -trimpath to avoid leaking local paths into the built executables.
|
||||||
gobuild.Args = append(gobuild.Args, "-trimpath")
|
gobuild.Args = append(gobuild.Args, "-trimpath")
|
||||||
|
|
||||||
@ -290,17 +285,31 @@ func doTest(cmdline []string) {
|
|||||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||||
verbose = flag.Bool("v", false, "Whether to log verbosely")
|
verbose = flag.Bool("v", false, "Whether to log verbosely")
|
||||||
race = flag.Bool("race", false, "Execute the race detector")
|
race = flag.Bool("race", false, "Execute the race detector")
|
||||||
|
short = flag.Bool("short", false, "Pass the 'short'-flag to go test")
|
||||||
|
cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads")
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
|
|
||||||
|
// Get test fixtures.
|
||||||
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
|
downloadSpecTestFixtures(csdb, *cachedir)
|
||||||
|
|
||||||
// Configure the toolchain.
|
// Configure the toolchain.
|
||||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||||
if *dlgo {
|
if *dlgo {
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
tc.Root = build.DownloadGo(csdb)
|
||||||
tc.Root = build.DownloadGo(csdb, dlgoVersion)
|
|
||||||
}
|
}
|
||||||
gotest := tc.Go("test")
|
gotest := tc.Go("test")
|
||||||
|
|
||||||
|
// CI needs a bit more time for the statetests (default 10m).
|
||||||
|
gotest.Args = append(gotest.Args, "-timeout=20m")
|
||||||
|
|
||||||
|
// Enable CKZG backend in CI.
|
||||||
|
gotest.Args = append(gotest.Args, "-tags=ckzg")
|
||||||
|
|
||||||
|
// Enable integration-tests
|
||||||
|
gotest.Args = append(gotest.Args, "-tags=integrationtests")
|
||||||
|
|
||||||
// Test a single package at a time. CI builders are slow
|
// Test a single package at a time. CI builders are slow
|
||||||
// and some tests run into timeouts under load.
|
// and some tests run into timeouts under load.
|
||||||
gotest.Args = append(gotest.Args, "-p", "1")
|
gotest.Args = append(gotest.Args, "-p", "1")
|
||||||
@ -313,6 +322,9 @@ func doTest(cmdline []string) {
|
|||||||
if *race {
|
if *race {
|
||||||
gotest.Args = append(gotest.Args, "-race")
|
gotest.Args = append(gotest.Args, "-race")
|
||||||
}
|
}
|
||||||
|
if *short {
|
||||||
|
gotest.Args = append(gotest.Args, "-short")
|
||||||
|
}
|
||||||
|
|
||||||
packages := []string{"./..."}
|
packages := []string{"./..."}
|
||||||
if len(flag.CommandLine.Args()) > 0 {
|
if len(flag.CommandLine.Args()) > 0 {
|
||||||
@ -322,6 +334,25 @@ func doTest(cmdline []string) {
|
|||||||
build.MustRun(gotest)
|
build.MustRun(gotest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||||
|
func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string {
|
||||||
|
executionSpecTestsVersion, err := build.Version(csdb, "spec-tests")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
ext := ".tar.gz"
|
||||||
|
base := "fixtures_develop" // TODO(MariusVanDerWijden) rename once the version becomes part of the filename
|
||||||
|
url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/v%s/%s%s", executionSpecTestsVersion, base, ext)
|
||||||
|
archivePath := filepath.Join(cachedir, base+ext)
|
||||||
|
if err := csdb.DownloadFile(url, archivePath); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return filepath.Join(cachedir, base)
|
||||||
|
}
|
||||||
|
|
||||||
// doLint runs golangci-lint on requested packages.
|
// doLint runs golangci-lint on requested packages.
|
||||||
func doLint(cmdline []string) {
|
func doLint(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
@ -335,15 +366,17 @@ func doLint(cmdline []string) {
|
|||||||
|
|
||||||
linter := downloadLinter(*cachedir)
|
linter := downloadLinter(*cachedir)
|
||||||
lflags := []string{"run", "--config", ".golangci.yml"}
|
lflags := []string{"run", "--config", ".golangci.yml"}
|
||||||
build.MustRunCommand(linter, append(lflags, packages...)...)
|
build.MustRunCommandWithOutput(linter, append(lflags, packages...)...)
|
||||||
fmt.Println("You have achieved perfection.")
|
fmt.Println("You have achieved perfection.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadLinter downloads and unpacks golangci-lint.
|
// downloadLinter downloads and unpacks golangci-lint.
|
||||||
func downloadLinter(cachedir string) string {
|
func downloadLinter(cachedir string) string {
|
||||||
const version = "1.51.1"
|
|
||||||
|
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
|
version, err := build.Version(csdb, "golangci")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
arch := runtime.GOARCH
|
arch := runtime.GOARCH
|
||||||
ext := ".tar.gz"
|
ext := ".tar.gz"
|
||||||
|
|
||||||
@ -725,6 +758,10 @@ func doDebianSource(cmdline []string) {
|
|||||||
// to bootstrap the builder Go.
|
// to bootstrap the builder Go.
|
||||||
func downloadGoBootstrapSources(cachedir string) string {
|
func downloadGoBootstrapSources(cachedir string) string {
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
|
gobootVersion, err := build.Version(csdb, "ppa-builder")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion)
|
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion)
|
||||||
url := "https://dl.google.com/go/" + file
|
url := "https://dl.google.com/go/" + file
|
||||||
dst := filepath.Join(cachedir, file)
|
dst := filepath.Join(cachedir, file)
|
||||||
@ -737,6 +774,10 @@ func downloadGoBootstrapSources(cachedir string) string {
|
|||||||
// downloadGoSources downloads the Go source tarball.
|
// downloadGoSources downloads the Go source tarball.
|
||||||
func downloadGoSources(cachedir string) string {
|
func downloadGoSources(cachedir string) string {
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
|
dlgoVersion, err := build.Version(csdb, "golang")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
|
file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
|
||||||
url := "https://dl.google.com/go/" + file
|
url := "https://dl.google.com/go/" + file
|
||||||
dst := filepath.Join(cachedir, file)
|
dst := filepath.Join(cachedir, file)
|
||||||
@ -1063,3 +1104,7 @@ func doPurge(cmdline []string) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doSanityCheck() {
|
||||||
|
build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt"))
|
||||||
|
}
|
||||||
|
@ -28,7 +28,7 @@ override_dh_auto_build:
|
|||||||
mv .mod $(GOPATH)/pkg/mod
|
mv .mod $(GOPATH)/pkg/mod
|
||||||
|
|
||||||
# A fresh Go was built, all dependency downloads faked, hope build works now
|
# A fresh Go was built, all dependency downloads faked, hope build works now
|
||||||
../.go/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
../.go/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}} -ubuntu {{.Distro}}
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
# - NSIS Large Strings build, http://nsis.sourceforge.net/Special_Builds
|
# - NSIS Large Strings build, http://nsis.sourceforge.net/Special_Builds
|
||||||
# - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi)
|
# - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi)
|
||||||
#
|
#
|
||||||
# After intalling NSIS extra the NSIS Large Strings build zip and replace the makensis.exe and the
|
# After installing NSIS extra the NSIS Large Strings build zip and replace the makensis.exe and the
|
||||||
# files found in Stub.
|
# files found in Stub.
|
||||||
#
|
#
|
||||||
# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller
|
# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller
|
||||||
|
@ -46,12 +46,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -64,10 +65,8 @@ var (
|
|||||||
"vendor/", "tests/testdata/", "build/",
|
"vendor/", "tests/testdata/", "build/",
|
||||||
|
|
||||||
// don't relicense vendored sources
|
// don't relicense vendored sources
|
||||||
"cmd/internal/browser",
|
|
||||||
"common/bitutil/bitutil",
|
"common/bitutil/bitutil",
|
||||||
"common/prque/",
|
"common/prque/",
|
||||||
"consensus/ethash/xor.go",
|
|
||||||
"crypto/blake2b/",
|
"crypto/blake2b/",
|
||||||
"crypto/bn256/",
|
"crypto/bn256/",
|
||||||
"crypto/bls12381/",
|
"crypto/bls12381/",
|
||||||
@ -77,6 +76,7 @@ var (
|
|||||||
"log/",
|
"log/",
|
||||||
"metrics/",
|
"metrics/",
|
||||||
"signer/rules/deps",
|
"signer/rules/deps",
|
||||||
|
"internal/reexec",
|
||||||
|
|
||||||
// skip special licenses
|
// skip special licenses
|
||||||
"crypto/secp256k1", // Relicensed to BSD-3 via https://github.com/ethereum/go-ethereum/pull/17225
|
"crypto/secp256k1", // Relicensed to BSD-3 via https://github.com/ethereum/go-ethereum/pull/17225
|
||||||
@ -152,13 +152,6 @@ func (i info) gpl() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// authors implements the sort.Interface for strings in case-insensitive mode.
|
|
||||||
type authors []string
|
|
||||||
|
|
||||||
func (as authors) Len() int { return len(as) }
|
|
||||||
func (as authors) Less(i, j int) bool { return strings.ToLower(as[i]) < strings.ToLower(as[j]) }
|
|
||||||
func (as authors) Swap(i, j int) { as[i], as[j] = as[j], as[i] }
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
files = getFiles()
|
files = getFiles()
|
||||||
@ -299,7 +292,9 @@ func writeAuthors(files []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Write sorted list of authors back to the file.
|
// Write sorted list of authors back to the file.
|
||||||
sort.Sort(authors(list))
|
slices.SortFunc(list, func(a, b string) bool {
|
||||||
|
return strings.ToLower(a) < strings.ToLower(b)
|
||||||
|
})
|
||||||
content := new(bytes.Buffer)
|
content := new(bytes.Buffer)
|
||||||
content.WriteString(authorsFileHeader)
|
content.WriteString(authorsFileHeader)
|
||||||
for _, a := range list {
|
for _, a := range list {
|
||||||
|
@ -232,7 +232,7 @@ func abigen(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNameFilter(t *testing.T) {
|
func TestNameFilter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := newNameFilter("Foo")
|
_, err := newNameFilter("Foo")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
_, err = newNameFilter("too/many:colons:Foo")
|
_, err = newNameFilter("too/many:colons:Foo")
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
@ -43,7 +44,7 @@ func main() {
|
|||||||
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>)")
|
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>)")
|
||||||
netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)")
|
netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)")
|
||||||
runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode")
|
runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode")
|
||||||
verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-5)")
|
verbosity = flag.Int("verbosity", 3, "log verbosity (0-5)")
|
||||||
vmodule = flag.String("vmodule", "", "log verbosity pattern")
|
vmodule = flag.String("vmodule", "", "log verbosity pattern")
|
||||||
|
|
||||||
nodeKey *ecdsa.PrivateKey
|
nodeKey *ecdsa.PrivateKey
|
||||||
@ -51,10 +52,11 @@ func main() {
|
|||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||||
glogger.Verbosity(log.Lvl(*verbosity))
|
slogVerbosity := log.FromLegacyLevel(*verbosity)
|
||||||
|
glogger.Verbosity(slogVerbosity)
|
||||||
glogger.Vmodule(*vmodule)
|
glogger.Vmodule(*vmodule)
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
natm, err := nat.Parse(*natdesc)
|
natm, err := nat.Parse(*natdesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,21 +109,20 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("-ListenUDP: %v", err)
|
utils.Fatalf("-ListenUDP: %v", err)
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
realaddr := conn.LocalAddr().(*net.UDPAddr)
|
|
||||||
if natm != nil {
|
|
||||||
if !realaddr.IP.IsLoopback() {
|
|
||||||
go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
|
|
||||||
}
|
|
||||||
if ext, err := natm.ExternalIP(); err == nil {
|
|
||||||
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printNotice(&nodeKey.PublicKey, *realaddr)
|
|
||||||
|
|
||||||
db, _ := enode.OpenDB("")
|
db, _ := enode.OpenDB("")
|
||||||
ln := enode.NewLocalNode(db, nodeKey)
|
ln := enode.NewLocalNode(db, nodeKey)
|
||||||
|
|
||||||
|
listenerAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||||
|
if natm != nil && !listenerAddr.IP.IsLoopback() {
|
||||||
|
natAddr := doPortMapping(natm, ln, listenerAddr)
|
||||||
|
if natAddr != nil {
|
||||||
|
listenerAddr = natAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printNotice(&nodeKey.PublicKey, *listenerAddr)
|
||||||
cfg := discover.Config{
|
cfg := discover.Config{
|
||||||
PrivateKey: nodeKey,
|
PrivateKey: nodeKey,
|
||||||
NetRestrict: restrictList,
|
NetRestrict: restrictList,
|
||||||
@ -148,3 +149,61 @@ func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) {
|
|||||||
fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
|
fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
|
||||||
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
|
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doPortMapping(natm nat.Interface, ln *enode.LocalNode, addr *net.UDPAddr) *net.UDPAddr {
|
||||||
|
const (
|
||||||
|
protocol = "udp"
|
||||||
|
name = "ethereum discovery"
|
||||||
|
)
|
||||||
|
newLogger := func(external int, internal int) log.Logger {
|
||||||
|
return log.New("proto", protocol, "extport", external, "intport", internal, "interface", natm)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
intport = addr.Port
|
||||||
|
extaddr = &net.UDPAddr{IP: addr.IP, Port: addr.Port}
|
||||||
|
mapTimeout = nat.DefaultMapTimeout
|
||||||
|
log = newLogger(addr.Port, intport)
|
||||||
|
)
|
||||||
|
addMapping := func() {
|
||||||
|
// Get the external address.
|
||||||
|
var err error
|
||||||
|
extaddr.IP, err = natm.ExternalIP()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Couldn't get external IP", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create the mapping.
|
||||||
|
p, err := natm.AddMapping(protocol, extaddr.Port, intport, name, mapTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Couldn't add port mapping", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p != uint16(extaddr.Port) {
|
||||||
|
extaddr.Port = int(p)
|
||||||
|
log = newLogger(extaddr.Port, intport)
|
||||||
|
log.Info("NAT mapped alternative port")
|
||||||
|
} else {
|
||||||
|
log.Info("NAT mapped port")
|
||||||
|
}
|
||||||
|
// Update IP/port information of the local node.
|
||||||
|
ln.SetStaticIP(extaddr.IP)
|
||||||
|
ln.SetFallbackUDP(extaddr.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform mapping once, synchronously.
|
||||||
|
log.Info("Attempting port mapping")
|
||||||
|
addMapping()
|
||||||
|
|
||||||
|
// Refresh the mapping periodically.
|
||||||
|
go func() {
|
||||||
|
refresh := time.NewTimer(mapTimeout)
|
||||||
|
defer refresh.Stop()
|
||||||
|
for range refresh.C {
|
||||||
|
addMapping()
|
||||||
|
refresh.Reset(mapTimeout)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return extaddr
|
||||||
|
}
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
## Checkpoint-admin
|
|
||||||
|
|
||||||
Checkpoint-admin is a tool for updating checkpoint oracle status. It provides a series of functions including deploying checkpoint oracle contract, signing for new checkpoints, and updating checkpoints in the checkpoint oracle contract.
|
|
||||||
|
|
||||||
### Checkpoint
|
|
||||||
|
|
||||||
In the LES protocol, there is an important concept called checkpoint. In simple terms, whenever a certain number of blocks are generated on the blockchain, a new checkpoint is generated which contains some important information such as
|
|
||||||
|
|
||||||
* Block hash at checkpoint
|
|
||||||
* Canonical hash trie root at checkpoint
|
|
||||||
* Bloom trie root at checkpoint
|
|
||||||
|
|
||||||
*For a more detailed introduction to checkpoint, please see the LES [spec](https://github.com/ethereum/devp2p/blob/master/caps/les.md).*
|
|
||||||
|
|
||||||
Using this information, light clients can skip all historical block headers when synchronizing data and start synchronization from this checkpoint. Therefore, as long as the light client can obtain some latest and correct checkpoints, the amount of data and time for synchronization will be greatly reduced.
|
|
||||||
|
|
||||||
However, from a security perspective, the most critical step in a synchronization algorithm based on checkpoints is to determine whether the checkpoint used by the light client is correct. Otherwise, all blockchain data synchronized based on this checkpoint may be wrong. For this we provide two different ways to ensure the correctness of the checkpoint used by the light client.
|
|
||||||
|
|
||||||
#### Hardcoded checkpoint
|
|
||||||
|
|
||||||
There are several hardcoded checkpoints in the [source code](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L38) of the go-ethereum project. These checkpoints are updated by go-ethereum developers when new versions of software are released. Because light client users trust Geth developers to some extent, hardcoded checkpoints in the code can also be considered correct.
|
|
||||||
|
|
||||||
#### Checkpoint oracle
|
|
||||||
|
|
||||||
Hardcoded checkpoints can solve the problem of verifying the correctness of checkpoints (although this is a more centralized solution). But the pain point of this solution is that developers can only update checkpoints when a new version of software is released. In addition, light client users usually do not keep the Geth version they use always up to date. So hardcoded checkpoints used by users are generally stale. Therefore, it still needs to download a large amount of blockchain data during synchronization.
|
|
||||||
|
|
||||||
Checkpoint oracle is a more flexible solution. In simple terms, this is a smart contract that is deployed on the blockchain. The smart contract records several designated trusted signers. Whenever enough trusted signers have issued their signatures for the same checkpoint, it can be considered that the checkpoint has been authenticated by the signers. Checkpoints authenticated by trusted signers can be considered correct.
|
|
||||||
|
|
||||||
So this way, even without updating the software version, as long as the trusted signers regularly update the checkpoint in oracle on time, the light client can always use the latest and verified checkpoint for data synchronization.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Checkpoint-admin is a command line tool designed for checkpoint oracle. Users can easily deploy contracts and update checkpoints through this tool.
|
|
||||||
|
|
||||||
#### Install
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go get github.com/ethereum/go-ethereum/cmd/checkpoint-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Deploy
|
|
||||||
|
|
||||||
Deploy checkpoint oracle contract. `--signers` indicates the specified trusted signer, and `--threshold` indicates the minimum number of signatures required by trusted signers to update a checkpoint.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
checkpoint-admin deploy --rpc <NODE_RPC_ENDPOINT> --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_TX> --signers <TRUSTED_SIGNER_LIST> --threshold 1
|
|
||||||
```
|
|
||||||
|
|
||||||
It is worth noting that checkpoint-admin only supports clef as a signer for transactions and plain text(checkpoint). For more clef usage, please see the clef [tutorial](https://geth.ethereum.org/docs/tools/clef/tutorial) .
|
|
||||||
|
|
||||||
#### Sign
|
|
||||||
|
|
||||||
Checkpoint-admin provides two different modes of signing. You can automatically obtain the current stable checkpoint and sign it interactively, and you can also use the information provided by the command line flags to sign checkpoint offline.
|
|
||||||
|
|
||||||
**Interactive mode**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
checkpoint-admin sign --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_CHECKPOINT> --rpc <NODE_RPC_ENDPOINT>
|
|
||||||
```
|
|
||||||
|
|
||||||
*It is worth noting that the connected Geth node can be a fullnode or a light client. If it is fullnode, you must enable the LES protocol. E.G. add `--light.serv 50` to the startup command line flags*.
|
|
||||||
|
|
||||||
**Offline mode**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
checkpoint-admin sign --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_CHECKPOINT> --index <CHECKPOINT_INDEX> --hash <CHECKPOINT_HASH> --oracle <CHECKPOINT_ORACLE_ADDRESS>
|
|
||||||
```
|
|
||||||
|
|
||||||
*CHECKPOINT_HASH is obtained based on this [calculation method](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L251).*
|
|
||||||
|
|
||||||
#### Publish
|
|
||||||
|
|
||||||
Collect enough signatures from different trusted signers for the same checkpoint and submit them to oracle to update the "authenticated" checkpoint in the contract.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
checkpoint-admin publish --clef <CLEF_ENDPOINT> --rpc <NODE_RPC_ENDPOINT> --signer <SIGNER_TO_SIGN_TX> --index <CHECKPOINT_INDEX> --signatures <CHECKPOINT_SIGNATURE_LIST>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Status query
|
|
||||||
|
|
||||||
Check the latest status of checkpoint oracle.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
checkpoint-admin status --rpc <NODE_RPC_ENDPOINT>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Enable checkpoint oracle in your private network
|
|
||||||
|
|
||||||
Currently, only the Ethereum mainnet and the default supported test networks (rinkeby, goerli) activate this feature. If you want to activate this feature in your private network, you can overwrite the relevant checkpoint oracle settings through the configuration file after deploying the oracle contract.
|
|
||||||
|
|
||||||
* Get your node configuration file `geth dumpconfig OTHER_COMMAND_LINE_OPTIONS > config.toml`
|
|
||||||
* Edit the configuration file and add the following information
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[Eth.CheckpointOracle]
|
|
||||||
Address = CHECKPOINT_ORACLE_ADDRESS
|
|
||||||
Signers = [TRUSTED_SIGNER_1, ..., TRUSTED_SIGNER_N]
|
|
||||||
Threshold = THRESHOLD
|
|
||||||
```
|
|
||||||
|
|
||||||
* Start geth with the modified configuration file
|
|
||||||
|
|
||||||
*In the private network, all fullnodes and light clients need to be started using the same checkpoint oracle settings.*
|
|
@ -1,120 +0,0 @@
|
|||||||
// Copyright 2019 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/external"
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
|
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newClient creates a client with specified remote URL.
|
|
||||||
func newClient(ctx *cli.Context) *ethclient.Client {
|
|
||||||
client, err := ethclient.Dial(ctx.String(nodeURLFlag.Name))
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to connect to Ethereum node: %v", err)
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRPCClient creates a rpc client with specified node URL.
|
|
||||||
func newRPCClient(url string) *rpc.Client {
|
|
||||||
client, err := rpc.Dial(url)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to connect to Ethereum node: %v", err)
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContractAddr retrieves the register contract address through
|
|
||||||
// rpc request.
|
|
||||||
func getContractAddr(client *rpc.Client) common.Address {
|
|
||||||
var addr string
|
|
||||||
if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil {
|
|
||||||
utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err)
|
|
||||||
}
|
|
||||||
return common.HexToAddress(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCheckpoint retrieves the specified checkpoint or the latest one
|
|
||||||
// through rpc request.
|
|
||||||
func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoint {
|
|
||||||
var checkpoint *params.TrustedCheckpoint
|
|
||||||
|
|
||||||
if ctx.IsSet(indexFlag.Name) {
|
|
||||||
var result [3]string
|
|
||||||
index := uint64(ctx.Int64(indexFlag.Name))
|
|
||||||
if err := client.Call(&result, "les_getCheckpoint", index); err != nil {
|
|
||||||
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
|
|
||||||
}
|
|
||||||
checkpoint = ¶ms.TrustedCheckpoint{
|
|
||||||
SectionIndex: index,
|
|
||||||
SectionHead: common.HexToHash(result[0]),
|
|
||||||
CHTRoot: common.HexToHash(result[1]),
|
|
||||||
BloomRoot: common.HexToHash(result[2]),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var result [4]string
|
|
||||||
err := client.Call(&result, "les_latestCheckpoint")
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
|
|
||||||
}
|
|
||||||
index, err := strconv.ParseUint(result[0], 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to parse checkpoint index %v", err)
|
|
||||||
}
|
|
||||||
checkpoint = ¶ms.TrustedCheckpoint{
|
|
||||||
SectionIndex: index,
|
|
||||||
SectionHead: common.HexToHash(result[1]),
|
|
||||||
CHTRoot: common.HexToHash(result[2]),
|
|
||||||
BloomRoot: common.HexToHash(result[3]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return checkpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// newContract creates a registrar contract instance with specified
|
|
||||||
// contract address or the default contracts for mainnet or testnet.
|
|
||||||
func newContract(client *rpc.Client) (common.Address, *checkpointoracle.CheckpointOracle) {
|
|
||||||
addr := getContractAddr(client)
|
|
||||||
if addr == (common.Address{}) {
|
|
||||||
utils.Fatalf("No specified registrar contract address")
|
|
||||||
}
|
|
||||||
contract, err := checkpointoracle.NewCheckpointOracle(addr, ethclient.NewClient(client))
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err)
|
|
||||||
}
|
|
||||||
return addr, contract
|
|
||||||
}
|
|
||||||
|
|
||||||
// newClefSigner sets up a clef backend and returns a clef transaction signer.
|
|
||||||
func newClefSigner(ctx *cli.Context) *bind.TransactOpts {
|
|
||||||
clef, err := external.NewExternalSigner(ctx.String(clefURLFlag.Name))
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to create clef signer %v", err)
|
|
||||||
}
|
|
||||||
return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))})
|
|
||||||
}
|
|
@ -1,311 +0,0 @@
|
|||||||
// Copyright 2019 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
|
|
||||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandDeploy = &cli.Command{
|
|
||||||
Name: "deploy",
|
|
||||||
Usage: "Deploy a new checkpoint oracle contract",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
nodeURLFlag,
|
|
||||||
clefURLFlag,
|
|
||||||
signerFlag,
|
|
||||||
signersFlag,
|
|
||||||
thresholdFlag,
|
|
||||||
},
|
|
||||||
Action: deploy,
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandSign = &cli.Command{
|
|
||||||
Name: "sign",
|
|
||||||
Usage: "Sign the checkpoint with the specified key",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
nodeURLFlag,
|
|
||||||
clefURLFlag,
|
|
||||||
signerFlag,
|
|
||||||
indexFlag,
|
|
||||||
hashFlag,
|
|
||||||
oracleFlag,
|
|
||||||
},
|
|
||||||
Action: sign,
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandPublish = &cli.Command{
|
|
||||||
Name: "publish",
|
|
||||||
Usage: "Publish a checkpoint into the oracle",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
nodeURLFlag,
|
|
||||||
clefURLFlag,
|
|
||||||
signerFlag,
|
|
||||||
indexFlag,
|
|
||||||
signaturesFlag,
|
|
||||||
},
|
|
||||||
Action: publish,
|
|
||||||
}
|
|
||||||
|
|
||||||
// deploy deploys the checkpoint registrar contract.
|
|
||||||
//
|
|
||||||
// Note the network where the contract is deployed depends on
|
|
||||||
// the network where the connected node is located.
|
|
||||||
func deploy(ctx *cli.Context) error {
|
|
||||||
// Gather all the addresses that should be permitted to sign
|
|
||||||
var addrs []common.Address
|
|
||||||
for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") {
|
|
||||||
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
|
|
||||||
utils.Fatalf("Invalid account in --signers: '%s'", trimmed)
|
|
||||||
}
|
|
||||||
addrs = append(addrs, common.HexToAddress(account))
|
|
||||||
}
|
|
||||||
// Retrieve and validate the signing threshold
|
|
||||||
needed := ctx.Int(thresholdFlag.Name)
|
|
||||||
if needed == 0 || needed > len(addrs) {
|
|
||||||
utils.Fatalf("Invalid signature threshold %d", needed)
|
|
||||||
}
|
|
||||||
// Print a summary to ensure the user understands what they're signing
|
|
||||||
fmt.Printf("Deploying new checkpoint oracle:\n\n")
|
|
||||||
for i, addr := range addrs {
|
|
||||||
fmt.Printf("Admin %d => %s\n", i+1, addr.Hex())
|
|
||||||
}
|
|
||||||
fmt.Printf("\nSignatures needed to publish: %d\n", needed)
|
|
||||||
|
|
||||||
// setup clef signer, create an abigen transactor and an RPC client
|
|
||||||
transactor, client := newClefSigner(ctx), newClient(ctx)
|
|
||||||
|
|
||||||
// Deploy the checkpoint oracle
|
|
||||||
fmt.Println("Sending deploy request to Clef...")
|
|
||||||
oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
|
|
||||||
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to deploy checkpoint oracle %v", err)
|
|
||||||
}
|
|
||||||
log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sign creates the signature for specific checkpoint
|
|
||||||
// with local key. Only contract admins have the permission to
|
|
||||||
// sign checkpoint.
|
|
||||||
func sign(ctx *cli.Context) error {
|
|
||||||
var (
|
|
||||||
offline bool // The indicator whether we sign checkpoint by offline.
|
|
||||||
chash common.Hash
|
|
||||||
cindex uint64
|
|
||||||
address common.Address
|
|
||||||
|
|
||||||
node *rpc.Client
|
|
||||||
oracle *checkpointoracle.CheckpointOracle
|
|
||||||
)
|
|
||||||
if !ctx.IsSet(nodeURLFlag.Name) {
|
|
||||||
// Offline mode signing
|
|
||||||
offline = true
|
|
||||||
if !ctx.IsSet(hashFlag.Name) {
|
|
||||||
utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode")
|
|
||||||
}
|
|
||||||
chash = common.HexToHash(ctx.String(hashFlag.Name))
|
|
||||||
|
|
||||||
if !ctx.IsSet(indexFlag.Name) {
|
|
||||||
utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode")
|
|
||||||
}
|
|
||||||
cindex = ctx.Uint64(indexFlag.Name)
|
|
||||||
|
|
||||||
if !ctx.IsSet(oracleFlag.Name) {
|
|
||||||
utils.Fatalf("Please specify oracle address (--oracle) to sign in offline mode")
|
|
||||||
}
|
|
||||||
address = common.HexToAddress(ctx.String(oracleFlag.Name))
|
|
||||||
} else {
|
|
||||||
// Interactive mode signing, retrieve the data from the remote node
|
|
||||||
node = newRPCClient(ctx.String(nodeURLFlag.Name))
|
|
||||||
|
|
||||||
checkpoint := getCheckpoint(ctx, node)
|
|
||||||
chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, getContractAddr(node)
|
|
||||||
|
|
||||||
// Check the validity of checkpoint
|
|
||||||
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancelFn()
|
|
||||||
|
|
||||||
head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
num := head.Number.Uint64()
|
|
||||||
if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
|
|
||||||
utils.Fatalf("Invalid future checkpoint")
|
|
||||||
}
|
|
||||||
_, oracle = newContract(node)
|
|
||||||
latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cindex < latest {
|
|
||||||
utils.Fatalf("Checkpoint is too old")
|
|
||||||
}
|
|
||||||
if cindex == latest && (latest != 0 || h.Uint64() != 0) {
|
|
||||||
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
signature string
|
|
||||||
signer string
|
|
||||||
)
|
|
||||||
// isAdmin checks whether the specified signer is admin.
|
|
||||||
isAdmin := func(addr common.Address) error {
|
|
||||||
signers, err := oracle.Contract().GetAllAdmin(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, s := range signers {
|
|
||||||
if s == addr {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("signer %v is not the admin", addr.Hex())
|
|
||||||
}
|
|
||||||
// Print to the user the data thy are about to sign
|
|
||||||
fmt.Printf("Oracle => %s\n", address.Hex())
|
|
||||||
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
|
|
||||||
|
|
||||||
// Sign checkpoint in clef mode.
|
|
||||||
signer = ctx.String(signerFlag.Name)
|
|
||||||
|
|
||||||
if !offline {
|
|
||||||
if err := isAdmin(common.HexToAddress(signer)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clef := newRPCClient(ctx.String(clefURLFlag.Name))
|
|
||||||
p := make(map[string]string)
|
|
||||||
buf := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(buf, cindex)
|
|
||||||
p["address"] = address.Hex()
|
|
||||||
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
|
|
||||||
|
|
||||||
fmt.Println("Sending signing request to Clef...")
|
|
||||||
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
|
|
||||||
utils.Fatalf("Failed to sign checkpoint, err %v", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Signer => %s\n", signer)
|
|
||||||
fmt.Printf("Signature => %s\n", signature)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sighash calculates the hash of the data to sign for the checkpoint oracle.
|
|
||||||
func sighash(index uint64, oracle common.Address, hash common.Hash) []byte {
|
|
||||||
buf := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(buf, index)
|
|
||||||
|
|
||||||
data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...)
|
|
||||||
return crypto.Keccak256(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ecrecover calculates the sender address from a sighash and signature combo.
|
|
||||||
func ecrecover(sighash []byte, sig []byte) common.Address {
|
|
||||||
sig[64] -= 27
|
|
||||||
defer func() { sig[64] += 27 }()
|
|
||||||
|
|
||||||
signer, err := crypto.SigToPub(sighash, sig)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err)
|
|
||||||
}
|
|
||||||
return crypto.PubkeyToAddress(*signer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// publish registers the specified checkpoint which generated by connected node
|
|
||||||
// with a authorised private key.
|
|
||||||
func publish(ctx *cli.Context) error {
|
|
||||||
// Print the checkpoint oracle's current status to make sure we're interacting
|
|
||||||
// with the correct network and contract.
|
|
||||||
status(ctx)
|
|
||||||
|
|
||||||
// Gather the signatures from the CLI
|
|
||||||
var sigs [][]byte
|
|
||||||
for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") {
|
|
||||||
trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x")
|
|
||||||
if len(trimmed) != 130 {
|
|
||||||
utils.Fatalf("Invalid signature in --signature: '%s'", trimmed)
|
|
||||||
} else {
|
|
||||||
sigs = append(sigs, common.Hex2Bytes(trimmed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retrieve the checkpoint we want to sign to sort the signatures
|
|
||||||
var (
|
|
||||||
client = newRPCClient(ctx.String(nodeURLFlag.Name))
|
|
||||||
addr, oracle = newContract(client)
|
|
||||||
checkpoint = getCheckpoint(ctx, client)
|
|
||||||
sighash = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash())
|
|
||||||
)
|
|
||||||
for i := 0; i < len(sigs); i++ {
|
|
||||||
for j := i + 1; j < len(sigs); j++ {
|
|
||||||
signerA := ecrecover(sighash, sigs[i])
|
|
||||||
signerB := ecrecover(sighash, sigs[j])
|
|
||||||
if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 {
|
|
||||||
sigs[i], sigs[j] = sigs[j], sigs[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retrieve recent header info to protect replay attack
|
|
||||||
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancelFn()
|
|
||||||
|
|
||||||
head, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
num := head.Number.Uint64()
|
|
||||||
recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Print a summary of the operation that's going to be performed
|
|
||||||
fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex())
|
|
||||||
for i, sig := range sigs {
|
|
||||||
fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex())
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex())
|
|
||||||
|
|
||||||
// Publish the checkpoint into the oracle
|
|
||||||
fmt.Println("Sending publish request to Clef...")
|
|
||||||
tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Register contract failed %v", err)
|
|
||||||
}
|
|
||||||
log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex())
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
// Copyright 2019 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// checkpoint-admin is a utility that can be used to query checkpoint information
|
|
||||||
// and register stable checkpoints into an oracle contract.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var app = flags.NewApp("ethereum checkpoint helper tool")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
app.Commands = []*cli.Command{
|
|
||||||
commandStatus,
|
|
||||||
commandDeploy,
|
|
||||||
commandSign,
|
|
||||||
commandPublish,
|
|
||||||
}
|
|
||||||
app.Flags = []cli.Flag{
|
|
||||||
oracleFlag,
|
|
||||||
nodeURLFlag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commonly used command line flags.
|
|
||||||
var (
|
|
||||||
indexFlag = &cli.Int64Flag{
|
|
||||||
Name: "index",
|
|
||||||
Usage: "Checkpoint index (query latest from remote node if not specified)",
|
|
||||||
}
|
|
||||||
hashFlag = &cli.StringFlag{
|
|
||||||
Name: "hash",
|
|
||||||
Usage: "Checkpoint hash (query latest from remote node if not specified)",
|
|
||||||
}
|
|
||||||
oracleFlag = &cli.StringFlag{
|
|
||||||
Name: "oracle",
|
|
||||||
Usage: "Checkpoint oracle address (query from remote node if not specified)",
|
|
||||||
}
|
|
||||||
thresholdFlag = &cli.Int64Flag{
|
|
||||||
Name: "threshold",
|
|
||||||
Usage: "Minimal number of signatures required to approve a checkpoint",
|
|
||||||
}
|
|
||||||
nodeURLFlag = &cli.StringFlag{
|
|
||||||
Name: "rpc",
|
|
||||||
Value: "http://localhost:8545",
|
|
||||||
Usage: "The rpc endpoint of a local or remote geth node",
|
|
||||||
}
|
|
||||||
clefURLFlag = &cli.StringFlag{
|
|
||||||
Name: "clef",
|
|
||||||
Value: "http://localhost:8550",
|
|
||||||
Usage: "The rpc endpoint of clef",
|
|
||||||
}
|
|
||||||
signerFlag = &cli.StringFlag{
|
|
||||||
Name: "signer",
|
|
||||||
Usage: "Signer address for clef signing",
|
|
||||||
}
|
|
||||||
signersFlag = &cli.StringFlag{
|
|
||||||
Name: "signers",
|
|
||||||
Usage: "Comma separated accounts of trusted checkpoint signers",
|
|
||||||
}
|
|
||||||
signaturesFlag = &cli.StringFlag{
|
|
||||||
Name: "signatures",
|
|
||||||
Usage: "Comma separated checkpoint signatures to submit",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
|
||||||
fdlimit.Raise(2048)
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
// Copyright 2019 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandStatus = &cli.Command{
|
|
||||||
Name: "status",
|
|
||||||
Usage: "Fetches the signers and checkpoint status of the oracle contract",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
nodeURLFlag,
|
|
||||||
},
|
|
||||||
Action: status,
|
|
||||||
}
|
|
||||||
|
|
||||||
// status fetches the admin list of specified registrar contract.
|
|
||||||
func status(ctx *cli.Context) error {
|
|
||||||
// Create a wrapper around the checkpoint oracle contract
|
|
||||||
addr, oracle := newContract(newRPCClient(ctx.String(nodeURLFlag.Name)))
|
|
||||||
fmt.Printf("Oracle => %s\n", addr.Hex())
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// Retrieve the list of authorized signers (admins)
|
|
||||||
admins, err := oracle.Contract().GetAllAdmin(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, admin := range admins {
|
|
||||||
fmt.Printf("Admin %d => %s\n", i+1, admin.Hex())
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// Retrieve the latest checkpoint
|
|
||||||
index, checkpoint, height, err := oracle.Contract().GetLatestCheckpoint(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("Checkpoint (published at #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and asks for permission to sign the content. If the users grants the signing request, Clef will send the signature back to the DApp.
|
Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and asks for permission to sign the content. If the users grants the signing request, Clef will send the signature back to the DApp.
|
||||||
|
|
||||||
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronised with the chain, or is a node that has no built-in (or limited) account management.
|
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management.
|
||||||
|
|
||||||
Clef can run as a daemon on the same machine, off a usb-stick like [USB armory](https://inversepath.com/usbarmory), or even a separate VM in a [QubesOS](https://www.qubes-os.org/) type setup.
|
Clef can run as a daemon on the same machine, off a usb-stick like [USB armory](https://inversepath.com/usbarmory), or even a separate VM in a [QubesOS](https://www.qubes-os.org/) type setup.
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ GLOBAL OPTIONS:
|
|||||||
--loglevel value log level to emit to the screen (default: 4)
|
--loglevel value log level to emit to the screen (default: 4)
|
||||||
--keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore")
|
--keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore")
|
||||||
--configdir value Directory for Clef configuration (default: "$HOME/.clef")
|
--configdir value Directory for Clef configuration (default: "$HOME/.clef")
|
||||||
--chainid value Chain id to use for signing (1=mainnet, 4=Rinkeby, 5=Goerli) (default: 1)
|
--chainid value Chain id to use for signing (1=mainnet, 5=Goerli) (default: 1)
|
||||||
--lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength
|
--lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength
|
||||||
--nousb Disables monitoring for and managing USB hardware wallets
|
--nousb Disables monitoring for and managing USB hardware wallets
|
||||||
--pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm")
|
--pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm")
|
||||||
@ -916,7 +916,7 @@ There are a couple of implementation for a UI. We'll try to keep this list up to
|
|||||||
|
|
||||||
| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters|
|
| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters|
|
||||||
| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- |
|
| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- |
|
||||||
| QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
| QtSigner| https://github.com/holiman/qtsigner/ | Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
||||||
| GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
| GtkSigner| https://github.com/holiman/gtksigner | Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
||||||
| Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
| Frame | https://github.com/floating/frame/commits/go-signer | Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
||||||
| Clef UI| https://github.com/ethereum/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)|
|
| Clef UI| https://github.com/ethereum/clef-ui | Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)|
|
||||||
|
@ -26,12 +26,13 @@ import (
|
|||||||
|
|
||||||
// TestImportRaw tests clef --importraw
|
// TestImportRaw tests clef --importraw
|
||||||
func TestImportRaw(t *testing.T) {
|
func TestImportRaw(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
||||||
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
||||||
t.Cleanup(func() { os.Remove(keyPath) })
|
t.Cleanup(func() { os.Remove(keyPath) })
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
t.Run("happy-path", func(t *testing.T) {
|
t.Run("happy-path", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Run clef importraw
|
// Run clef importraw
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||||
clef.input("myverylongpassword").input("myverylongpassword")
|
clef.input("myverylongpassword").input("myverylongpassword")
|
||||||
@ -43,6 +44,7 @@ func TestImportRaw(t *testing.T) {
|
|||||||
})
|
})
|
||||||
// tests clef --importraw with mismatched passwords.
|
// tests clef --importraw with mismatched passwords.
|
||||||
t.Run("pw-mismatch", func(t *testing.T) {
|
t.Run("pw-mismatch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Run clef importraw
|
// Run clef importraw
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||||
clef.input("myverylongpassword1").input("myverylongpassword2").WaitExit()
|
clef.input("myverylongpassword1").input("myverylongpassword2").WaitExit()
|
||||||
@ -52,6 +54,7 @@ func TestImportRaw(t *testing.T) {
|
|||||||
})
|
})
|
||||||
// tests clef --importraw with a too short password.
|
// tests clef --importraw with a too short password.
|
||||||
t.Run("short-pw", func(t *testing.T) {
|
t.Run("short-pw", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Run clef importraw
|
// Run clef importraw
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||||
clef.input("shorty").input("shorty").WaitExit()
|
clef.input("shorty").input("shorty").WaitExit()
|
||||||
@ -64,12 +67,13 @@ func TestImportRaw(t *testing.T) {
|
|||||||
|
|
||||||
// TestListAccounts tests clef --list-accounts
|
// TestListAccounts tests clef --list-accounts
|
||||||
func TestListAccounts(t *testing.T) {
|
func TestListAccounts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
||||||
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
||||||
t.Cleanup(func() { os.Remove(keyPath) })
|
t.Cleanup(func() { os.Remove(keyPath) })
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
t.Run("no-accounts", func(t *testing.T) {
|
t.Run("no-accounts", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-accounts")
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-accounts")
|
||||||
if out := string(clef.Output()); !strings.Contains(out, "The keystore is empty.") {
|
if out := string(clef.Output()); !strings.Contains(out, "The keystore is empty.") {
|
||||||
t.Logf("Output\n%v", out)
|
t.Logf("Output\n%v", out)
|
||||||
@ -77,6 +81,7 @@ func TestListAccounts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("one-account", func(t *testing.T) {
|
t.Run("one-account", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// First, we need to import
|
// First, we need to import
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||||
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
|
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
|
||||||
@ -91,12 +96,13 @@ func TestListAccounts(t *testing.T) {
|
|||||||
|
|
||||||
// TestListWallets tests clef --list-wallets
|
// TestListWallets tests clef --list-wallets
|
||||||
func TestListWallets(t *testing.T) {
|
func TestListWallets(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
||||||
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
||||||
t.Cleanup(func() { os.Remove(keyPath) })
|
t.Cleanup(func() { os.Remove(keyPath) })
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
t.Run("no-accounts", func(t *testing.T) {
|
t.Run("no-accounts", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-wallets")
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-wallets")
|
||||||
if out := string(clef.Output()); !strings.Contains(out, "There are no wallets.") {
|
if out := string(clef.Output()); !strings.Contains(out, "There are no wallets.") {
|
||||||
t.Logf("Output\n%v", out)
|
t.Logf("Output\n%v", out)
|
||||||
@ -104,6 +110,7 @@ func TestListWallets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("one-account", func(t *testing.T) {
|
t.Run("one-account", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// First, we need to import
|
// First, we need to import
|
||||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||||
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
|
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
|
||||||
|
@ -75,7 +75,7 @@ Example:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Info",
|
"type": "Info",
|
||||||
"message": "User should see this aswell"
|
"message": "User should see this as well"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -99,7 +100,7 @@ var (
|
|||||||
chainIdFlag = &cli.Int64Flag{
|
chainIdFlag = &cli.Int64Flag{
|
||||||
Name: "chainid",
|
Name: "chainid",
|
||||||
Value: params.MainnetChainConfig.ChainID.Int64(),
|
Value: params.MainnetChainConfig.ChainID.Int64(),
|
||||||
Usage: "Chain id to use for signing (1=mainnet, 4=Rinkeby, 5=Goerli)",
|
Usage: "Chain id to use for signing (1=mainnet, 5=Goerli)",
|
||||||
}
|
}
|
||||||
rpcPortFlag = &cli.IntFlag{
|
rpcPortFlag = &cli.IntFlag{
|
||||||
Name: "http.port",
|
Name: "http.port",
|
||||||
@ -326,7 +327,7 @@ func initializeSecrets(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if num != len(masterSeed) {
|
if num != len(masterSeed) {
|
||||||
return fmt.Errorf("failed to read enough random")
|
return errors.New("failed to read enough random")
|
||||||
}
|
}
|
||||||
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
||||||
if c.Bool(utils.LightKDFFlag.Name) {
|
if c.Bool(utils.LightKDFFlag.Name) {
|
||||||
@ -482,7 +483,7 @@ func initialize(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
} else if !c.Bool(acceptFlag.Name) {
|
} else if !c.Bool(acceptFlag.Name) {
|
||||||
if !confirm(legalWarning) {
|
if !confirm(legalWarning) {
|
||||||
return fmt.Errorf("aborted by user")
|
return errors.New("aborted by user")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
@ -491,7 +492,8 @@ func initialize(c *cli.Context) error {
|
|||||||
if usecolor {
|
if usecolor {
|
||||||
output = colorable.NewColorable(logOutput)
|
output = colorable.NewColorable(logOutput)
|
||||||
}
|
}
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor))))
|
verbosity := log.FromLegacyLevel(c.Int(logLevelFlag.Name))
|
||||||
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, verbosity, usecolor)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -580,6 +582,7 @@ func accountImport(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if first != second {
|
if first != second {
|
||||||
|
//lint:ignore ST1005 This is a message for the user
|
||||||
return errors.New("Passwords do not match")
|
return errors.New("Passwords do not match")
|
||||||
}
|
}
|
||||||
acc, err := internalApi.ImportRawKey(hex.EncodeToString(crypto.FromECDSA(pKey)), first)
|
acc, err := internalApi.ImportRawKey(hex.EncodeToString(crypto.FromECDSA(pKey)), first)
|
||||||
@ -701,6 +704,7 @@ func signer(c *cli.Context) error {
|
|||||||
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
||||||
"light-kdf", lightKdf, "advanced", advanced)
|
"light-kdf", lightKdf, "advanced", advanced)
|
||||||
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
|
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
|
||||||
|
defer am.Close()
|
||||||
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
|
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
|
||||||
|
|
||||||
// Establish the bidirectional communication, by creating a new UI backend and registering
|
// Establish the bidirectional communication, by creating a new UI backend and registering
|
||||||
@ -732,6 +736,7 @@ func signer(c *cli.Context) error {
|
|||||||
cors := utils.SplitAndTrim(c.String(utils.HTTPCORSDomainFlag.Name))
|
cors := utils.SplitAndTrim(c.String(utils.HTTPCORSDomainFlag.Name))
|
||||||
|
|
||||||
srv := rpc.NewServer()
|
srv := rpc.NewServer()
|
||||||
|
srv.SetBatchLimits(node.DefaultConfig.BatchRequestLimit, node.DefaultConfig.BatchResponseMaxSize)
|
||||||
err := node.RegisterApis(rpcAPI, []string{"account"}, srv)
|
err := node.RegisterApis(rpcAPI, []string{"account"}, srv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not register API: %w", err)
|
utils.Fatalf("Could not register API: %w", err)
|
||||||
@ -742,7 +747,7 @@ func signer(c *cli.Context) error {
|
|||||||
port := c.Int(rpcPortFlag.Name)
|
port := c.Int(rpcPortFlag.Name)
|
||||||
|
|
||||||
// start http server
|
// start http server
|
||||||
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.HTTPListenAddrFlag.Name), port)
|
httpEndpoint := net.JoinHostPort(c.String(utils.HTTPListenAddrFlag.Name), fmt.Sprintf("%d", port))
|
||||||
httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler)
|
httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not start RPC api: %v", err)
|
utils.Fatalf("Could not start RPC api: %v", err)
|
||||||
@ -844,7 +849,7 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
masterSeed, err := decryptSeed(cipherKey, password)
|
masterSeed, err := decryptSeed(cipherKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decrypt the master seed of clef")
|
return nil, errors.New("failed to decrypt the master seed of clef")
|
||||||
}
|
}
|
||||||
if len(masterSeed) < 256 {
|
if len(masterSeed) < 256 {
|
||||||
return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
|
return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
|
||||||
@ -1204,7 +1209,7 @@ func GenDoc(ctx *cli.Context) error {
|
|||||||
URL: accounts.URL{Path: ".. ignored .."},
|
URL: accounts.URL{Path: ".. ignored .."},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
|
Address: common.MaxAddress,
|
||||||
},
|
},
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class StdIOHandler:
|
|||||||
{"jsonrpc":"2.0","id":20,"method":"ui_approveTx","params":[{"transaction":{"from":"0xDEADbEeF000000000000000000000000DeaDbeEf","to":"0xDEADbEeF000000000000000000000000DeaDbeEf","gas":"0x3e8","gasPrice":"0x5","maxFeePerGas":null,"maxPriorityFeePerGas":null,"value":"0x6","nonce":"0x1","data":"0x"},"call_info":null,"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
{"jsonrpc":"2.0","id":20,"method":"ui_approveTx","params":[{"transaction":{"from":"0xDEADbEeF000000000000000000000000DeaDbeEf","to":"0xDEADbEeF000000000000000000000000DeaDbeEf","gas":"0x3e8","gasPrice":"0x5","maxFeePerGas":null,"maxPriorityFeePerGas":null,"value":"0x6","nonce":"0x1","data":"0x"},"call_info":null,"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
||||||
|
|
||||||
:param transaction: transaction info
|
:param transaction: transaction info
|
||||||
:param call_info: info abou the call, e.g. if ABI info could not be
|
:param call_info: info about the call, e.g. if ABI info could not be
|
||||||
:param meta: metadata about the request, e.g. where the call comes from
|
:param meta: metadata about the request, e.g. where the call comes from
|
||||||
:return:
|
:return:
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
|
@ -21,8 +21,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/reexec"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/reexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
const registeredName = "clef-test"
|
const registeredName = "clef-test"
|
||||||
|
@ -44,7 +44,7 @@ set to standard output. The following filters are supported:
|
|||||||
- `-limit <N>` limits the output set to N entries, taking the top N nodes by score
|
- `-limit <N>` limits the output set to N entries, taking the top N nodes by score
|
||||||
- `-ip <CIDR>` filters nodes by IP subnet
|
- `-ip <CIDR>` filters nodes by IP subnet
|
||||||
- `-min-age <duration>` filters nodes by 'first seen' time
|
- `-min-age <duration>` filters nodes by 'first seen' time
|
||||||
- `-eth-network <mainnet/rinkeby/goerli/sepolia>` filters nodes by "eth" ENR entry
|
- `-eth-network <mainnet/goerli/sepolia/holesky>` filters nodes by "eth" ENR entry
|
||||||
- `-les-server` filters nodes by LES server support
|
- `-les-server` filters nodes by LES server support
|
||||||
- `-snap` filters nodes by snap protocol support
|
- `-snap` filters nodes by snap protocol support
|
||||||
|
|
||||||
@ -108,31 +108,32 @@ Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.
|
|||||||
|
|
||||||
The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth].
|
The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth].
|
||||||
|
|
||||||
To run the eth protocol test suite against your implementation, the node needs to be initialized as such:
|
To run the eth protocol test suite against your implementation, the node needs to be initialized
|
||||||
|
with our test chain. The chain files are located in `./cmd/devp2p/internal/ethtest/testdata`.
|
||||||
|
|
||||||
1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory
|
1. initialize the geth node with the `genesis.json` file
|
||||||
2. import the `halfchain.rlp` file in the `testdata` directory
|
2. import blocks from `chain.rlp`
|
||||||
3. run geth with the following flags:
|
3. run the client using the resulting database. For geth, use a command like the one below:
|
||||||
```
|
|
||||||
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, run the following command, replacing `<enode>` with the enode of the geth node:
|
geth \
|
||||||
```
|
--datadir <datadir> \
|
||||||
devp2p rlpx eth-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
|
--nodiscover \
|
||||||
```
|
--nat=none \
|
||||||
|
--networkid 3503995874084926 \
|
||||||
|
--verbosity 5 \
|
||||||
|
--authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
|
||||||
|
|
||||||
|
Note that the tests also require access to the engine API.
|
||||||
|
The test suite can now be executed using the devp2p tool.
|
||||||
|
|
||||||
|
devp2p rlpx eth-test \
|
||||||
|
--chain internal/ethtest/testdata \
|
||||||
|
--node enode://.... \
|
||||||
|
--engineapi http://127.0.0.1:8551 \
|
||||||
|
--jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
|
||||||
|
|
||||||
Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again.
|
Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again.
|
||||||
|
|
||||||
#### Eth66 Test Suite
|
|
||||||
|
|
||||||
The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically.
|
|
||||||
To run the eth66 protocol test suite, initialize a geth node as described above and run the following command,
|
|
||||||
replacing `<enode>` with the enode of the geth node:
|
|
||||||
|
|
||||||
```
|
|
||||||
devp2p rlpx eth66-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
|
|
||||||
```
|
|
||||||
|
|
||||||
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
|
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
|
||||||
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup
|
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -51,7 +52,14 @@ type resolver interface {
|
|||||||
RequestENR(*enode.Node) (*enode.Node, error)
|
RequestENR(*enode.Node) (*enode.Node, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler {
|
func newCrawler(input nodeSet, bootnodes []*enode.Node, disc resolver, iters ...enode.Iterator) (*crawler, error) {
|
||||||
|
if len(input) == 0 {
|
||||||
|
input.add(bootnodes...)
|
||||||
|
}
|
||||||
|
if len(input) == 0 {
|
||||||
|
return nil, errors.New("no input nodes to start crawling")
|
||||||
|
}
|
||||||
|
|
||||||
c := &crawler{
|
c := &crawler{
|
||||||
input: input,
|
input: input,
|
||||||
output: make(nodeSet, len(input)),
|
output: make(nodeSet, len(input)),
|
||||||
@ -67,7 +75,7 @@ func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler
|
|||||||
for id, n := range input {
|
for id, n := range input {
|
||||||
c.output[id] = n
|
c.output[id] = n
|
||||||
}
|
}
|
||||||
return c
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet {
|
func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet {
|
||||||
@ -87,11 +95,11 @@ func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet {
|
|||||||
go c.runIterator(doneCh, it)
|
go c.runIterator(doneCh, it)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
added uint64
|
added atomic.Uint64
|
||||||
updated uint64
|
updated atomic.Uint64
|
||||||
skipped uint64
|
skipped atomic.Uint64
|
||||||
recent uint64
|
recent atomic.Uint64
|
||||||
removed uint64
|
removed atomic.Uint64
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
)
|
)
|
||||||
wg.Add(nthreads)
|
wg.Add(nthreads)
|
||||||
@ -103,15 +111,15 @@ func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet {
|
|||||||
case n := <-c.ch:
|
case n := <-c.ch:
|
||||||
switch c.updateNode(n) {
|
switch c.updateNode(n) {
|
||||||
case nodeSkipIncompat:
|
case nodeSkipIncompat:
|
||||||
atomic.AddUint64(&skipped, 1)
|
skipped.Add(1)
|
||||||
case nodeSkipRecent:
|
case nodeSkipRecent:
|
||||||
atomic.AddUint64(&recent, 1)
|
recent.Add(1)
|
||||||
case nodeRemoved:
|
case nodeRemoved:
|
||||||
atomic.AddUint64(&removed, 1)
|
removed.Add(1)
|
||||||
case nodeAdded:
|
case nodeAdded:
|
||||||
atomic.AddUint64(&added, 1)
|
added.Add(1)
|
||||||
default:
|
default:
|
||||||
atomic.AddUint64(&updated, 1)
|
updated.Add(1)
|
||||||
}
|
}
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
return
|
return
|
||||||
@ -138,11 +146,11 @@ loop:
|
|||||||
break loop
|
break loop
|
||||||
case <-statusTicker.C:
|
case <-statusTicker.C:
|
||||||
log.Info("Crawling in progress",
|
log.Info("Crawling in progress",
|
||||||
"added", atomic.LoadUint64(&added),
|
"added", added.Load(),
|
||||||
"updated", atomic.LoadUint64(&updated),
|
"updated", updated.Load(),
|
||||||
"removed", atomic.LoadUint64(&removed),
|
"removed", removed.Load(),
|
||||||
"ignored(recent)", atomic.LoadUint64(&recent),
|
"ignored(recent)", recent.Load(),
|
||||||
"ignored(incompatible)", atomic.LoadUint64(&skipped))
|
"ignored(incompatible)", skipped.Load())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -142,7 +143,7 @@ var discoveryNodeFlags = []cli.Flag{
|
|||||||
|
|
||||||
func discv4Ping(ctx *cli.Context) error {
|
func discv4Ping(ctx *cli.Context) error {
|
||||||
n := getNodeArg(ctx)
|
n := getNodeArg(ctx)
|
||||||
disc := startV4(ctx)
|
disc, _ := startV4(ctx)
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@ -155,7 +156,7 @@ func discv4Ping(ctx *cli.Context) error {
|
|||||||
|
|
||||||
func discv4RequestRecord(ctx *cli.Context) error {
|
func discv4RequestRecord(ctx *cli.Context) error {
|
||||||
n := getNodeArg(ctx)
|
n := getNodeArg(ctx)
|
||||||
disc := startV4(ctx)
|
disc, _ := startV4(ctx)
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
respN, err := disc.RequestENR(n)
|
respN, err := disc.RequestENR(n)
|
||||||
@ -168,7 +169,7 @@ func discv4RequestRecord(ctx *cli.Context) error {
|
|||||||
|
|
||||||
func discv4Resolve(ctx *cli.Context) error {
|
func discv4Resolve(ctx *cli.Context) error {
|
||||||
n := getNodeArg(ctx)
|
n := getNodeArg(ctx)
|
||||||
disc := startV4(ctx)
|
disc, _ := startV4(ctx)
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
fmt.Println(disc.Resolve(n).String())
|
fmt.Println(disc.Resolve(n).String())
|
||||||
@ -177,7 +178,7 @@ func discv4Resolve(ctx *cli.Context) error {
|
|||||||
|
|
||||||
func discv4ResolveJSON(ctx *cli.Context) error {
|
func discv4ResolveJSON(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 1 {
|
if ctx.NArg() < 1 {
|
||||||
return fmt.Errorf("need nodes file as argument")
|
return errors.New("need nodes file as argument")
|
||||||
}
|
}
|
||||||
nodesFile := ctx.Args().Get(0)
|
nodesFile := ctx.Args().Get(0)
|
||||||
inputSet := make(nodeSet)
|
inputSet := make(nodeSet)
|
||||||
@ -195,10 +196,13 @@ func discv4ResolveJSON(ctx *cli.Context) error {
|
|||||||
nodeargs = append(nodeargs, n)
|
nodeargs = append(nodeargs, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the crawler.
|
disc, config := startV4(ctx)
|
||||||
disc := startV4(ctx)
|
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
c := newCrawler(inputSet, disc, enode.IterNodes(nodeargs))
|
|
||||||
|
c, err := newCrawler(inputSet, config.Bootnodes, disc, enode.IterNodes(nodeargs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.revalidateInterval = 0
|
c.revalidateInterval = 0
|
||||||
output := c.run(0, 1)
|
output := c.run(0, 1)
|
||||||
writeNodesJSON(nodesFile, output)
|
writeNodesJSON(nodesFile, output)
|
||||||
@ -207,17 +211,21 @@ func discv4ResolveJSON(ctx *cli.Context) error {
|
|||||||
|
|
||||||
func discv4Crawl(ctx *cli.Context) error {
|
func discv4Crawl(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 1 {
|
if ctx.NArg() < 1 {
|
||||||
return fmt.Errorf("need nodes file as argument")
|
return errors.New("need nodes file as argument")
|
||||||
}
|
}
|
||||||
nodesFile := ctx.Args().First()
|
nodesFile := ctx.Args().First()
|
||||||
var inputSet nodeSet
|
inputSet := make(nodeSet)
|
||||||
if common.FileExist(nodesFile) {
|
if common.FileExist(nodesFile) {
|
||||||
inputSet = loadNodesJSON(nodesFile)
|
inputSet = loadNodesJSON(nodesFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
disc := startV4(ctx)
|
disc, config := startV4(ctx)
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
c := newCrawler(inputSet, disc, disc.RandomNodes())
|
|
||||||
|
c, err := newCrawler(inputSet, config.Bootnodes, disc, disc.RandomNodes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.revalidateInterval = 10 * time.Minute
|
c.revalidateInterval = 10 * time.Minute
|
||||||
output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name))
|
output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name))
|
||||||
writeNodesJSON(nodesFile, output)
|
writeNodesJSON(nodesFile, output)
|
||||||
@ -228,7 +236,7 @@ func discv4Crawl(ctx *cli.Context) error {
|
|||||||
func discv4Test(ctx *cli.Context) error {
|
func discv4Test(ctx *cli.Context) error {
|
||||||
// Configure test package globals.
|
// Configure test package globals.
|
||||||
if !ctx.IsSet(remoteEnodeFlag.Name) {
|
if !ctx.IsSet(remoteEnodeFlag.Name) {
|
||||||
return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name)
|
return fmt.Errorf("missing -%v", remoteEnodeFlag.Name)
|
||||||
}
|
}
|
||||||
v4test.Remote = ctx.String(remoteEnodeFlag.Name)
|
v4test.Remote = ctx.String(remoteEnodeFlag.Name)
|
||||||
v4test.Listen1 = ctx.String(testListen1Flag.Name)
|
v4test.Listen1 = ctx.String(testListen1Flag.Name)
|
||||||
@ -237,14 +245,14 @@ func discv4Test(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startV4 starts an ephemeral discovery V4 node.
|
// startV4 starts an ephemeral discovery V4 node.
|
||||||
func startV4(ctx *cli.Context) *discover.UDPv4 {
|
func startV4(ctx *cli.Context) (*discover.UDPv4, discover.Config) {
|
||||||
ln, config := makeDiscoveryConfig(ctx)
|
ln, config := makeDiscoveryConfig(ctx)
|
||||||
socket := listen(ctx, ln)
|
socket := listen(ctx, ln)
|
||||||
disc, err := discover.ListenV4(socket, ln, config)
|
disc, err := discover.ListenV4(socket, ln, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
return disc
|
return disc, config
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) {
|
func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) {
|
||||||
@ -336,7 +344,7 @@ func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
||||||
s := params.RinkebyBootnodes
|
s := params.MainnetBootnodes
|
||||||
if ctx.IsSet(bootnodesFlag.Name) {
|
if ctx.IsSet(bootnodesFlag.Name) {
|
||||||
input := ctx.String(bootnodesFlag.Name)
|
input := ctx.String(bootnodesFlag.Name)
|
||||||
if input == "" {
|
if input == "" {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user