Merge remote-tracking branch 'upstream/develop' into statediff

This commit is contained in:
Roy Crihfield 2024-03-28 17:07:00 +08:00
commit 4ad641831b
1104 changed files with 85670 additions and 73923 deletions

View File

@ -6,7 +6,7 @@ version: 2.1
jobs:
test:
docker:
- image: cimg/go:1.19.4
- image: cimg/go:1.21.0
steps:
- checkout
- run:
@ -38,7 +38,7 @@ jobs:
command: go test ./core/rawdb/
build_geth_push:
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
steps:
- checkout

23
.github/workflows/go.yml vendored Normal file
View 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
View File

@ -48,3 +48,5 @@ profile.cov
**/yarn-error.log
logs/
tests/spec-tests/

View File

@ -12,7 +12,6 @@ run:
linters:
disable-all: true
enable:
- goconst
- goimports
- gosimple
- govet
@ -39,9 +38,6 @@ linters:
linters-settings:
gofmt:
simplify: true
goconst:
min-len: 3 # minimum length of string constant
min-occurrences: 6 # minimum number of occurrences
issues:
exclude-rules:

View File

@ -9,18 +9,6 @@ jobs:
- azure-osx
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
# will attempt to push the multi-arch image if they are the last builder
- stage: build
@ -28,7 +16,7 @@ jobs:
os: linux
arch: amd64
dist: bionic
go: 1.20.x
go: 1.21.x
env:
- docker
services:
@ -45,7 +33,7 @@ jobs:
os: linux
arch: arm64
dist: bionic
go: 1.20.x
go: 1.21.x
env:
- docker
services:
@ -63,10 +51,9 @@ jobs:
os: linux
dist: bionic
sudo: required
go: 1.20.x
go: 1.21.x
env:
- azure-linux
- GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
addons:
@ -97,56 +84,51 @@ jobs:
- stage: build
if: type = push
os: osx
go: 1.20.x
osx_image: xcode14.2
go: 1.21.x
env:
- azure-osx
- GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
script:
- 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 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
- stage: build
os: linux
arch: amd64
dist: bionic
go: 1.20.x
env:
- GO111MODULE=on
go: 1.21.x
script:
- go run build/ci.go test $TEST_PACKAGES
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- stage: build
if: type = pull_request
os: linux
arch: arm64
dist: bionic
go: 1.19.x
env:
- GO111MODULE=on
go: 1.20.x
script:
- go run build/ci.go test $TEST_PACKAGES
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- stage: build
os: linux
dist: bionic
go: 1.19.x
env:
- GO111MODULE=on
go: 1.20.x
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
- stage: build
if: type = cron || (type = push && tag ~= /^v[0-9]/)
os: linux
dist: bionic
go: 1.20.x
go: 1.21.x
env:
- ubuntu-ppa
- GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
addons:
@ -167,10 +149,9 @@ jobs:
if: type = cron
os: linux
dist: bionic
go: 1.20.x
go: 1.21.x
env:
- azure-purge
- GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
script:
@ -181,9 +162,7 @@ jobs:
if: type = cron
os: linux
dist: bionic
go: 1.20.x
env:
- GO111MODULE=on
go: 1.21.x
script:
- go run build/ci.go test -race $TEST_PACKAGES
- travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES

View File

@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM=""
# 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

View File

@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM=""
# 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

View File

@ -6,29 +6,35 @@
GOBIN = ./build/bin
GO ?= latest
GORUN = env GO111MODULE=on go run
GORUN = go run
#? geth: Build geth
geth:
$(GORUN) build/ci.go install ./cmd/geth
@echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth."
#? all: Build all packages and executables
all:
$(GORUN) build/ci.go install
#? test: Run the tests
test: all
$(GORUN) build/ci.go test
#? lint: Run certain pre-selected linters
lint: ## Run linters.
$(GORUN) build/ci.go lint
#? clean: Clean go cache, built executables, and the auto generated folder
clean:
env GO111MODULE=on go clean -cache
go clean -cache
rm -fr build/_workspace/pkg/ $(GOBIN)/*
# The devtools target installs tools required for 'go generate'.
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
#? devtools: Install recommended developer tools
devtools:
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
env GOBIN= go install github.com/fjl/gencodec@latest
@ -36,3 +42,9 @@ devtools:
env GOBIN= go install ./cmd/abigen
@type "solc" 2> /dev/null || echo 'Please install solc'
@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

View File

@ -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
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
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.

View File

@ -22,13 +22,14 @@ import (
"errors"
"fmt"
"io"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// 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.
type ABI struct {
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())
}
// 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.
func (abi *ABI) HasFallback() bool {
return abi.Fallback.Type == Fallback
@ -235,17 +247,37 @@ func (abi *ABI) HasReceive() bool {
// revertSelector is a special function selector for revert reason unpacking.
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
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
// the provided revert reason is abi-encoded as if it were a call to a function
// `Error(string)`. So it's a special tool for it.
// the provided revert reason is abi-encoded as if it were a call to function
// `Error(string)` or `Panic(uint256)`. So it's a special tool for it.
func UnpackRevert(data []byte) (string, error) {
if len(data) < 4 {
return "", errors.New("invalid data for unpacking")
}
if !bytes.Equal(data[:4], revertSelector) {
return "", errors.New("invalid data for unpacking")
}
switch {
case bytes.Equal(data[:4], revertSelector):
typ, err := NewType("string", "", nil)
if err != nil {
return "", err
@ -255,4 +287,25 @@ func UnpackRevert(data []byte) (string, error) {
return "", err
}
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")
}
}

View File

@ -120,6 +120,7 @@ var methods = map[string]Method{
}
func TestReader(t *testing.T) {
t.Parallel()
abi := ABI{
Methods: methods,
}
@ -151,6 +152,7 @@ func TestReader(t *testing.T) {
}
func TestInvalidABI(t *testing.T) {
t.Parallel()
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
_, err := JSON(strings.NewReader(json))
if err == nil {
@ -170,6 +172,7 @@ func TestInvalidABI(t *testing.T) {
// constructor(uint256 a, uint256 b) public{}
// }
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"}]`
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
// Test from JSON
@ -199,6 +202,7 @@ func TestConstructor(t *testing.T) {
}
func TestTestNumbers(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata))
if err != nil {
t.Fatal(err)
@ -236,6 +240,7 @@ func TestTestNumbers(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)
exp := "foo(string,string)"
if m.Sig != exp {
@ -274,6 +279,7 @@ func TestMethodSignature(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"}]`
abi, err := JSON(strings.NewReader(json))
if err != nil {
@ -297,6 +303,7 @@ func TestOverloadedMethodSignature(t *testing.T) {
}
func TestCustomErrors(t *testing.T) {
t.Parallel()
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
abi, err := JSON(strings.NewReader(json))
if err != nil {
@ -311,6 +318,7 @@ func TestCustomErrors(t *testing.T) {
}
func TestMultiPack(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata))
if err != nil {
t.Fatal(err)
@ -348,6 +356,7 @@ func ExampleJSON() {
}
func TestInputVariableInputLength(t *testing.T) {
t.Parallel()
const definition = `[
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
{ "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) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata))
if err != nil {
t.Error(err)
@ -650,6 +660,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
}
func TestDefaultFunctionParsing(t *testing.T) {
t.Parallel()
const definition = `[{ "name" : "balance", "type" : "function" }]`
abi, err := JSON(strings.NewReader(definition))
@ -663,6 +674,7 @@ func TestDefaultFunctionParsing(t *testing.T) {
}
func TestBareEvents(t *testing.T) {
t.Parallel()
const definition = `[
{ "type" : "event", "name" : "balance" },
{ "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]}
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"}]`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
@ -777,6 +790,7 @@ func TestUnpackEvent(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"}]`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
@ -827,6 +841,7 @@ func TestUnpackEventIntoMap(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"}]`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
@ -877,6 +892,7 @@ func TestUnpackMethodIntoMap(t *testing.T) {
}
func TestUnpackIntoMapNamingConflict(t *testing.T) {
t.Parallel()
// 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"}]`
abi, err := JSON(strings.NewReader(abiJSON))
@ -960,6 +976,7 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) {
}
func TestABI_MethodById(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata))
if err != nil {
t.Fatal(err)
@ -992,6 +1009,7 @@ func TestABI_MethodById(t *testing.T) {
}
func TestABI_EventById(t *testing.T) {
t.Parallel()
tests := []struct {
name 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
// conflict and that the second transfer method will be renamed transfer1.
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"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
@ -1089,6 +1137,7 @@ func TestDoubleDuplicateMethodNames(t *testing.T) {
// event send();
// }
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"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
@ -1116,6 +1165,7 @@ func TestDoubleDuplicateEventNames(t *testing.T) {
// event send(uint256, uint256);
// }
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"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
@ -1145,9 +1195,13 @@ func TestUnpackRevert(t *testing.T) {
{"", "", errors.New("invalid data for unpacking")},
{"08c379a1", "", errors.New("invalid data for unpacking")},
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
}
for index, c := range cases {
index, c := index, c
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
t.Parallel()
got, err := UnpackRevert(common.Hex2Bytes(c.input))
if c.expectErr != nil {
if err == nil {

View File

@ -20,17 +20,31 @@ import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
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 (
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
stateMut = []string{"", "pure", "view", "payable"}
stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]}
pays = []string{"", "true", "false"}
payables = []*string{&pays[0], &pays[1]}
stateMut = []string{"pure", "view", "payable"}
pays = []string{"true", "false"}
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
varNames = append(vNames, names...)
varTypes = []string{"bool", "address", "bytes", "string",
@ -47,7 +61,7 @@ var (
"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 {
_, err := abi.Pack(method, out...)
if err != nil {
@ -63,7 +77,7 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool)
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 {
outptr := reflect.New(reflect.TypeOf(input))
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
@ -79,12 +93,12 @@ func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
return false
}
type args struct {
type arg struct {
name 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)
if stateMutability != nil {
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
@ -111,60 +125,55 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab
sig += "} ]"
}
sig += `}]`
return abi.JSON(strings.NewReader(sig))
//fmt.Printf("sig: %s\n", sig)
return JSON(strings.NewReader(sig))
}
func runFuzzer(input []byte) int {
good := false
fuzzer := fuzz.NewFromGoFuzz(input)
name := names[getUInt(fuzzer)%len(names)]
stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)]
payable := payables[getUInt(fuzzer)%len(payables)]
maxLen := 5
for k := 1; k < maxLen; k++ {
var arg []args
for i := k; i > 0; i-- {
argName := varNames[i]
argTyp := varTypes[getUInt(fuzzer)%len(varTypes)]
if getUInt(fuzzer)%10 == 0 {
func fuzzAbi(input []byte) {
var (
fuzzer = fuzz.NewFromGoFuzz(input)
name = oneOf(fuzzer, names)
stateM = oneOfOrNil(fuzzer, stateMut)
payable = oneOfOrNil(fuzzer, pays)
arguments []arg
)
for i := 0; i < upTo(fuzzer, 10); i++ {
argName := oneOf(fuzzer, varNames)
argTyp := oneOf(fuzzer, varTypes)
switch upTo(fuzzer, 10) {
case 0: // 10% chance to make it a slice
argTyp += "[]"
} else if getUInt(fuzzer)%10 == 0 {
arrayArgs := getUInt(fuzzer)%30 + 1
argTyp += fmt.Sprintf("[%d]", arrayArgs)
case 1: // 10% chance to make it an array
argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30))
default:
}
arg = append(arg, args{
name: argName,
typ: argTyp,
})
arguments = append(arguments, arg{name: argName, typ: argTyp})
}
abi, err := createABI(name, stateM, payable, arg)
abi, err := createABI(name, stateM, payable, arguments)
if err != nil {
continue
//fmt.Printf("err: %v\n", err)
panic(err)
}
structs, b := unpackPack(abi, name, input)
c := packUnpack(abi, name, &structs)
good = good || b || c
}
if good {
return 1
}
return 0
structs, _ := unpackPack(abi, name, input)
_ = packUnpack(abi, name, &structs)
}
func Fuzz(input []byte) int {
return runFuzzer(input)
}
func getUInt(fuzzer *fuzz.Fuzzer) int {
func upTo(fuzzer *fuzz.Fuzzer, max int) int {
var i int
fuzzer.Fuzz(&i)
if i < 0 {
i = -i
if i < 0 {
return 0
return (-1 - i) % max
}
}
return i
return i % max
}
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
}

View File

@ -80,7 +80,7 @@ func (arguments Arguments) isTuple() bool {
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
if len(data) == 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
}
@ -95,7 +95,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte)
}
if len(data) == 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
}

View File

@ -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
// an decrypted key from a keystore.
// a decrypted key from a keystore.
//
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
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
// 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) {
if chainID == nil {
return nil, ErrNoChainID

View File

@ -29,13 +29,17 @@ import (
var (
// 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
// 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")
// ErrNoPendingState is raised when attempting to perform a pending state action
// on a backend that doesn't implement PendingContractCaller.
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
// an empty contract behind.
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
@ -64,11 +68,27 @@ type PendingContractCaller interface {
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
// 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
// to the transactor to decide.
type ContractTransactor interface {
ethereum.GasEstimator
ethereum.GasPricer
ethereum.GasPricer1559
ethereum.TransactionSender
// HeaderByNumber returns a block header from the current canonical chain. If
// number is nil, the latest known header is returned.
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(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.
@ -118,6 +106,12 @@ type DeployBackend interface {
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.
type ContractBackend interface {
ContractCaller

View File

@ -18,917 +18,35 @@ package backends
import (
"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/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/vm"
"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"
"github.com/ethereum/go-ethereum/ethclient/simulated"
)
// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend.
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
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
// SimulatedBackend is a simulated blockchain.
// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead.
type SimulatedBackend struct {
database ethdb.Database // In memory database to store our testing data
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
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
*simulated.Backend
simulated.Client
}
// NewSimulatedBackendWithDatabase creates a new binding backend based on the given database
// and uses a simulated blockchain for testing purposes.
// A simulated backend always uses chainID 1337.
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
// Fork sets the head to a new block, which is based on the provided parentHash.
func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error {
return b.Backend.Fork(parentHash)
}
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes.
//
// 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
// chain should be started. Transactions (old and new) can then be applied on
// top and Commit-ed.
//
// Note, the side-chain will only become canonical (and trigger the events) when
// it becomes longer. Until then CallContract will still operate on the current
// canonical chain.
//
// 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()),
// Deprecated: please use simulated.Backend from package
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
func NewSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit))
return &SimulatedBackend{
Backend: b,
Client: b.Client(),
}
}
// 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

View File

@ -48,6 +48,7 @@ type CallOpts struct {
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
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)
}
@ -189,6 +190,23 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
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 {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
if err != nil {
@ -220,7 +238,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
if err != nil {
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
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.
// It's usually used to initiate transactions for invoking **Fallback** function.
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
return c.transact(opts, &c.address, calldata)
}

View File

@ -114,7 +114,28 @@ func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ether
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) {
t.Parallel()
mc := &mockPendingCaller{
mockCaller: &mockCaller{
codeAtBytes: []byte{1, 2, 3},
@ -166,6 +187,7 @@ func TestPassingBlockNumber(t *testing.T) {
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
t.Parallel()
hash := crypto.Keccak256Hash([]byte("testName"))
topics := []common.Hash{
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
@ -187,6 +209,7 @@ func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
}
func TestUnpackAnonymousLogIntoMap(t *testing.T) {
t.Parallel()
mockLog := newMockLog(nil, common.HexToHash("0x0"))
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) {
t.Parallel()
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
if err != nil {
t.Fatal(err)
@ -229,6 +253,7 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
}
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
t.Parallel()
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
if err != nil {
t.Fatal(err)
@ -254,6 +279,7 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
}
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
t.Parallel()
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
addrBytes := mockAddress.Bytes()
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
@ -280,6 +306,7 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
}
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
t.Parallel()
bytes := []byte{1, 2, 3, 4, 5}
hash := crypto.Keccak256Hash(bytes)
topics := []common.Hash{
@ -302,6 +329,7 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
}
func TestTransactGasFee(t *testing.T) {
t.Parallel()
assert := assert.New(t)
// GasTipCap and GasFeeCap
@ -377,6 +405,7 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
}
func TestCall(t *testing.T) {
t.Parallel()
var method, methodWithArg = "something", "somethingArrrrg"
tests := []struct {
name, method string
@ -400,6 +429,15 @@ func TestCall(t *testing.T) {
Pending: true,
},
method: method,
}, {
name: "ok hash",
mc: &mockBlockHashCaller{
codeAtHashBytes: []byte{0},
},
opts: &bind.CallOpts{
BlockHash: common.Hash{0xaa},
},
method: method,
}, {
name: "pack error, no method",
mc: new(mockCaller),
@ -413,6 +451,14 @@ func TestCall(t *testing.T) {
},
method: method,
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",
mc: &mockPendingCaller{
@ -460,6 +506,34 @@ func TestCall(t *testing.T) {
mc: new(mockCaller),
method: method,
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",
mc: &mockCaller{
@ -507,6 +581,7 @@ func TestCall(t *testing.T) {
// TestCrashers contains some strings which previously caused the abi codec to crash.
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":"&"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))

View File

@ -79,7 +79,7 @@ func isKeyWord(arg string) bool {
// 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
// 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.
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 (
@ -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
normalized := original
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
// Ensure there is no duplicated identifier
var identifiers = callIdentifiers
if !original.IsConstant() {
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] {
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
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] {
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
// 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.
if bound == "string" || bound == "[]byte" {
bound = "common.Hash"

View File

@ -289,7 +289,7 @@ var bindTests = []struct {
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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"
`,
`
@ -297,7 +297,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// Deploy an interaction tester contract and call a transaction on it
@ -305,6 +305,7 @@ var bindTests = []struct {
if err != nil {
t.Fatalf("Failed to deploy interactor contract: %v", err)
}
sim.Commit()
if _, err := interactor.Transact(auth, "Transact string"); err != nil {
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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -352,7 +353,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -398,7 +399,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"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"
`,
`
@ -456,7 +457,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -504,7 +505,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// Deploy a default method invoker contract and execute its default method
@ -512,6 +513,7 @@ var bindTests = []struct {
if err != nil {
t.Fatalf("Failed to deploy defaulter contract: %v", err)
}
sim.Commit()
if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil {
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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -570,7 +572,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"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
sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000))
defer sim.Close()
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/backends"
"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
sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000))
defer sim.Close()
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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -702,7 +704,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"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"
`,
`
@ -752,7 +754,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -827,7 +829,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"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"
`,
`
@ -921,7 +923,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -1111,7 +1113,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
//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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
@ -1246,7 +1248,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
_, _, 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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -1388,7 +1390,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
//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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
// Initialize test accounts
key, _ := crypto.GenerateKey()
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()
// 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/backends"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
`,
`
// Initialize test accounts
@ -1543,7 +1545,7 @@ var bindTests = []struct {
addr := crypto.PubkeyToAddress(key.PublicKey)
// 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()
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/backends"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
`,
`
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
// 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()
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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
@ -1667,7 +1669,7 @@ var bindTests = []struct {
key, _ := crypto.GenerateKey()
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()
// Deploy a tester contract and execute a structured call on it
@ -1677,7 +1679,7 @@ var bindTests = []struct {
}
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 {
t.Fatalf("Failed to call anonymous field retriever: %v", err)
} 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/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
`,
`
key, _ := crypto.GenerateKey()
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()
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/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/eth/ethconfig"
`,
@ -1816,7 +1818,7 @@ var bindTests = []struct {
var (
key, _ = crypto.GenerateKey()
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()
@ -1874,11 +1876,12 @@ var bindTests = []struct {
[]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"}]`},
`
"context"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/eth/ethconfig"
`,
@ -1886,7 +1889,7 @@ var bindTests = []struct {
var (
key, _ = crypto.GenerateKey()
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()
@ -1895,7 +1898,7 @@ var bindTests = []struct {
t.Fatal(err)
}
sim.Commit()
_, err = bind.WaitDeployed(nil, sim, tx)
_, err = bind.WaitDeployed(context.Background(), sim, tx)
if err != nil {
t.Error(err)
}
@ -1926,11 +1929,12 @@ var bindTests = []struct {
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
imports: `
"context"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/eth/ethconfig"
`,
@ -1938,7 +1942,7 @@ var bindTests = []struct {
var (
key, _ = crypto.GenerateKey()
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()
@ -1948,7 +1952,7 @@ var bindTests = []struct {
}
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.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"},
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: `
"context"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/eth/ethconfig"
`,
@ -1986,7 +1991,7 @@ var bindTests = []struct {
var (
key, _ = crypto.GenerateKey()
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()
@ -1996,7 +2001,7 @@ var bindTests = []struct {
}
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.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"},
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
imports: `
"context"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/eth/ethconfig"
`,
@ -2026,7 +2032,7 @@ var bindTests = []struct {
var (
key, _ = crypto.GenerateKey()
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)
if err != nil {
@ -2034,16 +2040,40 @@ var bindTests = []struct {
}
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)
}
`,
}, {
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
// the requested tester run against it.
func TestGolangBindings(t *testing.T) {
t.Parallel()
// Skip the test if no Go command can be found
gocmd := runtime.GOROOT() + "/bin/go"
if !common.FileExist(gocmd) {

View File

@ -24,11 +24,11 @@ import (
"time"
"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/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient/simulated"
"github.com/ethereum/go-ethereum/params"
)
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -53,21 +53,21 @@ var waitDeployedTests = map[string]struct {
}
func TestWaitDeployed(t *testing.T) {
t.Parallel()
for name, test := range waitDeployedTests {
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
backend := simulated.NewBackend(
types.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
10000000,
)
defer backend.Close()
// Create the transaction
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough
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.SignTx(tx, types.HomesteadSigner{}, testKey)
tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey)
// Wait for it to get mined in the background.
var (
@ -77,12 +77,12 @@ func TestWaitDeployed(t *testing.T) {
ctx = context.Background()
)
go func() {
address, err = bind.WaitDeployed(ctx, backend, tx)
address, err = bind.WaitDeployed(ctx, backend.Client(), tx)
close(mined)
}()
// Send and mine the transaction.
backend.SendTransaction(ctx, tx)
backend.Client().SendTransaction(ctx, tx)
backend.Commit()
select {
@ -100,41 +100,40 @@ func TestWaitDeployed(t *testing.T) {
}
func TestWaitDeployedCornerCases(t *testing.T) {
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
backend := simulated.NewBackend(
types.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
10000000,
)
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))
// Create a transaction to an account.
code := "6060604052600a8060106000396000f360606040526008565b00"
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())
defer cancel()
backend.SendTransaction(ctx, tx)
backend.Client().SendTransaction(ctx, tx)
backend.Commit()
notContentCreation := errors.New("tx is not contract creation")
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() {
t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err)
notContractCreation := errors.New("tx is not contract creation")
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() {
t.Errorf("error mismatch: want %q, got %q, ", notContractCreation, err)
}
// Create a transaction that is not mined.
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() {
contextCanceled := errors.New("context canceled")
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() {
t.Errorf("error missmatch: want %q, got %q, ", contextCanceled, err)
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != contextCanceled.Error() {
t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err)
}
}()
backend.SendTransaction(ctx, tx)
backend.Client().SendTransaction(ctx, tx)
cancel()
}

View File

@ -18,7 +18,6 @@ package abi
import (
"bytes"
"errors"
"fmt"
"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
}
func (e *Error) Unpack(data []byte) (interface{}, error) {
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]) {
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:])
}

View File

@ -81,6 +81,7 @@ var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
func TestEventId(t *testing.T) {
t.Parallel()
var table = []struct {
definition string
expectations map[string]common.Hash
@ -112,6 +113,7 @@ func TestEventId(t *testing.T) {
}
func TestEventString(t *testing.T) {
t.Parallel()
var table = []struct {
definition 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.
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"}]}]`
abi, err := JSON(strings.NewReader(definition))
require.NoError(t, err)
@ -161,6 +164,7 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) {
}
func TestEventTupleUnpack(t *testing.T) {
t.Parallel()
type EventTransfer struct {
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.
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"}]}]`
type testStruct struct {
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.
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"}]}]`
type testStruct struct {
Value1 [2]uint8 // indexed

View File

@ -117,24 +117,23 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
id = crypto.Keccak256([]byte(sig))[:4]
}
// Extract meaningful state mutability of solidity method.
// If it's default value, never print it.
state := mutability
if state == "nonpayable" {
state = ""
}
if state != "" {
state = state + " "
}
identity := fmt.Sprintf("function %v", rawName)
if funType == Fallback {
switch funType {
case Fallback:
identity = "fallback"
} else if funType == Receive {
case Receive:
identity = "receive"
} else if funType == Constructor {
case 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{
Name: name,

View File

@ -35,6 +35,7 @@ const methoddata = `
]`
func TestMethodString(t *testing.T) {
t.Parallel()
var table = []struct {
method string
expectation string
@ -84,11 +85,12 @@ func TestMethodString(t *testing.T) {
for _, test := range table {
var got string
if test.method == "fallback" {
switch test.method {
case "fallback":
got = abi.Fallback.String()
} else if test.method == "receive" {
case "receive":
got = abi.Receive.String()
} else {
default:
got = abi.Methods[test.method].String()
}
if got != test.expectation {
@ -98,6 +100,7 @@ func TestMethodString(t *testing.T) {
}
func TestMethodSig(t *testing.T) {
t.Parallel()
var cases = []struct {
method string
expect string

View File

@ -57,7 +57,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
reflectValue = mustArrayToByteSlice(reflectValue)
}
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
case FixedBytesTy, FunctionTy:
@ -66,7 +66,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
}
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
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)
}
}

View File

@ -32,8 +32,11 @@ import (
// TestPack tests the general pack/unpack tests in packing_test.go
func TestPack(t *testing.T) {
t.Parallel()
for i, test := range packUnpackTests {
i, test := i, test
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
encb, err := hex.DecodeString(test.packed)
if err != nil {
t.Fatalf("invalid hex %s: %v", test.packed, err)
@ -57,6 +60,7 @@ func TestPack(t *testing.T) {
}
func TestMethodPack(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata))
if err != nil {
t.Fatal(err)
@ -177,6 +181,7 @@ func TestMethodPack(t *testing.T) {
}
func TestPackNumber(t *testing.T) {
t.Parallel()
tests := []struct {
value reflect.Value
packed []byte

View File

@ -134,7 +134,7 @@ func setSlice(dst, src reflect.Value) error {
dst.Set(slice)
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 {
@ -155,7 +155,7 @@ func setArray(dst, src reflect.Value) error {
dst.Set(array)
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 {
@ -163,7 +163,7 @@ func setStruct(dst, src reflect.Value) error {
srcField := src.Field(i)
dstField := dst.Field(i)
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 {
return err
@ -228,7 +228,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
structFieldName := ToCamelCase(argName)
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

View File

@ -170,8 +170,11 @@ var reflectTests = []reflectTest{
}
func TestReflectNameToStruct(t *testing.T) {
t.Parallel()
for _, test := range reflectTests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
if len(test.err) > 0 {
if err == nil || err.Error() != test.err {
@ -192,6 +195,7 @@ func TestReflectNameToStruct(t *testing.T) {
}
func TestConvertType(t *testing.T) {
t.Parallel()
// Test Basic Struct
type T struct {
X *big.Int

View File

@ -17,6 +17,7 @@
package abi
import (
"errors"
"fmt"
)
@ -40,7 +41,7 @@ func isIdentifierSymbol(c byte) bool {
func parseToken(unescapedSelector string, isIdent bool) (string, string, error) {
if len(unescapedSelector) == 0 {
return "", "", fmt.Errorf("empty token")
return "", "", errors.New("empty token")
}
firstChar := unescapedSelector[0]
position := 1
@ -110,7 +111,7 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error)
func parseType(unescapedSelector string) (interface{}, string, error) {
if len(unescapedSelector) == 0 {
return nil, "", fmt.Errorf("empty type")
return nil, "", errors.New("empty type")
}
if unescapedSelector[0] == '(' {
return parseCompositeType(unescapedSelector)

View File

@ -24,6 +24,7 @@ import (
)
func TestParseSelector(t *testing.T) {
t.Parallel()
mkType := func(types ...interface{}) []ArgumentMarshaling {
var result []ArgumentMarshaling
for i, typeOrComponents := range types {

View File

@ -24,6 +24,7 @@ import (
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
)
@ -41,8 +42,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
copy(topic[:], math.U256Bytes(rule))
case bool:
if rule {
topic[common.HashLength-1] = 1
@ -75,7 +75,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
copy(topic[:], hash[:])
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
// stored directly but instead a keccak256-hash of an encoding is stored.
//

View File

@ -17,6 +17,7 @@
package abi
import (
"math"
"math/big"
"reflect"
"testing"
@ -26,6 +27,7 @@ import (
)
func TestMakeTopics(t *testing.T) {
t.Parallel()
type args struct {
query [][]interface{}
}
@ -54,9 +56,27 @@ func TestMakeTopics(t *testing.T) {
false,
},
{
"support *big.Int types in topics",
args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}},
[][]common.Hash{{common.Hash{128}}},
"support positive *big.Int types in topics",
args{[][]interface{}{
{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,
},
{
@ -117,7 +137,9 @@ func TestMakeTopics(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := MakeTopics(tt.args.query...)
if (err != nil) != tt.wantErr {
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
@ -347,10 +369,13 @@ func setupTopicsTests() []topicTest {
}
func TestParseTopics(t *testing.T) {
t.Parallel()
tests := setupTopicsTests()
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
createObj := tt.args.createObj()
if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != 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) {
t.Parallel()
tests := setupTopicsTests()
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
outMap := make(map[string]interface{})
if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)

View File

@ -70,7 +70,7 @@ var (
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
// check that array brackets are equal if they exist
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
@ -109,7 +109,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
}
typ.stringKind = embeddedType.stringKind + sliced
} else {
return Type{}, fmt.Errorf("invalid formatting of array type")
return Type{}, errors.New("invalid formatting of array type")
}
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.
func (t Type) requiresLengthPrefix() bool {
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy

View File

@ -31,6 +31,7 @@ type typeWithoutStringer Type
// Tests that all allowed types get recognized by the type parser.
func TestTypeRegexp(t *testing.T) {
t.Parallel()
tests := []struct {
blob string
components []ArgumentMarshaling
@ -117,6 +118,7 @@ func TestTypeRegexp(t *testing.T) {
}
func TestTypeCheck(t *testing.T) {
t.Parallel()
for i, test := range []struct {
typ string
components []ArgumentMarshaling
@ -308,6 +310,7 @@ func TestTypeCheck(t *testing.T) {
}
func TestInternalType(t *testing.T) {
t.Parallel()
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
internalType := "struct a.b[]"
kind := Type{
@ -332,6 +335,7 @@ func TestInternalType(t *testing.T) {
}
func TestGetTypeSize(t *testing.T) {
t.Parallel()
var testCases = []struct {
typ string
components []ArgumentMarshaling
@ -368,6 +372,7 @@ func TestGetTypeSize(t *testing.T) {
}
func TestNewFixedBytesOver32(t *testing.T) {
t.Parallel()
_, err := NewType("bytes4096", "", nil)
if err == nil {
t.Errorf("fixed bytes with size over 32 is not spec'd")

View File

@ -18,6 +18,7 @@ package abi
import (
"encoding/binary"
"errors"
"fmt"
"math"
"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)
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
if t.T != FunctionTy {
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
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 {
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.
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
if t.T != FixedBytesTy {
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
return nil, errors.New("abi: invalid type in call to make fixed byte array")
}
// convert
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
var refSlice reflect.Value
if t.T == SliceTy {
switch t.T {
case SliceTy:
// declare our slice
refSlice = reflect.MakeSlice(t.GetType(), size, size)
} else if t.T == ArrayTy {
case ArrayTy:
// declare our array
refSlice = reflect.New(t.GetType()).Elem()
} else {
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
default:
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
}
// Arrays have packed elements, resulting in longer unpack steps.

View File

@ -33,6 +33,7 @@ import (
// TestUnpack tests the general pack/unpack tests in packing_test.go
func TestUnpack(t *testing.T) {
t.Parallel()
for i, test := range packUnpackTests {
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
//Unpack
@ -206,13 +207,13 @@ var unpackTests = []unpackTest{
def: `[{"type":"bool"}]`,
enc: "",
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}]`,
enc: "",
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}]`,
@ -224,6 +225,7 @@ var unpackTests = []unpackTest{
// TestLocalUnpackTests runs test specially designed only for unpacking.
// All test cases that can be used to test packing and unpacking should move to packing_test.go
func TestLocalUnpackTests(t *testing.T) {
t.Parallel()
for i, test := range unpackTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
//Unpack
@ -251,6 +253,7 @@ func TestLocalUnpackTests(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"}]`))
if err != nil {
t.Fatal(err)
@ -321,6 +324,7 @@ func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOut
}
func TestMethodMultiReturn(t *testing.T) {
t.Parallel()
type reversed struct {
String string
Int *big.Int
@ -400,6 +404,7 @@ func TestMethodMultiReturn(t *testing.T) {
}
func TestMultiReturnWithArray(t *testing.T) {
t.Parallel()
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
@ -423,6 +428,7 @@ func TestMultiReturnWithArray(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"}]}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
@ -453,6 +459,7 @@ func TestMultiReturnWithStringArray(t *testing.T) {
}
func TestMultiReturnWithStringSlice(t *testing.T) {
t.Parallel()
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
@ -485,6 +492,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) {
}
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
t.Parallel()
// 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
// 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) {
t.Parallel()
const definition = `[
{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
@ -774,6 +783,7 @@ func TestUnmarshal(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"}]}]}]`
abi, err := JSON(strings.NewReader(simpleTuple))
if err != nil {
@ -876,6 +886,7 @@ func TestUnpackTuple(t *testing.T) {
}
func TestOOMMaliciousInput(t *testing.T) {
t.Parallel()
oomTests := []unpackTest{
{
def: `[{"type": "uint8[]"}]`,
@ -946,6 +957,7 @@ func TestOOMMaliciousInput(t *testing.T) {
}
func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
t.Parallel()
var encodeABI Arguments
uint256Ty, err := NewType("uint256", "", nil)
if err != nil {

View File

@ -24,6 +24,7 @@ import (
)
func TestTextHash(t *testing.T) {
t.Parallel()
hash := TextHash([]byte("Hello Joe"))
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
if !bytes.Equal(hash, want) {

View File

@ -17,6 +17,7 @@
package external
import (
"errors"
"fmt"
"math/big"
"sync"
@ -98,11 +99,11 @@ func (api *ExternalSigner) Status() (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 {
return fmt.Errorf("operation not supported on external signers")
return errors.New("operation not supported on external signers")
}
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) {
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) {
@ -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) {
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) {
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) {
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) {

View File

@ -25,6 +25,7 @@ import (
// Tests that HD derivation paths can be correctly parsed into our internal binary
// representation.
func TestHDPathParsing(t *testing.T) {
t.Parallel()
tests := []struct {
input string
output DerivationPath
@ -89,6 +90,7 @@ func testDerive(t *testing.T, next func() DerivationPath, expected []string) {
}
func TestHdPathIteration(t *testing.T) {
t.Parallel()
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
[]string{
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",

View File

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"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
@ -38,11 +39,10 @@ import (
// exist yet, the code will attempt to create a watcher at most this often.
const minReloadInterval = 2 * time.Second
type accountsByURL []accounts.Account
func (s accountsByURL) Len() int { return len(s) }
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] }
// byURL defines the sorting order for accounts.
func byURL(a, b accounts.Account) int {
return a.URL.Cmp(b.URL)
}
// AmbiguousAddrError is returned when attempting to unlock
// an address for which more than one file exists.
@ -67,7 +67,7 @@ type accountCache struct {
keydir string
watcher *watcher
mu sync.Mutex
all accountsByURL
all []accounts.Account
byAddr map[common.Address][]accounts.Account
throttle *time.Timer
notify chan struct{}
@ -194,7 +194,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
default:
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
copy(err.Matches, matches)
sort.Sort(accountsByURL(err.Matches))
slices.SortFunc(err.Matches, byURL)
return accounts.Account{}, err
}
}

View File

@ -17,12 +17,12 @@
package keystore
import (
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
"time"
@ -30,6 +30,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/slices"
)
var (
@ -67,14 +68,14 @@ func waitWatcherStart(ks *KeyStore) bool {
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
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()
if reflect.DeepEqual(list, wantAccounts) {
// ks should have also received change notifications
select {
case <-ks.changes:
default:
return fmt.Errorf("wasn't notified of new accounts")
return errors.New("wasn't notified of new accounts")
}
return nil
}
@ -151,6 +152,7 @@ func TestWatchNoDir(t *testing.T) {
}
func TestCacheInitialReload(t *testing.T) {
t.Parallel()
cache, _ := newAccountCache(cachetestDir)
accounts := cache.accounts()
if !reflect.DeepEqual(accounts, cachetestAccounts) {
@ -159,6 +161,7 @@ func TestCacheInitialReload(t *testing.T) {
}
func TestCacheAddDeleteOrder(t *testing.T) {
t.Parallel()
cache, _ := newAccountCache("testdata/no-such-dir")
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.
wantAccounts := make([]accounts.Account, len(accs))
copy(wantAccounts, accs)
sort.Sort(accountsByURL(wantAccounts))
slices.SortFunc(wantAccounts, byURL)
list := cache.accounts()
if !reflect.DeepEqual(list, 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) {
t.Parallel()
dir := filepath.Join("testdata", "dir")
cache, _ := newAccountCache(dir)
cache.watcher.running = true // prevent unexpected reloads
@ -349,7 +353,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
return
}
// 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
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
time.Sleep(time.Second)
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
// Now replace file contents again
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
time.Sleep(time.Second)
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
// Now replace file contents with crap
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {

View 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)
}
})
}

View File

@ -20,7 +20,6 @@ import (
"math/rand"
"os"
"runtime"
"sort"
"strings"
"sync"
"sync/atomic"
@ -31,11 +30,13 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"golang.org/x/exp/slices"
)
var testSigData = make([]byte, 32)
func TestKeyStore(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStore(t, true)
a, err := ks.NewAccount("foo")
@ -70,6 +71,7 @@ func TestKeyStore(t *testing.T) {
}
func TestSign(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
pass := "" // not used but required by API
@ -86,6 +88,7 @@ func TestSign(t *testing.T) {
}
func TestSignWithPassphrase(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
pass := "passwd"
@ -280,6 +283,7 @@ type walletEvent struct {
// Tests that wallet notifications and correctly fired when accounts are added
// or deleted from the keystore.
func TestWalletNotifications(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false)
// 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.
func TestImportECDSA(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
key, err := crypto.GenerateKey()
if err != nil {
@ -359,6 +364,7 @@ func TestImportECDSA(t *testing.T) {
// TestImportECDSA tests the import and export functionality of a keystore.
func TestImportExport(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old")
if err != nil {
@ -387,6 +393,7 @@ func TestImportExport(t *testing.T) {
// TestImportRace tests the keystore on races.
// This test should fail under -race if importing races.
func TestImportRace(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old")
if err != nil {
@ -397,19 +404,19 @@ func TestImportRace(t *testing.T) {
t.Fatalf("failed to export account: %v", acc)
}
_, ks2 := tmpKeyStore(t, true)
var atom uint32
var atom atomic.Uint32
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func() {
defer wg.Done()
if _, err := ks2.Import(json, "new", "new"); err != nil {
atomic.AddUint32(&atom, 1)
atom.Add(1)
}
}()
}
wg.Wait()
if atom != 1 {
if atom.Load() != 1 {
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 {
liveList = append(liveList, account)
}
sort.Sort(accountsByURL(liveList))
slices.SortFunc(liveList, byURL)
for j, wallet := range wallets {
if accs := wallet.Accounts(); len(accs) != 1 {
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))

View File

@ -136,7 +136,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
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) {
salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
@ -225,10 +225,13 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
if err != nil {
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)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid UUID: %w", err)
}
return &Key{
Id: id,

View File

@ -30,6 +30,7 @@ const (
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
func TestKeyEncryptDecrypt(t *testing.T) {
t.Parallel()
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
if err != nil {
t.Fatal(err)
@ -54,7 +55,7 @@ func TestKeyEncryptDecrypt(t *testing.T) {
// Recrypt with a new password and start over
password += "new data appended" // nolint: gosec
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)
}
}
}

View File

@ -40,6 +40,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
}
func TestKeyStorePlain(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStoreIface(t, false)
pass := "" // not used but required by API
@ -60,6 +61,7 @@ func TestKeyStorePlain(t *testing.T) {
}
func TestKeyStorePassphrase(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStoreIface(t, true)
pass := "foo"
@ -80,6 +82,7 @@ func TestKeyStorePassphrase(t *testing.T) {
}
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStoreIface(t, true)
pass := "foo"
@ -93,6 +96,7 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
}
func TestImportPreSaleKey(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStoreIface(t, true)
// file content of a presale key file generated with:

View File

@ -20,6 +20,7 @@
package keystore
import (
"os"
"time"
"github.com/ethereum/go-ethereum/log"
@ -77,7 +78,9 @@ func (w *watcher) loop() {
}
defer watcher.Close()
if err := watcher.Add(w.ac.keydir); err != nil {
if !os.IsNotExist(err) {
logger.Warn("Failed to watch keystore folder", "err", err)
}
return
}
@ -122,7 +125,7 @@ func (w *watcher) loop() {
if !ok {
return
}
log.Info("Filsystem watcher error", "err", err)
log.Info("Filesystem watcher error", "err", err)
case <-debounce.C:
w.ac.scanAccounts()
rescanTriggered = false

View File

@ -98,6 +98,9 @@ func NewManager(config *Config, backends ...Backend) *Manager {
// Close terminates the account manager's internal notification processes.
func (am *Manager) Close() error {
for _, w := range am.wallets {
w.Close()
}
errc := make(chan error)
am.quit <- errc
return <-errc

View File

@ -8,7 +8,7 @@
## 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)

View File

@ -241,7 +241,7 @@ func (hub *Hub) refreshWallets() {
card.Disconnect(pcsc.LeaveCard)
continue
}
// Card connected, start tracking in amongs the wallets
// Card connected, start tracking among the wallets
hub.wallets[reader] = wallet
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
}

View File

@ -24,6 +24,7 @@ import (
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/crypto"
@ -125,7 +126,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
// Unpair disestablishes an existing pairing.
func (s *SecureChannelSession) Unpair() error {
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{})
@ -141,7 +142,7 @@ func (s *SecureChannelSession) Unpair() error {
// Open initializes the secure channel.
func (s *SecureChannelSession) Open() error {
if s.iv != nil {
return fmt.Errorf("session already opened")
return errors.New("session already opened")
}
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.
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
if s.iv == nil {
return nil, fmt.Errorf("channel not open")
return nil, errors.New("channel not open")
}
data, err := s.encryptAPDU(data)
@ -254,7 +255,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b
return nil, err
}
if !bytes.Equal(s.iv, rmac) {
return nil, fmt.Errorf("invalid MAC in response")
return nil, errors.New("invalid MAC in response")
}
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 0")
return nil, errors.New("expected end of padding, got 0")
}
// updateIV is an internal method that updates the initialization vector after

View File

@ -252,7 +252,7 @@ func (w *Wallet) release() error {
// with the wallet.
func (w *Wallet) pair(puk []byte) error {
if w.session.paired() {
return fmt.Errorf("wallet already paired")
return errors.New("wallet already paired")
}
pairing, err := w.session.pair(puk)
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)
}
parts := strings.SplitN(account.URL.Path, "/", 2)
if len(parts) != 2 {
url, path, found := strings.Cut(account.URL.Path, "/")
if !found {
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 accounts.ParseDerivationPath(parts[1])
return accounts.ParseDerivationPath(path)
}
// 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.
func (s *Session) unpair() error {
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()
}
@ -907,7 +907,7 @@ func (s *Session) initialize(seed []byte) error {
return err
}
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()

View File

@ -21,6 +21,7 @@ import (
)
func TestURLParsing(t *testing.T) {
t.Parallel()
url, err := parseURL("https://ethereum.org")
if err != nil {
t.Errorf("unexpected error: %v", err)
@ -40,6 +41,7 @@ func TestURLParsing(t *testing.T) {
}
func TestURLString(t *testing.T) {
t.Parallel()
url := URL{Scheme: "https", Path: "ethereum.org"}
if url.String() != "https://ethereum.org" {
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) {
t.Parallel()
url := URL{Scheme: "https", Path: "ethereum.org"}
json, err := url.MarshalJSON()
if err != nil {
t.Errorf("unexpcted error: %v", err)
t.Errorf("unexpected error: %v", err)
}
if string(json) != "\"https://ethereum.org\"" {
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) {
t.Parallel()
url := &URL{}
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
if err != nil {
t.Errorf("unexpcted error: %v", err)
t.Errorf("unexpected error: %v", err)
}
if url.Scheme != "https" {
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
@ -77,6 +81,7 @@ func TestURLUnmarshalJSON(t *testing.T) {
}
func TestURLComparison(t *testing.T) {
t.Parallel()
tests := []struct {
urlA URL
urlB URL

View File

@ -65,7 +65,7 @@ type Hub struct {
// TODO(karalabe): remove if hotplug lands on Windows
commsPend int // Number of operations blocking 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.
@ -151,7 +151,7 @@ func (hub *Hub) refreshWallets() {
return
}
// If USB enumeration is continually failing, don't keep trying indefinitely
if atomic.LoadUint32(&hub.enumFails) > 2 {
if hub.enumFails.Load() > 2 {
return
}
// Retrieve the current list of USB wallet devices
@ -172,7 +172,7 @@ func (hub *Hub) refreshWallets() {
}
infos, err := usb.Enumerate(hub.vendorID, 0)
if err != nil {
failcount := atomic.AddUint32(&hub.enumFails, 1)
failcount := hub.enumFails.Add(1)
if runtime.GOOS == "linux" {
// See rationale before the enumeration why this is needed and only on Linux.
hub.commsLock.Unlock()
@ -181,7 +181,7 @@ func (hub *Hub) refreshWallets() {
"vendor", hub.vendorID, "failcount", failcount, "err", err)
return
}
atomic.StoreUint32(&hub.enumFails, 0)
hub.enumFails.Store(0)
for _, info := range infos {
for _, id := range hub.productIDs {

View File

@ -279,7 +279,7 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
}
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
if _, err = hex.Decode(address[:], hexstr); err != nil {
return common.Address{}, err

View File

@ -16,7 +16,7 @@
// This file contains the implementation for interacting with the Trezor hardware
// 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 !!!
//

View File

@ -483,6 +483,10 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
w.stateLock.Lock()
defer w.stateLock.Unlock()
if w.device == nil {
return accounts.Account{}, accounts.ErrWalletClosed
}
if _, ok := w.paths[address]; !ok {
w.accounts = append(w.accounts, account)
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
}
// 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
// an error.
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {

View File

@ -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 nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
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

View File

@ -80,6 +80,7 @@ var (
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

View File

@ -20,12 +20,14 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
}
var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp)
enc.Random = p.Random
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot
return json.Marshal(&enc)
}
@ -36,6 +38,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
Random *common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
}
var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil {
@ -56,5 +59,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.Withdrawals != nil {
p.Withdrawals = dec.Withdrawals
}
if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot
}
return nil
}

View File

@ -32,6 +32,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
}
var enc ExecutableData
enc.ParentHash = e.ParentHash
@ -54,6 +56,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
}
}
enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
return json.Marshal(&enc)
}
@ -75,6 +79,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
}
var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil {
@ -142,5 +148,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.Withdrawals != nil {
e.Withdrawals = dec.Withdrawals
}
if dec.BlobGasUsed != nil {
e.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
}
if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
}
return nil
}

View File

@ -17,10 +17,14 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Override bool `json:"shouldOverrideBuilder"`
}
var enc ExecutionPayloadEnvelope
enc.ExecutionPayload = e.ExecutionPayload
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
enc.BlobsBundle = e.BlobsBundle
enc.Override = e.Override
return json.Marshal(&enc)
}
@ -29,6 +33,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Override *bool `json:"shouldOverrideBuilder"`
}
var dec ExecutionPayloadEnvelope
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")
}
e.BlockValue = (*big.Int)(dec.BlockValue)
if dec.BlobsBundle != nil {
e.BlobsBundle = dec.BlobsBundle
}
if dec.Override != nil {
e.Override = *dec.Override
}
return nil
}

View File

@ -26,6 +26,16 @@ import (
"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
// PayloadAttributes describes the environment context in which a block should
@ -35,6 +45,7 @@ type PayloadAttributes struct {
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
}
// JSON type overrides for PayloadAttributes.
@ -61,6 +72,8 @@ type ExecutableData struct {
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
}
// JSON type overrides for executableData.
@ -73,6 +86,8 @@ type executableDataMarshaling struct {
ExtraData hexutil.Bytes
LogsBloom 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
@ -80,6 +95,14 @@ type executableDataMarshaling struct {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" 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.
@ -102,6 +125,21 @@ type TransitionConfigurationV1 struct {
// PayloadID is an identifier of the payload build process
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 {
return hexutil.Encode(b[:])
}
@ -155,11 +193,12 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// len(extraData) <= 32
// uncleHash = emptyUncleHash
// difficulty = 0
// if versionedHashes != nil, versionedHashes match to blob transactions
//
// and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty
// 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)
if err != nil {
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) {
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
// ExecutableData before withdrawals are enabled by marshaling
// Withdrawals as the json null value.
@ -199,6 +250,9 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
Extra: params.ExtraData,
MixDigest: params.Random,
WithdrawalsHash: withdrawalsRoot,
ExcessBlobGas: params.ExcessBlobGas,
BlobGasUsed: params.BlobGasUsed,
ParentBeaconRoot: beaconRoot,
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
if block.Hash() != params.BlockHash {
@ -209,7 +263,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
// BlockToExecutableData constructs the ExecutableData structure by filling the
// 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{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
@ -226,8 +280,22 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
Random: block.MixDigest(),
ExtraData: block.Extra(),
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
@ -235,3 +303,21 @@ type ExecutionPayloadBodyV1 struct {
TransactionData []hexutil.Bytes `json:"transactions"`
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
View 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
}

View 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
}

View 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
View 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)
}
}

View 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
View 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
}

View File

@ -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.
//
// 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
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package snapshot
package params
import (
"bytes"
const (
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.
type hashes []common.Hash
// Len is the number of elements in the collection.
func (hs hashes) Len() int { return len(hs) }
// Less reports whether the element with index i should sort before the element
// with index j.
func (hs hashes) Less(i, j int) bool { return bytes.Compare(hs[i][:], hs[j][:]) < 0 }
// Swap swaps the elements with indexes i and j.
func (hs hashes) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] }
const (
StateIndexGenesisTime = 32
StateIndexGenesisValidators = 33
StateIndexForkVersion = 141
StateIndexLatestHeader = 36
StateIndexBlockRoots = 37
StateIndexStateRoots = 38
StateIndexHistoricRoots = 39
StateIndexFinalBlock = 105
StateIndexSyncCommittee = 54
StateIndexNextSyncCommittee = 55
StateIndexExecPayload = 56
StateIndexExecHead = 908
)

191
beacon/types/committee.go Normal file
View 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
View 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
}

View 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
}

View 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
View 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
View 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
}

View File

@ -1,46 +1,65 @@
# This file contains sha256 checksums of optional build dependencies.
e447b498cde50215c4f7619e5124b0fc4e25fb5d16ea47271c47f278e7aa763a go1.20.3.src.tar.gz
c1e1161d6d859deb576e6cfabeb40e3d042ceb1c6f444f617c3c9d76269c3565 go1.20.3.darwin-amd64.tar.gz
86b0ed0f2b2df50fa8036eea875d1cf2d76cefdacf247c44639a1464b7e36b95 go1.20.3.darwin-arm64.tar.gz
340e80abd047c597fdc0f50a6cc59617f06c297d62f7fc77f4a0164e2da6f7aa go1.20.3.freebsd-386.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
# version:spec-tests 2.1.0
# https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
fba08acc4027f69f07cef48fbff70b8a7ecdfaa1c2aba9ad3fb31d60d9f5d4bc golangci-lint-1.51.1-darwin-amd64.tar.gz
75b8f0ff3a4e68147156be4161a49d4576f1be37a0b506473f8c482140c1e7f2 golangci-lint-1.51.1-darwin-arm64.tar.gz
e06b3459aaed356e1667580be00b05f41f3b2e29685d12cdee571c23e1edb414 golangci-lint-1.51.1-freebsd-386.tar.gz
623ce2d0fa4d35cc2e8d69fa7334227ab592380962a13b4d9cdc77cf41db2008 golangci-lint-1.51.1-freebsd-amd64.tar.gz
131365feb0584cc2736c43192fa673ca50e5b6b765456990cb379ecfb787e568 golangci-lint-1.51.1-freebsd-armv6.tar.gz
98fb627927cbb654f5bf85dcffc5f646666b2ce96ea0fed977c9fb28abd51532 golangci-lint-1.51.1-freebsd-armv7.tar.gz
b36a99702fa762c15840261bc0fb41b4b1b16b8b19b8c0941bae98c85bb0f8b8 golangci-lint-1.51.1-linux-386.tar.gz
17aeb26c76820c22efa0e1838b0ab93e90cfedef43fbfc9a2f33f27eb9e5e070 golangci-lint-1.51.1-linux-amd64.tar.gz
9744bc34e7b8d82ca788b667bfb7155a39b4be9aef43bf9f10318b1372cea338 golangci-lint-1.51.1-linux-arm64.tar.gz
0dda8dbeb2ff7455a044ec8e347f2fc6d655d2e99d281b3b95e88167031c673d golangci-lint-1.51.1-linux-armv6.tar.gz
0512f311b11d43b8b22989d929f0fe8a2e1e5ebe497f1eb0ff73a0fc3d188fd1 golangci-lint-1.51.1-linux-armv7.tar.gz
d767108dcf84a8eaa844df3454cb0f75a492f4e7102ecc2b0a3545cfe073a566 golangci-lint-1.51.1-linux-loong64.tar.gz
3bd56c54daec16585b2668e0dfabb27af2c2b38cc0fdb46923e2521e1634846b golangci-lint-1.51.1-linux-mips64.tar.gz
f72f5adfa2219e15d2414c9a2966f86e74556cf17a85c727a7fb7770a16cf814 golangci-lint-1.51.1-linux-mips64le.tar.gz
e605521dac98096d8737e1997c954f41f1d0d8275b8731f62783d410c23574b9 golangci-lint-1.51.1-linux-ppc64le.tar.gz
2f683217b814339e74d61ca700922d8407f15addd6d4c5e8b156fbab79f26a87 golangci-lint-1.51.1-linux-riscv64.tar.gz
d98528292b65971a3594e5880530e7624597dc9806fcfccdfbe39be411713d63 golangci-lint-1.51.1-linux-s390x.tar.gz
9bb2d0fe9e692ed0aea4f2537e3e6862b2f6768fe2849a84f4a6ad09da9fd971 golangci-lint-1.51.1-netbsd-386.tar.gz
34cafdcd11ae73ae88d66c33eb8449f5c976fc3e37b44774dbe9c71caa95e592 golangci-lint-1.51.1-netbsd-amd64.tar.gz
f8b4e1e47ac17caafe8a5f32f975a2b6a7cb14c27c0f73c1fb15c20ca91c2e03 golangci-lint-1.51.1-netbsd-armv6.tar.gz
c4f58b7e227b9fd41f0e9310dc83f4a4e7d026598e2f6e95b78761081a6d9bd2 golangci-lint-1.51.1-netbsd-armv7.tar.gz
6710e2f5375dc75521c1a17980a6cbbe6ff76c2f8b852964a8af558899a97cf5 golangci-lint-1.51.1-windows-386.zip
722d7b87b9cdda0a3835d5030b3fc5385c2eba4c107f63f6391cfb2ac35f051d golangci-lint-1.51.1-windows-amd64.zip
eb57f9bcb56646f2e3d6ccaf02ec227815fb05077b2e0b1bf9e755805acdc2b9 golangci-lint-1.51.1-windows-arm64.zip
bce02f7232723cb727755ee11f168a700a00896a25d37f87c4b173bce55596b4 golangci-lint-1.51.1-windows-armv6.zip
cf6403f84707ce8c98664736772271bc8874f2e760c2fd0f00cf3e85963507e9 golangci-lint-1.51.1-windows-armv7.zip
# version:golang 1.21.6
# https://go.dev/dl/
124926a62e45f78daabbaedb9c011d97633186a33c238ffc1e25320c02046248 go1.21.6.src.tar.gz
31d6ecca09010ab351e51343a5af81d678902061fee871f912bdd5ef4d778850 go1.21.6.darwin-amd64.tar.gz
0ff541fb37c38e5e5c5bcecc8f4f43c5ffd5e3a6c33a5d3e4003ded66fcfb331 go1.21.6.darwin-arm64.tar.gz
a1d1a149b34bf0f53965a237682c6da1140acabb131bf0e597240e4a140b0e5e go1.21.6.freebsd-386.tar.gz
de59e1217e4398b1522eed8dddabab2fa1b97aecbdca3af08e34832b4f0e3f81 go1.21.6.freebsd-amd64.tar.gz
05d09041b5a1193c14e4b2db3f7fcc649b236c567f5eb93305c537851b72dd95 go1.21.6.linux-386.tar.gz
3f934f40ac360b9c01f616a9aa1796d227d8b0328bf64cb045c7b8c4ee9caea4 go1.21.6.linux-amd64.tar.gz
e2e8aa88e1b5170a0d495d7d9c766af2b2b6c6925a8f8956d834ad6b4cacbd9a go1.21.6.linux-arm64.tar.gz
6a8eda6cc6a799ff25e74ce0c13fdc1a76c0983a0bb07c789a2a3454bf6ec9b2 go1.21.6.linux-armv6l.tar.gz
e872b1e9a3f2f08fd4554615a32ca9123a4ba877ab6d19d36abc3424f86bc07f go1.21.6.linux-ppc64le.tar.gz
92894d0f732d3379bc414ffdd617eaadad47e1d72610e10d69a1156db03fc052 go1.21.6.linux-s390x.tar.gz
65b38857135cf45c80e1d267e0ce4f80fe149326c68835217da4f2da9b7943fe go1.21.6.windows-386.zip
27ac9dd6e66fb3fd0acfa6792ff053c86e7d2c055b022f4b5d53bfddec9e3301 go1.21.6.windows-amd64.zip
b93aff8f3c882c764c66a39b7a1483b0460e051e9992bf3435479129e5051bcd go1.21.6.windows-arm64.zip
# version:golangci 1.55.2
# https://github.com/golangci/golangci-lint/releases/
# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/
632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz
234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz
2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz
e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz
5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz
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 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

View File

@ -120,15 +120,15 @@ var (
// Distros for which packages are created.
// 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:
// 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{
"trusty": "golang-1.11", // EOL: 04/2024
"xenial": "golang-go", // EOL: 04/2026
"bionic": "golang-go", // EOL: 04/2028
"focal": "golang-go", // EOL: 04/2030
"jammy": "golang-go", // EOL: 04/2032
"kinetic": "golang-go", // EOL: 07/2023
"lunar": "golang-go", // EOL: 01/2024
"trusty": "golang-1.11", // 14.04, EOL: 04/2024
"xenial": "golang-go", // 16.04, EOL: 04/2026
"bionic": "golang-go", // 18.04, EOL: 04/2028
"focal": "golang-go", // 20.04, EOL: 04/2030
"jammy": "golang-go", // 22.04, EOL: 04/2032
"mantic": "golang-go", // 23.10, EOL: 07/2024
}
debGoBootPaths = map[string]string{
@ -136,18 +136,8 @@ var (
"golang-go": "/usr/lib/go",
}
// This is the version of Go that will be downloaded by
//
// 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"
// This is where the tests should be unpacked.
executionSpecTestsDir = "tests/spec-tests"
)
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
@ -185,6 +175,8 @@ func main() {
doWindowsInstaller(os.Args[2:])
case "purge":
doPurge(os.Args[2:])
case "sanitycheck":
doSanityCheck()
default:
log.Fatal("unknown command ", os.Args[1])
}
@ -200,19 +192,23 @@ func doInstall(cmdline []string) {
staticlink = flag.Bool("static", false, "Create statically-linked executable")
)
flag.CommandLine.Parse(cmdline)
env := build.Env()
// Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
if *dlgo {
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.
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.
env := build.Env()
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
// 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" {
gobuild.Args = append(gobuild.Args, "-p", "1")
}
// We use -trimpath to avoid leaking local paths into the built executables.
gobuild.Args = append(gobuild.Args, "-trimpath")
@ -290,17 +285,31 @@ func doTest(cmdline []string) {
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
verbose = flag.Bool("v", false, "Whether to log verbosely")
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)
// Get test fixtures.
csdb := build.MustLoadChecksums("build/checksums.txt")
downloadSpecTestFixtures(csdb, *cachedir)
// Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
if *dlgo {
csdb := build.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb, dlgoVersion)
tc.Root = build.DownloadGo(csdb)
}
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
// and some tests run into timeouts under load.
gotest.Args = append(gotest.Args, "-p", "1")
@ -313,6 +322,9 @@ func doTest(cmdline []string) {
if *race {
gotest.Args = append(gotest.Args, "-race")
}
if *short {
gotest.Args = append(gotest.Args, "-short")
}
packages := []string{"./..."}
if len(flag.CommandLine.Args()) > 0 {
@ -322,6 +334,25 @@ func doTest(cmdline []string) {
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.
func doLint(cmdline []string) {
var (
@ -335,15 +366,17 @@ func doLint(cmdline []string) {
linter := downloadLinter(*cachedir)
lflags := []string{"run", "--config", ".golangci.yml"}
build.MustRunCommand(linter, append(lflags, packages...)...)
build.MustRunCommandWithOutput(linter, append(lflags, packages...)...)
fmt.Println("You have achieved perfection.")
}
// downloadLinter downloads and unpacks golangci-lint.
func downloadLinter(cachedir string) string {
const version = "1.51.1"
csdb := build.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "golangci")
if err != nil {
log.Fatal(err)
}
arch := runtime.GOARCH
ext := ".tar.gz"
@ -725,6 +758,10 @@ func doDebianSource(cmdline []string) {
// to bootstrap the builder Go.
func downloadGoBootstrapSources(cachedir string) string {
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)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file)
@ -737,6 +774,10 @@ func downloadGoBootstrapSources(cachedir string) string {
// downloadGoSources downloads the Go source tarball.
func downloadGoSources(cachedir string) string {
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)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file)
@ -1063,3 +1104,7 @@ func doPurge(cmdline []string) {
log.Fatal(err)
}
}
func doSanityCheck() {
build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt"))
}

View File

@ -28,7 +28,7 @@ override_dh_auto_build:
mv .mod $(GOPATH)/pkg/mod
# 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:

View File

@ -20,7 +20,7 @@
# - 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)
#
# 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.
#
# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller

View File

@ -46,12 +46,13 @@ import (
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"text/template"
"time"
"golang.org/x/exp/slices"
)
var (
@ -64,10 +65,8 @@ var (
"vendor/", "tests/testdata/", "build/",
// don't relicense vendored sources
"cmd/internal/browser",
"common/bitutil/bitutil",
"common/prque/",
"consensus/ethash/xor.go",
"crypto/blake2b/",
"crypto/bn256/",
"crypto/bls12381/",
@ -77,6 +76,7 @@ var (
"log/",
"metrics/",
"signer/rules/deps",
"internal/reexec",
// skip special licenses
"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
}
// 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() {
var (
files = getFiles()
@ -299,7 +292,9 @@ func writeAuthors(files []string) {
}
}
// 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.WriteString(authorsFileHeader)
for _, a := range list {

View File

@ -232,7 +232,7 @@ func abigen(c *cli.Context) error {
}
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 {
fmt.Fprintln(os.Stderr, err)

View File

@ -8,6 +8,7 @@ import (
)
func TestNameFilter(t *testing.T) {
t.Parallel()
_, err := newNameFilter("Foo")
require.Error(t, err)
_, err = newNameFilter("too/many:colons:Foo")

View File

@ -23,6 +23,7 @@ import (
"fmt"
"net"
"os"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"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>)")
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")
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")
nodeKey *ecdsa.PrivateKey
@ -51,10 +52,11 @@ func main() {
)
flag.Parse()
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(*verbosity))
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
slogVerbosity := log.FromLegacyLevel(*verbosity)
glogger.Verbosity(slogVerbosity)
glogger.Vmodule(*vmodule)
log.Root().SetHandler(glogger)
log.SetDefault(log.NewLogger(glogger))
natm, err := nat.Parse(*natdesc)
if err != nil {
@ -107,21 +109,20 @@ func main() {
if err != nil {
utils.Fatalf("-ListenUDP: %v", err)
}
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)
defer conn.Close()
db, _ := enode.OpenDB("")
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{
PrivateKey: nodeKey,
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("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
}

View File

@ -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.*

View File

@ -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 = &params.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 = &params.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))})
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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.
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.
@ -29,7 +29,7 @@ GLOBAL OPTIONS:
--loglevel value log level to emit to the screen (default: 4)
--keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore")
--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
--nousb Disables monitoring for and managing USB hardware wallets
--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|
| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- |
| 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: |
| 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)|
| 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: |
| 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)|

View File

@ -26,12 +26,13 @@ import (
// TestImportRaw tests clef --importraw
func TestImportRaw(t *testing.T) {
t.Parallel()
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
t.Cleanup(func() { os.Remove(keyPath) })
t.Parallel()
t.Run("happy-path", func(t *testing.T) {
t.Parallel()
// Run clef importraw
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
clef.input("myverylongpassword").input("myverylongpassword")
@ -43,6 +44,7 @@ func TestImportRaw(t *testing.T) {
})
// tests clef --importraw with mismatched passwords.
t.Run("pw-mismatch", func(t *testing.T) {
t.Parallel()
// Run clef importraw
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
clef.input("myverylongpassword1").input("myverylongpassword2").WaitExit()
@ -52,6 +54,7 @@ func TestImportRaw(t *testing.T) {
})
// tests clef --importraw with a too short password.
t.Run("short-pw", func(t *testing.T) {
t.Parallel()
// Run clef importraw
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
clef.input("shorty").input("shorty").WaitExit()
@ -64,12 +67,13 @@ func TestImportRaw(t *testing.T) {
// TestListAccounts tests clef --list-accounts
func TestListAccounts(t *testing.T) {
t.Parallel()
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
t.Cleanup(func() { os.Remove(keyPath) })
t.Parallel()
t.Run("no-accounts", func(t *testing.T) {
t.Parallel()
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-accounts")
if out := string(clef.Output()); !strings.Contains(out, "The keystore is empty.") {
t.Logf("Output\n%v", out)
@ -77,6 +81,7 @@ func TestListAccounts(t *testing.T) {
}
})
t.Run("one-account", func(t *testing.T) {
t.Parallel()
// First, we need to import
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
@ -91,12 +96,13 @@ func TestListAccounts(t *testing.T) {
// TestListWallets tests clef --list-wallets
func TestListWallets(t *testing.T) {
t.Parallel()
keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
t.Cleanup(func() { os.Remove(keyPath) })
t.Parallel()
t.Run("no-accounts", func(t *testing.T) {
t.Parallel()
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-wallets")
if out := string(clef.Output()); !strings.Contains(out, "There are no wallets.") {
t.Logf("Output\n%v", out)
@ -104,6 +110,7 @@ func TestListWallets(t *testing.T) {
}
})
t.Run("one-account", func(t *testing.T) {
t.Parallel()
// First, we need to import
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()

View File

@ -75,7 +75,7 @@ Example:
},
{
"type": "Info",
"message": "User should see this aswell"
"message": "User should see this as well"
}
],
"meta": {

View File

@ -27,6 +27,7 @@ import (
"fmt"
"io"
"math/big"
"net"
"os"
"os/signal"
"path/filepath"
@ -99,7 +100,7 @@ var (
chainIdFlag = &cli.Int64Flag{
Name: "chainid",
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{
Name: "http.port",
@ -326,7 +327,7 @@ func initializeSecrets(c *cli.Context) error {
return err
}
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
if c.Bool(utils.LightKDFFlag.Name) {
@ -482,7 +483,7 @@ func initialize(c *cli.Context) error {
}
} else if !c.Bool(acceptFlag.Name) {
if !confirm(legalWarning) {
return fmt.Errorf("aborted by user")
return errors.New("aborted by user")
}
fmt.Println()
}
@ -491,7 +492,8 @@ func initialize(c *cli.Context) error {
if usecolor {
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
}
@ -580,6 +582,7 @@ func accountImport(c *cli.Context) error {
return err
}
if first != second {
//lint:ignore ST1005 This is a message for the user
return errors.New("Passwords do not match")
}
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,
"light-kdf", lightKdf, "advanced", advanced)
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
defer am.Close()
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
// 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))
srv := rpc.NewServer()
srv.SetBatchLimits(node.DefaultConfig.BatchRequestLimit, node.DefaultConfig.BatchResponseMaxSize)
err := node.RegisterApis(rpcAPI, []string{"account"}, srv)
if err != nil {
utils.Fatalf("Could not register API: %w", err)
@ -742,7 +747,7 @@ func signer(c *cli.Context) error {
port := c.Int(rpcPortFlag.Name)
// 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)
if err != nil {
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)
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 {
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 .."},
},
{
Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
Address: common.MaxAddress,
},
}})
}

View File

@ -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":""}}]}
: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
:return:
""" # noqa: E501

View File

@ -21,8 +21,8 @@ import (
"os"
"testing"
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/internal/cmdtest"
"github.com/ethereum/go-ethereum/internal/reexec"
)
const registeredName = "clef-test"

View File

@ -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
- `-ip <CIDR>` filters nodes by IP subnet
- `-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
- `-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].
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
2. import the `halfchain.rlp` file in the `testdata` directory
3. run geth with the following flags:
```
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5
```
1. initialize the geth node with the `genesis.json` file
2. import blocks from `chain.rlp`
3. run the client using the resulting database. For geth, use a command like the one below:
Then, run the following command, replacing `<enode>` with the enode of the geth node:
```
devp2p rlpx eth-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
```
geth \
--datadir <datadir> \
--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.
#### 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
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup

View File

@ -17,6 +17,7 @@
package main
import (
"errors"
"sync"
"sync/atomic"
"time"
@ -51,7 +52,14 @@ type resolver interface {
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{
input: 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 {
c.output[id] = n
}
return c
return c, nil
}
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)
}
var (
added uint64
updated uint64
skipped uint64
recent uint64
removed uint64
added atomic.Uint64
updated atomic.Uint64
skipped atomic.Uint64
recent atomic.Uint64
removed atomic.Uint64
wg sync.WaitGroup
)
wg.Add(nthreads)
@ -103,15 +111,15 @@ func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet {
case n := <-c.ch:
switch c.updateNode(n) {
case nodeSkipIncompat:
atomic.AddUint64(&skipped, 1)
skipped.Add(1)
case nodeSkipRecent:
atomic.AddUint64(&recent, 1)
recent.Add(1)
case nodeRemoved:
atomic.AddUint64(&removed, 1)
removed.Add(1)
case nodeAdded:
atomic.AddUint64(&added, 1)
added.Add(1)
default:
atomic.AddUint64(&updated, 1)
updated.Add(1)
}
case <-c.closed:
return
@ -138,11 +146,11 @@ loop:
break loop
case <-statusTicker.C:
log.Info("Crawling in progress",
"added", atomic.LoadUint64(&added),
"updated", atomic.LoadUint64(&updated),
"removed", atomic.LoadUint64(&removed),
"ignored(recent)", atomic.LoadUint64(&recent),
"ignored(incompatible)", atomic.LoadUint64(&skipped))
"added", added.Load(),
"updated", updated.Load(),
"removed", removed.Load(),
"ignored(recent)", recent.Load(),
"ignored(incompatible)", skipped.Load())
}
}

View File

@ -17,6 +17,7 @@
package main
import (
"errors"
"fmt"
"net"
"strconv"
@ -142,7 +143,7 @@ var discoveryNodeFlags = []cli.Flag{
func discv4Ping(ctx *cli.Context) error {
n := getNodeArg(ctx)
disc := startV4(ctx)
disc, _ := startV4(ctx)
defer disc.Close()
start := time.Now()
@ -155,7 +156,7 @@ func discv4Ping(ctx *cli.Context) error {
func discv4RequestRecord(ctx *cli.Context) error {
n := getNodeArg(ctx)
disc := startV4(ctx)
disc, _ := startV4(ctx)
defer disc.Close()
respN, err := disc.RequestENR(n)
@ -168,7 +169,7 @@ func discv4RequestRecord(ctx *cli.Context) error {
func discv4Resolve(ctx *cli.Context) error {
n := getNodeArg(ctx)
disc := startV4(ctx)
disc, _ := startV4(ctx)
defer disc.Close()
fmt.Println(disc.Resolve(n).String())
@ -177,7 +178,7 @@ func discv4Resolve(ctx *cli.Context) error {
func discv4ResolveJSON(ctx *cli.Context) error {
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)
inputSet := make(nodeSet)
@ -195,10 +196,13 @@ func discv4ResolveJSON(ctx *cli.Context) error {
nodeargs = append(nodeargs, n)
}
// Run the crawler.
disc := startV4(ctx)
disc, config := startV4(ctx)
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
output := c.run(0, 1)
writeNodesJSON(nodesFile, output)
@ -207,17 +211,21 @@ func discv4ResolveJSON(ctx *cli.Context) error {
func discv4Crawl(ctx *cli.Context) error {
if ctx.NArg() < 1 {
return fmt.Errorf("need nodes file as argument")
return errors.New("need nodes file as argument")
}
nodesFile := ctx.Args().First()
var inputSet nodeSet
inputSet := make(nodeSet)
if common.FileExist(nodesFile) {
inputSet = loadNodesJSON(nodesFile)
}
disc := startV4(ctx)
disc, config := startV4(ctx)
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
output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name))
writeNodesJSON(nodesFile, output)
@ -228,7 +236,7 @@ func discv4Crawl(ctx *cli.Context) error {
func discv4Test(ctx *cli.Context) error {
// Configure test package globals.
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.Listen1 = ctx.String(testListen1Flag.Name)
@ -237,14 +245,14 @@ func discv4Test(ctx *cli.Context) error {
}
// 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)
socket := listen(ctx, ln)
disc, err := discover.ListenV4(socket, ln, config)
if err != nil {
exit(err)
}
return disc
return disc, 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) {
s := params.RinkebyBootnodes
s := params.MainnetBootnodes
if ctx.IsSet(bootnodesFlag.Name) {
input := ctx.String(bootnodesFlag.Name)
if input == "" {

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