Compare commits

..

5 Commits

Author SHA1 Message Date
Péter Szilágyi
69568c5548 params: release Geth v1.10.25 2022-09-15 17:55:58 +02:00
Marius van der Wijden
8f61fc8b73 params: set TerminalTotalDifficultyPassed to true (#25769)
* params: set TerminalTotalDifficultyPassed to true

* Update params/config.go

Co-authored-by: Martin Holst Swende <martin@swende.se>
2022-09-15 17:54:48 +02:00
Péter Szilágyi
972007a517 Release Geth v1.10.24 2022-09-14 11:07:10 +02:00
Sina Mahmoodi
3b41be695e graphql: fixes missing tx logs (#25745)
* graphql: fix tx logs

* graphql: refactor test service setup

* graphql: add test for tx logs
2022-09-13 23:03:49 +02:00
Sina Mahmoodi
d0dc349fd3 graphql: return correct logs for tx (#25612)
* graphql: fix tx logs

* minor

* Use optimized search for selecting tx logs
2022-09-13 23:03:21 +02:00
1341 changed files with 89211 additions and 111660 deletions

4
.github/CODEOWNERS vendored
View File

@@ -5,14 +5,16 @@ accounts/usbwallet @karalabe
accounts/scwallet @gballet accounts/scwallet @gballet
accounts/abi @gballet @MariusVanDerWijden accounts/abi @gballet @MariusVanDerWijden
cmd/clef @holiman cmd/clef @holiman
cmd/puppeth @karalabe
consensus @karalabe consensus @karalabe
core/ @karalabe @holiman @rjl493456442 core/ @karalabe @holiman @rjl493456442
eth/ @karalabe @holiman @rjl493456442 eth/ @karalabe @holiman @rjl493456442
eth/catalyst/ @gballet eth/catalyst/ @gballet
eth/tracers/ @s1na eth/tracers/ @s1na
graphql/ @s1na graphql/ @gballet @s1na
les/ @zsfelfoldi @rjl493456442 les/ @zsfelfoldi @rjl493456442
light/ @zsfelfoldi @rjl493456442 light/ @zsfelfoldi @rjl493456442
mobile/ @karalabe @ligi
node/ @fjl node/ @fjl
p2p/ @fjl @zsfelfoldi p2p/ @fjl @zsfelfoldi
rpc/ @fjl @holiman rpc/ @fjl @holiman

View File

@@ -35,6 +35,6 @@ and help.
## Configuration, dependencies, and tests ## Configuration, dependencies, and tests
Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/geth-developer/dev-guide) Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide)
for more details on configuring your environment, managing project dependencies for more details on configuring your environment, managing project dependencies
and testing procedures. and testing procedures.

View File

@@ -9,7 +9,6 @@ assignees: ''
#### System information #### System information
Geth version: `geth version` Geth version: `geth version`
CL client & version: e.g. lighthouse/nimbus/prysm@v1.0.0
OS & Version: Windows/Linux/OSX OS & Version: Windows/Linux/OSX
Commit hash : (if `develop`) Commit hash : (if `develop`)

View File

@@ -1,23 +0,0 @@
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

3
.gitignore vendored
View File

@@ -47,6 +47,3 @@ profile.cov
/dashboard/assets/package-lock.json /dashboard/assets/package-lock.json
**/yarn-error.log **/yarn-error.log
logs/
tests/spec-tests/

View File

@@ -12,18 +12,22 @@ run:
linters: linters:
disable-all: true disable-all: true
enable: enable:
- deadcode
- goconst
- goimports - goimports
- gosimple - gosimple
- govet - govet
- ineffassign - ineffassign
- misspell - misspell
- unconvert - unconvert
- varcheck
- typecheck - typecheck
- unused - unused
- staticcheck - staticcheck
- bidichk - bidichk
- durationcheck - durationcheck
- exportloopref - exportloopref
- gosec
- whitespace - whitespace
# - structcheck # lots of false positives # - structcheck # lots of false positives
@@ -38,6 +42,14 @@ linters:
linters-settings: linters-settings:
gofmt: gofmt:
simplify: true simplify: true
goconst:
min-len: 3 # minimum length of string constant
min-occurrences: 6 # minimum number of occurrences
gosec:
excludes:
- G404 # Use of weak random number generator - lots of FP
- G107 # Potential http request -- those are intentional
- G306 # G306: Expect WriteFile permissions to be 0600 or less
issues: issues:
exclude-rules: exclude-rules:
@@ -46,15 +58,16 @@ issues:
- deadcode - deadcode
- staticcheck - staticcheck
- path: internal/build/pgp.go - path: internal/build/pgp.go
text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.' text: 'SA1019: package golang.org/x/crypto/openpgp is deprecated'
- path: core/vm/contracts.go - path: core/vm/contracts.go
text: 'SA1019: "golang.org/x/crypto/ripemd160" is deprecated: RIPEMD-160 is a legacy hash and should not be used for new applications.' text: 'SA1019: package golang.org/x/crypto/ripemd160 is deprecated'
- path: accounts/usbwallet/trezor.go - path: accounts/usbwallet/trezor.go
text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.' text: 'SA1019: package github.com/golang/protobuf/proto is deprecated'
- path: accounts/usbwallet/trezor/ - path: accounts/usbwallet/trezor/
text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.' text: 'SA1019: package github.com/golang/protobuf/proto is deprecated'
exclude: exclude:
- 'SA1019: event.TypeMux is deprecated: use Feed' - 'SA1019: event.TypeMux is deprecated: use Feed'
- 'SA1019: strings.Title is deprecated' - 'SA1019: strings.Title is deprecated'
- 'SA1019: strings.Title has been deprecated since Go 1.18 and an alternative has been available since Go 1.0: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead.' - 'SA1019: strings.Title has been deprecated since Go 1.18 and an alternative has been available since Go 1.0: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead.'
- 'SA1029: should not use built-in type string as key for value' - 'SA1029: should not use built-in type string as key for value'
- 'G306: Expect WriteFile permissions to be 0600 or less'

View File

@@ -5,10 +5,25 @@ jobs:
allow_failures: allow_failures:
- stage: build - stage: build
os: osx os: osx
go: 1.17.x
env: env:
- azure-osx - azure-osx
- azure-ios
- cocoapods-ios
include: include:
# This builder only tests code linters on latest version of Go
- stage: lint
os: linux
dist: bionic
go: 1.18.x
env:
- lint
git:
submodules: false # avoid cloning ethereum/tests
script:
- go run build/ci.go lint
# These builders create the Docker sub-images for multi-arch push and each # These builders create the Docker sub-images for multi-arch push and each
# will attempt to push the multi-arch image if they are the last builder # will attempt to push the multi-arch image if they are the last builder
- stage: build - stage: build
@@ -16,7 +31,7 @@ jobs:
os: linux os: linux
arch: amd64 arch: amd64
dist: bionic dist: bionic
go: 1.21.x go: 1.18.x
env: env:
- docker - docker
services: services:
@@ -33,7 +48,7 @@ jobs:
os: linux os: linux
arch: arm64 arch: arm64
dist: bionic dist: bionic
go: 1.21.x go: 1.18.x
env: env:
- docker - docker
services: services:
@@ -45,15 +60,40 @@ jobs:
script: script:
- go run build/ci.go docker -image -manifest amd64,arm64 -upload ethereum/client-go - go run build/ci.go docker -image -manifest amd64,arm64 -upload ethereum/client-go
# This builder does the Ubuntu PPA upload
- stage: build
if: type = push
os: linux
dist: bionic
go: 1.18.x
env:
- ubuntu-ppa
- GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
addons:
apt:
packages:
- devscripts
- debhelper
- dput
- fakeroot
- python-bzrlib
- python-paramiko
script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
# This builder does the Linux Azure uploads # This builder does the Linux Azure uploads
- stage: build - stage: build
if: type = push if: type = push
os: linux os: linux
dist: bionic dist: bionic
sudo: required sudo: required
go: 1.21.x go: 1.18.x
env: env:
- azure-linux - azure-linux
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
addons: addons:
@@ -80,78 +120,115 @@ jobs:
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
# This builder does the OSX Azure uploads # This builder does the Android Maven and Azure uploads
- stage: build
if: type = push
os: linux
dist: bionic
addons:
apt:
packages:
- openjdk-8-jdk
env:
- azure-android
- maven-android
- GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
before_install:
# Install Android and it's dependencies manually, Travis is stale
- export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
- curl https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip -o android.zip
- unzip -q android.zip -d $HOME/sdk && rm android.zip
- mv $HOME/sdk/cmdline-tools $HOME/sdk/latest && mkdir $HOME/sdk/cmdline-tools && mv $HOME/sdk/latest $HOME/sdk/cmdline-tools
- export PATH=$PATH:$HOME/sdk/cmdline-tools/latest/bin
- export ANDROID_HOME=$HOME/sdk
- yes | sdkmanager --licenses >/dev/null
- sdkmanager "platform-tools" "platforms;android-15" "platforms;android-19" "platforms;android-24" "ndk-bundle"
# Install Go to allow building with
- curl https://dl.google.com/go/go1.18.linux-amd64.tar.gz | tar -xz
- export PATH=`pwd`/go/bin:$PATH
- export GOROOT=`pwd`/go
- export GOPATH=$HOME/go
script:
# Build the Android archive and upload it to Maven Central and Azure
- mkdir -p $GOPATH/src/github.com/ethereum
- ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
- stage: build - stage: build
if: type = push if: type = push
os: osx os: osx
osx_image: xcode14.2 go: 1.18.x
go: 1.21.x
env: env:
- azure-osx - azure-osx
- azure-ios
- cocoapods-ios
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:
- go run build/ci.go install -dlgo - go run build/ci.go install -dlgo
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
- go run build/ci.go install -dlgo -arch arm64
- go run build/ci.go archive -arch arm64 -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure
- gem uninstall cocoapods -a -x
- gem install cocoapods
- mv ~/.cocoapods/repos/master ~/.cocoapods/repos/master.bak
- sed -i '.bak' 's/repo.join/!repo.join/g' $(dirname `gem which cocoapods`)/cocoapods/sources_manager.rb
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git clone --depth=1 https://github.com/CocoaPods/Specs.git ~/.cocoapods/repos/master && pod setup --verbose; fi
- xctool -version
- xcrun simctl list
# Workaround for https://github.com/golang/go/issues/23749
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify SIGNIFY_KEY -deploy trunk -upload gethstore/builds
# These builders run the tests # These builders run the tests
- stage: build - stage: build
os: linux os: linux
arch: amd64 arch: amd64
dist: bionic dist: bionic
go: 1.21.x go: 1.18.x
env:
- GO111MODULE=on
script: script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES - go run build/ci.go test -coverage $TEST_PACKAGES
- stage: build - stage: build
if: type = pull_request if: type = pull_request
os: linux os: linux
arch: arm64 arch: arm64
dist: bionic dist: bionic
go: 1.20.x go: 1.18.x
script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- stage: build
os: linux
dist: bionic
go: 1.20.x
script:
- 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.21.x
env: env:
- ubuntu-ppa - GO111MODULE=on
git:
submodules: false # avoid cloning ethereum/tests
addons:
apt:
packages:
- devscripts
- debhelper
- dput
- fakeroot
- python-bzrlib
- python-paramiko
script: script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts - go run build/ci.go test -coverage $TEST_PACKAGES
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
- stage: build
os: linux
dist: bionic
go: 1.17.x
env:
- GO111MODULE=on
script:
- go run build/ci.go test -coverage $TEST_PACKAGES
# This builder does the Azure archive purges to avoid accumulating junk # This builder does the Azure archive purges to avoid accumulating junk
- stage: build - stage: build
if: type = cron if: type = cron
os: linux os: linux
dist: bionic dist: bionic
go: 1.21.x go: 1.18.x
env: env:
- azure-purge - azure-purge
- GO111MODULE=on
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:
@@ -162,7 +239,9 @@ jobs:
if: type = cron if: type = cron
os: linux os: linux
dist: bionic dist: bionic
go: 1.21.x go: 1.18.x
env:
- GO111MODULE=on
script: script:
- travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES - go run build/ci.go test -race -coverage $TEST_PACKAGES

View File

@@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM="" ARG BUILDNUM=""
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.21-alpine as builder FROM golang:1.18-alpine as builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git

View File

@@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM="" ARG BUILDNUM=""
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.21-alpine as builder FROM golang:1.18-alpine as builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git

View File

@@ -6,7 +6,7 @@
GOBIN = ./build/bin GOBIN = ./build/bin
GO ?= latest GO ?= latest
GORUN = go run GORUN = env GO111MODULE=on go run
geth: geth:
$(GORUN) build/ci.go install ./cmd/geth $(GORUN) build/ci.go install ./cmd/geth
@@ -16,6 +16,18 @@ geth:
all: all:
$(GORUN) build/ci.go install $(GORUN) build/ci.go install
android:
$(GORUN) build/ci.go aar --local
@echo "Done building."
@echo "Import \"$(GOBIN)/geth.aar\" to use the library."
@echo "Import \"$(GOBIN)/geth-sources.jar\" to add javadocs"
@echo "For more info see https://stackoverflow.com/questions/20994336/android-studio-how-to-attach-javadoc"
ios:
$(GORUN) build/ci.go xcode --local
@echo "Done building."
@echo "Import \"$(GOBIN)/Geth.framework\" to use the library."
test: all test: all
$(GORUN) build/ci.go test $(GORUN) build/ci.go test
@@ -23,7 +35,7 @@ lint: ## Run linters.
$(GORUN) build/ci.go lint $(GORUN) build/ci.go lint
clean: clean:
go clean -cache env GO111MODULE=on go clean -cache
rm -fr build/_workspace/pkg/ $(GOBIN)/* rm -fr build/_workspace/pkg/ $(GOBIN)/*
# The devtools target installs tools required for 'go generate'. # The devtools target installs tools required for 'go generate'.

View File

@@ -1,9 +1,9 @@
## Go Ethereum ## Go Ethereum
Golang execution layer implementation of the Ethereum protocol. Official Golang implementation of the Ethereum protocol.
[![API Reference]( [![API Reference](
https://pkg.go.dev/badge/github.com/ethereum/go-ethereum https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667
)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) )](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc)
[![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) [![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
[![Travis](https://travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.com/ethereum/go-ethereum) [![Travis](https://travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.com/ethereum/go-ethereum)
@@ -14,9 +14,9 @@ archives are published at https://geth.ethereum.org/downloads/.
## Building the source ## Building the source
For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/getting-started/installing-geth). For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth).
Building `geth` requires both a Go (version 1.19 or later) and a C compiler. You can install Building `geth` requires both a Go (version 1.16 or later) and a C compiler. You can install
them using your favourite package manager. Once the dependencies are installed, run them using your favourite package manager. Once the dependencies are installed, run
```shell ```shell
@@ -34,20 +34,21 @@ make all
The go-ethereum project comes with several wrappers/executables found in the `cmd` The go-ethereum project comes with several wrappers/executables found in the `cmd`
directory. directory.
| Command | Description | | Command | Description |
| :--------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/fundamentals/command-line-options) for command line options. | | **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. |
| `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. | | `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. |
| `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. | | `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. |
| `abigen` | Source code generator to convert Ethereum contract definitions into easy-to-use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/developers/dapp-developer/native-bindings) page for details. | | `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. |
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | | `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | | `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). |
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | | `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
## Running `geth` ## Running `geth`
Going through all the possible command line flags is out of scope here (please consult our Going through all the possible command line flags is out of scope here (please consult our
[CLI Wiki page](https://geth.ethereum.org/docs/fundamentals/command-line-options)), [CLI Wiki page](https://geth.ethereum.org/docs/interface/command-line-options)),
but we've enumerated a few common parameter combos to get you up to speed quickly but we've enumerated a few common parameter combos to get you up to speed quickly
on how you can run your own `geth` instance. on how you can run your own `geth` instance.
@@ -64,14 +65,14 @@ Recommended:
* Fast CPU with 4+ cores * Fast CPU with 4+ cores
* 16GB+ RAM * 16GB+ RAM
* High-performance SSD with at least 1TB of free space * High Performance SSD with at least 1TB free space
* 25+ MBit/sec download Internet service * 25+ MBit/sec download Internet service
### Full node on the main Ethereum network ### Full node on the main Ethereum network
By far the most common scenario is people wanting to simply interact with the Ethereum By far the most common scenario is people wanting to simply interact with the Ethereum
network: create accounts; transfer funds; deploy and interact with contracts. For this network: create accounts; transfer funds; deploy and interact with contracts. For this
particular use case, the user doesn't care about years-old historical data, so we can particular use-case the user doesn't care about years-old historical data, so we can
sync quickly to the current state of the network. To do so: sync quickly to the current state of the network. To do so:
```shell ```shell
@@ -82,11 +83,11 @@ This command will:
* Start `geth` in snap sync mode (default, can be changed with the `--syncmode` flag), * Start `geth` in snap sync mode (default, can be changed with the `--syncmode` flag),
causing it to download more data in exchange for avoiding processing the entire history causing it to download more data in exchange for avoiding processing the entire history
of the Ethereum network, which is very CPU intensive. of the Ethereum network, which is very CPU intensive.
* Start the built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interacting-with-geth/javascript-console), * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console),
(via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md) (via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md)
(note: the `web3` version bundled within `geth` is very old, and not up to date with official docs), (note: the `web3` version bundled within `geth` is very old, and not up to date with official docs),
as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc). as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server).
This tool is optional and if you leave it out you can always attach it to an already running This tool is optional and if you leave it out you can always attach to an already running
`geth` instance with `geth attach`. `geth` instance with `geth attach`.
### A Full node on the Görli test network ### A Full node on the Görli test network
@@ -101,12 +102,12 @@ the main network, but with play-Ether only.
$ geth --goerli console $ geth --goerli console
``` ```
The `console` subcommand has the same meaning as above and is equally The `console` subcommand has the exact same meaning as above and they are equally
useful on the testnet too. useful on the testnet too. Please, see above for their explanations if you've skipped here.
Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit: Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit:
* Instead of connecting to the main Ethereum network, the client will connect to the Görli * Instead of connecting the main Ethereum network, the client will connect to the Görli
test network, which uses different P2P bootnodes, different network IDs and genesis test network, which uses different P2P bootnodes, different network IDs and genesis
states. states.
* Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth` * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth`
@@ -117,12 +118,34 @@ Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a
`geth attach <datadir>/goerli/geth.ipc`. Windows users are not affected by `geth attach <datadir>/goerli/geth.ipc`. Windows users are not affected by
this. this.
*Note: Although some internal protective measures prevent transactions from *Note: Although there are some internal protective measures to prevent transactions from
crossing over between the main network and test network, you should always crossing over between the main network and test network, you should make sure to always
use separate accounts for play and real money. Unless you manually move use separate accounts for play-money and real-money. Unless you manually move
accounts, `geth` will by default correctly separate the two networks and will not make any accounts, `geth` will by default correctly separate the two networks and will not make any
accounts available between them.* accounts available between them.*
### Full node on the Rinkeby test network
Go Ethereum also supports connecting to the older proof-of-authority based test network
called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community.
```shell
$ geth --rinkeby console
```
### Full node on the Ropsten test network
In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The
Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such,
it has certain extra overhead and is more susceptible to reorganization attacks due to the
network's low difficulty/security.
```shell
$ geth --ropsten console
```
*Note: Older Geth configurations store the Ropsten database in the `testnet` subdirectory.*
### Configuration ### Configuration
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a As an alternative to passing the numerous flags to the `geth` binary, you can also pass a
@@ -132,7 +155,7 @@ configuration file via:
$ geth --config /path/to/your_config.toml $ geth --config /path/to/your_config.toml
``` ```
To get an idea of how the file should look like you can use the `dumpconfig` subcommand to To get an idea how the file should look like you can use the `dumpconfig` subcommand to
export your existing configuration: export your existing configuration:
```shell ```shell
@@ -152,7 +175,7 @@ docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
ethereum/client-go ethereum/client-go
``` ```
This will start `geth` in snap-sync mode with a DB memory allowance of 1GB, as the This will start `geth` in snap-sync mode with a DB memory allowance of 1GB just as the
above command does. It will also create a persistent volume in your home directory for above command does. It will also create a persistent volume in your home directory for
saving your blockchain as well as map the default ports. There is also an `alpine` tag saving your blockchain as well as map the default ports. There is also an `alpine` tag
available for a slim version of the image. available for a slim version of the image.
@@ -166,7 +189,7 @@ accessible from the outside.
As a developer, sooner rather than later you'll want to start interacting with `geth` and the As a developer, sooner rather than later you'll want to start interacting with `geth` and the
Ethereum network via your own programs and not manually through the console. To aid Ethereum network via your own programs and not manually through the console. To aid
this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.github.io/execution-apis/api-documentation/) this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.github.io/execution-apis/api-documentation/)
and [`geth` specific APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc)). and [`geth` specific APIs](https://geth.ethereum.org/docs/rpc/server)).
These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based
platforms, and named pipes on Windows). platforms, and named pipes on Windows).
@@ -186,7 +209,7 @@ HTTP based JSON-RPC API options:
* `--ws.addr` WS-RPC server listening interface (default: `localhost`) * `--ws.addr` WS-RPC server listening interface (default: `localhost`)
* `--ws.port` WS-RPC server listening port (default: `8546`) * `--ws.port` WS-RPC server listening port (default: `8546`)
* `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`) * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`)
* `--ws.origins` Origins from which to accept WebSocket requests * `--ws.origins` Origins from which to accept websockets requests
* `--ipcdisable` Disable the IPC-RPC server * `--ipcdisable` Disable the IPC-RPC server
* `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,txpool,web3`) * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,txpool,web3`)
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it) * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
@@ -299,8 +322,12 @@ also need to configure a miner to process transactions and create new blocks for
#### Running a private miner #### Running a private miner
Mining on the public Ethereum network is a complex task as it's only feasible using GPUs,
requiring an OpenCL or CUDA enabled `ethminer` instance. For information on such a
setup, please consult the [EtherMining subreddit](https://www.reddit.com/r/EtherMining/)
and the [ethminer](https://github.com/ethereum-mining/ethminer) repository.
In a private network setting a single CPU miner instance is more than enough for In a private network setting, however a single CPU miner instance is more than enough for
practical purposes as it can produce a stable stream of blocks at the correct intervals practical purposes as it can produce a stable stream of blocks at the correct intervals
without needing heavy resources (consider running on a single thread, no need for multiple without needing heavy resources (consider running on a single thread, no need for multiple
ones either). To start a `geth` instance for mining, run it with all your usual flags, extended ones either). To start a `geth` instance for mining, run it with all your usual flags, extended
@@ -317,7 +344,7 @@ transactions are accepted at (`--miner.gasprice`).
## Contribution ## Contribution
Thank you for considering helping out with the source code! We welcome contributions Thank you for considering to help out with the source code! We welcome contributions
from anyone on the internet, and are grateful for even the smallest of fixes! from anyone on the internet, and are grateful for even the smallest of fixes!
If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request
@@ -337,22 +364,16 @@ Please make sure your contributions adhere to our coding guidelines:
* Commit messages should be prefixed with the package(s) they modify. * Commit messages should be prefixed with the package(s) they modify.
* E.g. "eth, rpc: make trace configs optional" * E.g. "eth, rpc: make trace configs optional"
Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/geth-developer/dev-guide) Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide)
for more details on configuring your environment, managing project dependencies, and for more details on configuring your environment, managing project dependencies, and
testing procedures. testing procedures.
### Contributing to geth.ethereum.org
For contributions to the [go-ethereum website](https://geth.ethereum.org), please checkout and raise pull requests against the `website` branch.
For more detailed instructions please see the `website` branch [README](https://github.com/ethereum/go-ethereum/tree/website#readme) or the
[contributing](https://geth.ethereum.org/docs/developers/geth-developer/contributing) page of the website.
## License ## License
The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the
[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html),
also included in our repository in the `COPYING.LESSER` file. also included in our repository in the `COPYING.LESSER` file.
The go-ethereum binaries (i.e. all code inside of the `cmd` directory) are licensed under the The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the
[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also
included in our repository in the `COPYING` file. included in our repository in the `COPYING` file.

View File

@@ -22,7 +22,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@@ -88,7 +87,7 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {
var args Arguments var args Arguments
if method, ok := abi.Methods[name]; ok { if method, ok := abi.Methods[name]; ok {
if len(data)%32 != 0 { if len(data)%32 != 0 {
return nil, fmt.Errorf("abi: improperly formatted output: %q - Bytes: %+v", data, data) return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data)
} }
args = method.Outputs args = method.Outputs
} }
@@ -223,17 +222,6 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
} }
// ErrorByID looks up an error by the 4-byte id,
// returns nil if none found.
func (abi *ABI) ErrorByID(sigdata [4]byte) (*Error, error) {
for _, errABI := range abi.Errors {
if bytes.Equal(errABI.ID[:4], sigdata[:]) {
return &errABI, nil
}
}
return nil, fmt.Errorf("no error with id: %#x", sigdata[:])
}
// HasFallback returns an indicator whether a fallback function is included. // HasFallback returns an indicator whether a fallback function is included.
func (abi *ABI) HasFallback() bool { func (abi *ABI) HasFallback() bool {
return abi.Fallback.Type == Fallback return abi.Fallback.Type == Fallback
@@ -247,65 +235,21 @@ func (abi *ABI) HasReceive() bool {
// revertSelector is a special function selector for revert reason unpacking. // revertSelector is a special function selector for revert reason unpacking.
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
// panicSelector is a special function selector for panic reason unpacking.
var panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4]
// panicReasons map is for readable panic codes
// see this linkage for the details
// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
// the reason string list is copied from ether.js
// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
var panicReasons = map[uint64]string{
0x00: "generic panic",
0x01: "assert(false)",
0x11: "arithmetic underflow or overflow",
0x12: "division or modulo by zero",
0x21: "enum overflow",
0x22: "invalid encoded storage byte array accessed",
0x31: "out-of-bounds array access; popping on an empty array",
0x32: "out-of-bounds access of an array or bytesN",
0x41: "out of memory",
0x51: "uninitialized function",
}
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity // UnpackRevert resolves the abi-encoded revert reason. According to the solidity
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, // spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
// the provided revert reason is abi-encoded as if it were a call to function // the provided revert reason is abi-encoded as if it were a call to a function
// `Error(string)` or `Panic(uint256)`. So it's a special tool for it. // `Error(string)`. So it's a special tool for it.
func UnpackRevert(data []byte) (string, error) { func UnpackRevert(data []byte) (string, error) {
if len(data) < 4 { if len(data) < 4 {
return "", errors.New("invalid data for unpacking") return "", errors.New("invalid data for unpacking")
} }
switch { if !bytes.Equal(data[:4], revertSelector) {
case bytes.Equal(data[:4], revertSelector):
typ, err := NewType("string", "", nil)
if err != nil {
return "", err
}
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
if err != nil {
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") return "", errors.New("invalid data for unpacking")
} }
typ, _ := NewType("string", "", nil)
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
if err != nil {
return "", err
}
return unpacked[0].(string), nil
} }

View File

@@ -120,7 +120,6 @@ var methods = map[string]Method{
} }
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
t.Parallel()
abi := ABI{ abi := ABI{
Methods: methods, Methods: methods,
} }
@@ -152,7 +151,6 @@ func TestReader(t *testing.T) {
} }
func TestInvalidABI(t *testing.T) { func TestInvalidABI(t *testing.T) {
t.Parallel()
json := `[{ "type" : "function", "name" : "", "constant" : fals }]` json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
_, err := JSON(strings.NewReader(json)) _, err := JSON(strings.NewReader(json))
if err == nil { if err == nil {
@@ -167,12 +165,10 @@ func TestInvalidABI(t *testing.T) {
// TestConstructor tests a constructor function. // TestConstructor tests a constructor function.
// The test is based on the following contract: // The test is based on the following contract:
// // contract TestConstructor {
// contract TestConstructor { // constructor(uint256 a, uint256 b) public{}
// constructor(uint256 a, uint256 b) public{}
// } // }
func TestConstructor(t *testing.T) { func TestConstructor(t *testing.T) {
t.Parallel()
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]` json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil) method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
// Test from JSON // Test from JSON
@@ -202,7 +198,6 @@ func TestConstructor(t *testing.T) {
} }
func TestTestNumbers(t *testing.T) { func TestTestNumbers(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -240,7 +235,6 @@ func TestTestNumbers(t *testing.T) {
} }
func TestMethodSignature(t *testing.T) { func TestMethodSignature(t *testing.T) {
t.Parallel()
m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil) m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil)
exp := "foo(string,string)" exp := "foo(string,string)"
if m.Sig != exp { if m.Sig != exp {
@@ -279,7 +273,6 @@ func TestMethodSignature(t *testing.T) {
} }
func TestOverloadedMethodSignature(t *testing.T) { func TestOverloadedMethodSignature(t *testing.T) {
t.Parallel()
json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]` json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]`
abi, err := JSON(strings.NewReader(json)) abi, err := JSON(strings.NewReader(json))
if err != nil { if err != nil {
@@ -303,7 +296,6 @@ func TestOverloadedMethodSignature(t *testing.T) {
} }
func TestCustomErrors(t *testing.T) { func TestCustomErrors(t *testing.T) {
t.Parallel()
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]` json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
abi, err := JSON(strings.NewReader(json)) abi, err := JSON(strings.NewReader(json))
if err != nil { if err != nil {
@@ -318,7 +310,6 @@ func TestCustomErrors(t *testing.T) {
} }
func TestMultiPack(t *testing.T) { func TestMultiPack(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -356,7 +347,6 @@ func ExampleJSON() {
} }
func TestInputVariableInputLength(t *testing.T) { func TestInputVariableInputLength(t *testing.T) {
t.Parallel()
const definition = `[ const definition = `[
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] }, { "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] }, { "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
@@ -485,7 +475,6 @@ func TestInputVariableInputLength(t *testing.T) {
} }
func TestInputFixedArrayAndVariableInputLength(t *testing.T) { func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@@ -660,7 +649,6 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
} }
func TestDefaultFunctionParsing(t *testing.T) { func TestDefaultFunctionParsing(t *testing.T) {
t.Parallel()
const definition = `[{ "name" : "balance", "type" : "function" }]` const definition = `[{ "name" : "balance", "type" : "function" }]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
@@ -674,7 +662,6 @@ func TestDefaultFunctionParsing(t *testing.T) {
} }
func TestBareEvents(t *testing.T) { func TestBareEvents(t *testing.T) {
t.Parallel()
const definition = `[ const definition = `[
{ "type" : "event", "name" : "balance" }, { "type" : "event", "name" : "balance" },
{ "type" : "event", "name" : "anon", "anonymous" : true}, { "type" : "event", "name" : "anon", "anonymous" : true},
@@ -737,21 +724,17 @@ func TestBareEvents(t *testing.T) {
} }
// TestUnpackEvent is based on this contract: // TestUnpackEvent is based on this contract:
// // contract T {
// contract T { // event received(address sender, uint amount, bytes memo);
// event received(address sender, uint amount, bytes memo); // event receivedAddr(address sender);
// event receivedAddr(address sender); // function receive(bytes memo) external payable {
// function receive(bytes memo) external payable { // received(msg.sender, msg.value, memo);
// received(msg.sender, msg.value, memo); // receivedAddr(msg.sender);
// receivedAddr(msg.sender); // }
// } // }
// }
//
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: // When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
// // receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
func TestUnpackEvent(t *testing.T) { func TestUnpackEvent(t *testing.T) {
t.Parallel()
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
abi, err := JSON(strings.NewReader(abiJSON)) abi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
@@ -790,7 +773,6 @@ func TestUnpackEvent(t *testing.T) {
} }
func TestUnpackEventIntoMap(t *testing.T) { func TestUnpackEventIntoMap(t *testing.T) {
t.Parallel()
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
abi, err := JSON(strings.NewReader(abiJSON)) abi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
@@ -841,7 +823,6 @@ func TestUnpackEventIntoMap(t *testing.T) {
} }
func TestUnpackMethodIntoMap(t *testing.T) { func TestUnpackMethodIntoMap(t *testing.T) {
t.Parallel()
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
abi, err := JSON(strings.NewReader(abiJSON)) abi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
@@ -892,7 +873,6 @@ func TestUnpackMethodIntoMap(t *testing.T) {
} }
func TestUnpackIntoMapNamingConflict(t *testing.T) { func TestUnpackIntoMapNamingConflict(t *testing.T) {
t.Parallel()
// Two methods have the same name // Two methods have the same name
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
abi, err := JSON(strings.NewReader(abiJSON)) abi, err := JSON(strings.NewReader(abiJSON))
@@ -976,7 +956,6 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) {
} }
func TestABI_MethodById(t *testing.T) { func TestABI_MethodById(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(jsondata)) abi, err := JSON(strings.NewReader(jsondata))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -1009,7 +988,6 @@ func TestABI_MethodById(t *testing.T) {
} }
func TestABI_EventById(t *testing.T) { func TestABI_EventById(t *testing.T) {
t.Parallel()
tests := []struct { tests := []struct {
name string name string
json string json string
@@ -1075,39 +1053,9 @@ func TestABI_EventById(t *testing.T) {
} }
} }
func TestABI_ErrorByID(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(`[
{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"MyError1","type":"error"},
{"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"x","type":"tuple"},{"internalType":"address","name":"y","type":"address"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"z","type":"tuple"}],"name":"MyError2","type":"error"},
{"inputs":[{"internalType":"uint256[]","name":"x","type":"uint256[]"}],"name":"MyError3","type":"error"}
]`))
if err != nil {
t.Fatal(err)
}
for name, m := range abi.Errors {
a := fmt.Sprintf("%v", &m)
var id [4]byte
copy(id[:], m.ID[:4])
m2, err := abi.ErrorByID(id)
if err != nil {
t.Fatalf("Failed to look up ABI error: %v", err)
}
b := fmt.Sprintf("%v", m2)
if a != b {
t.Errorf("Error %v (id %x) not 'findable' by id in ABI", name, id)
}
}
// test unsuccessful lookups
if _, err = abi.ErrorByID([4]byte{}); err == nil {
t.Error("Expected error: no error with this id")
}
}
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name // TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
// conflict and that the second transfer method will be renamed transfer1. // conflict and that the second transfer method will be renamed transfer1.
func TestDoubleDuplicateMethodNames(t *testing.T) { func TestDoubleDuplicateMethodNames(t *testing.T) {
t.Parallel()
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON)) contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
@@ -1130,14 +1078,12 @@ func TestDoubleDuplicateMethodNames(t *testing.T) {
// TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name // TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name
// conflict and that the second send event will be renamed send1. // conflict and that the second send event will be renamed send1.
// The test runs the abi of the following contract. // The test runs the abi of the following contract.
// // contract DuplicateEvent {
// contract DuplicateEvent { // event send(uint256 a);
// event send(uint256 a);
// event send0(); // event send0();
// event send(); // event send();
// } // }
func TestDoubleDuplicateEventNames(t *testing.T) { func TestDoubleDuplicateEventNames(t *testing.T) {
t.Parallel()
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]` abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON)) contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
@@ -1160,12 +1106,10 @@ func TestDoubleDuplicateEventNames(t *testing.T) {
// TestUnnamedEventParam checks that an event with unnamed parameters is // TestUnnamedEventParam checks that an event with unnamed parameters is
// correctly handled. // correctly handled.
// The test runs the abi of the following contract. // The test runs the abi of the following contract.
// // contract TestEvent {
// contract TestEvent {
// event send(uint256, uint256); // event send(uint256, uint256);
// } // }
func TestUnnamedEventParam(t *testing.T) { func TestUnnamedEventParam(t *testing.T) {
t.Parallel()
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]` abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
contractAbi, err := JSON(strings.NewReader(abiJSON)) contractAbi, err := JSON(strings.NewReader(abiJSON))
if err != nil { if err != nil {
@@ -1195,13 +1139,9 @@ func TestUnpackRevert(t *testing.T) {
{"", "", errors.New("invalid data for unpacking")}, {"", "", errors.New("invalid data for unpacking")},
{"08c379a1", "", errors.New("invalid data for unpacking")}, {"08c379a1", "", errors.New("invalid data for unpacking")},
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil}, {"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
} }
for index, c := range cases { for index, c := range cases {
index, c := index, c
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
t.Parallel()
got, err := UnpackRevert(common.Hex2Bytes(c.input)) got, err := UnpackRevert(common.Hex2Bytes(c.input))
if c.expectErr != nil { if c.expectErr != nil {
if err == nil { if err == nil {

View File

@@ -80,7 +80,7 @@ func (arguments Arguments) isTuple() bool {
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
if len(data) == 0 { if len(data) == 0 {
if len(arguments.NonIndexed()) != 0 { if len(arguments.NonIndexed()) != 0 {
return nil, errors.New("abi: attempting to unmarshal an empty string while arguments are expected") return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
} }
return make([]interface{}, 0), nil return make([]interface{}, 0), nil
} }
@@ -95,7 +95,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte)
} }
if len(data) == 0 { if len(data) == 0 {
if len(arguments.NonIndexed()) != 0 { if len(arguments.NonIndexed()) != 0 {
return errors.New("abi: attempting to unmarshal an empty string while arguments are expected") return errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
} }
return nil // Nothing to unmarshal, return return nil // Nothing to unmarshal, return
} }
@@ -187,9 +187,6 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
virtualArgs := 0 virtualArgs := 0
for index, arg := range nonIndexedArgs { for index, arg := range nonIndexedArgs {
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
if err != nil {
return nil, err
}
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
// If we have a static array, like [3]uint256, these are coded as // If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256. // just like uint256,uint256,uint256.
@@ -207,6 +204,9 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
// coded as just like uint256,bool,uint256 // coded as just like uint256,bool,uint256
virtualArgs += getTypeSize(arg.Type)/32 - 1 virtualArgs += getTypeSize(arg.Type)/32 - 1
} }
if err != nil {
return nil, err
}
retval = append(retval, marshalledValue) retval = append(retval, marshalledValue)
} }
return retval, nil return retval, nil

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 // NewKeyStoreTransactor is a utility method to easily create a transaction signer from
// a decrypted key from a keystore. // an decrypted key from a keystore.
// //
// Deprecated: Use NewKeyStoreTransactorWithChainID instead. // Deprecated: Use NewKeyStoreTransactorWithChainID instead.
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
@@ -117,7 +117,7 @@ func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.I
} }
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from // NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
// a decrypted key from a keystore. // an decrypted key from a keystore.
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) { func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
if chainID == nil { if chainID == nil {
return nil, ErrNoChainID return nil, ErrNoChainID

View File

@@ -29,17 +29,13 @@ import (
var ( var (
// ErrNoCode is returned by call and transact operations for which the requested // ErrNoCode is returned by call and transact operations for which the requested
// recipient contract to operate on does not exist in the state db or does not // recipient contract to operate on does not exist in the state db or does not
// have any code associated with it (i.e. self-destructed). // have any code associated with it (i.e. suicided).
ErrNoCode = errors.New("no contract code at given address") ErrNoCode = errors.New("no contract code at given address")
// ErrNoPendingState is raised when attempting to perform a pending state action // ErrNoPendingState is raised when attempting to perform a pending state action
// on a backend that doesn't implement PendingContractCaller. // on a backend that doesn't implement PendingContractCaller.
ErrNoPendingState = errors.New("backend does not support pending state") ErrNoPendingState = errors.New("backend does not support pending state")
// ErrNoBlockHashState is raised when attempting to perform a block hash action
// on a backend that doesn't implement BlockHashContractCaller.
ErrNoBlockHashState = errors.New("backend does not support block hash state")
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
// an empty contract behind. // an empty contract behind.
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
@@ -68,27 +64,11 @@ type PendingContractCaller interface {
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
} }
// BlockHashContractCaller defines methods to perform contract calls on a specific block hash.
// Call will try to discover this interface when access to a block by hash is requested.
// If the backend does not support the block hash state, Call returns ErrNoBlockHashState.
type BlockHashContractCaller interface {
// CodeAtHash returns the code of the given account in the state at the specified block hash.
CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error)
// CallContractAtHash executes an Ethereum contract call against the state at the specified block hash.
CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
}
// ContractTransactor defines the methods needed to allow operating with a contract // ContractTransactor defines the methods needed to allow operating with a contract
// on a write only basis. Besides the transacting method, the remainder are helpers // on a write only basis. Besides the transacting method, the remainder are helpers
// used when the user does not provide some needed values, but rather leaves it up // used when the user does not provide some needed values, but rather leaves it up
// to the transactor to decide. // to the transactor to decide.
type ContractTransactor interface { type ContractTransactor interface {
ethereum.GasEstimator
ethereum.GasPricer
ethereum.GasPricer1559
ethereum.TransactionSender
// HeaderByNumber returns a block header from the current canonical chain. If // HeaderByNumber returns a block header from the current canonical chain. If
// number is nil, the latest known header is returned. // number is nil, the latest known header is returned.
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
@@ -98,6 +78,38 @@ type ContractTransactor interface {
// PendingNonceAt retrieves the current pending nonce associated with an account. // PendingNonceAt retrieves the current pending nonce associated with an account.
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
SuggestGasPrice(ctx context.Context) (*big.Int, error)
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
// a timely execution of a transaction.
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
// EstimateGas tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default.
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
// SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error
}
// ContractFilterer defines the methods needed to access log events using one-off
// queries or continuous event subscriptions.
type ContractFilterer interface {
// FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//
// TODO(karalabe): Deprecate when the subscription one can return past data too.
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
// SubscribeFilterLogs creates a background log filtering operation, returning
// a subscription immediately, which can be used to stream the found events.
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
} }
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed. // DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
@@ -106,12 +118,6 @@ type DeployBackend interface {
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
} }
// ContractFilterer defines the methods needed to access log events using one-off
// queries or continuous event subscriptions.
type ContractFilterer interface {
ethereum.LogFilterer
}
// ContractBackend defines the methods needed to work with contracts on a read-write basis. // ContractBackend defines the methods needed to work with contracts on a read-write basis.
type ContractBackend interface { type ContractBackend interface {
ContractCaller ContractCaller

View File

@@ -18,35 +18,881 @@ package backends
import ( import (
"context" "context"
"errors"
"fmt"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethclient/simulated" "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"
) )
// SimulatedBackend is a simulated blockchain. // This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend.
// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead. 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
type SimulatedBackend struct { type SimulatedBackend struct {
*simulated.Backend database ethdb.Database // In memory database to store our testing data
simulated.Client 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
} }
// Fork sets the head to a new block, which is based on the provided parentHash. // NewSimulatedBackendWithDatabase creates a new binding backend based on the given database
func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error { // and uses a simulated blockchain for testing purposes.
return b.Backend.Fork(parentHash) // 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}
genesis.MustCommit(database)
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, 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)
backend.rollback(blockchain.CurrentBlock())
return backend
} }
// NewSimulatedBackend creates a new binding backend using a simulated blockchain // NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes. // for testing purposes.
//
// A simulated backend always uses chainID 1337. // A simulated backend always uses chainID 1337.
//
// Deprecated: please use simulated.Backend from package
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit)) return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit)
return &SimulatedBackend{ }
Backend: b,
Client: b.Client(), // 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()
b.rollback(b.blockchain.CurrentBlock())
}
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.blockchain.CurrentBlock(), nil
}
block := b.blockchain.GetBlockByNumber(uint64(number.Int64()))
if block == nil {
return nil, errBlockDoesNotExist
}
return block, nil
}
// HeaderByHash returns a block header from the current canonical chain.
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
b.mu.Lock()
defer b.mu.Unlock()
if hash == b.pendingBlock.Hash() {
return b.pendingBlock.Header(), nil
}
header := b.blockchain.GetHeaderByHash(hash)
if header == nil {
return nil, errBlockDoesNotExist
}
return header, nil
}
// HeaderByNumber returns a block header from the current canonical chain. If number is
// nil, the latest known header is returned.
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
b.mu.Lock()
defer b.mu.Unlock()
if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 {
return b.blockchain.CurrentHeader(), nil
}
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
}
// TransactionCount returns the number of transactions in a given block.
func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
b.mu.Lock()
defer b.mu.Unlock()
if blockHash == b.pendingBlock.Hash() {
return uint(b.pendingBlock.Transactions().Len()), nil
}
block := b.blockchain.GetBlockByHash(blockHash)
if block == nil {
return uint(0), errBlockDoesNotExist
}
return uint(block.Transactions().Len()), nil
}
// TransactionInBlock returns the transaction for a specific block at a specific index.
func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
b.mu.Lock()
defer b.mu.Unlock()
if blockHash == b.pendingBlock.Hash() {
transactions := b.pendingBlock.Transactions()
if uint(len(transactions)) < index+1 {
return nil, errTransactionDoesNotExist
}
return transactions[index], nil
}
block := b.blockchain.GetBlockByHash(blockHash)
if block == nil {
return nil, errBlockDoesNotExist
}
transactions := block.Transactions()
if uint(len(transactions)) < index+1 {
return nil, errTransactionDoesNotExist
}
return transactions[index], nil
}
// PendingCodeAt returns the code associated with an account in the pending state.
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.pendingState.GetCode(contract), nil
}
func newRevertError(result *core.ExecutionResult) *revertError {
reason, errUnpack := abi.UnpackRevert(result.Revert())
err := errors.New("execution reverted")
if errUnpack == nil {
err = fmt.Errorf("execution reverted: %v", reason)
}
return &revertError{
error: err,
reason: hexutil.Encode(result.Revert()),
} }
} }
// revertError is an API error that encompasses an EVM revert with JSON error
// code and a binary data blob.
type revertError struct {
error
reason string // revert reason hex encoded
}
// ErrorCode returns the JSON error code for a revert.
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
func (e *revertError) ErrorCode() int {
return 3
}
// ErrorData returns the hex encoded revert reason.
func (e *revertError) ErrorData() interface{} {
return e.reason
}
// CallContract executes a contract call.
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
return nil, errBlockNumberUnsupported
}
stateDB, err := b.blockchain.State()
if err != nil {
return nil, err
}
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB)
if err != nil {
return nil, err
}
// If the result contains a revert reason, try to unpack and return it.
if len(res.Revert()) > 0 {
return nil, newRevertError(res)
}
return res.Return(), res.Err
}
// PendingCallContract executes a contract call on the pending state.
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
res, err := b.callContract(ctx, call, b.pendingBlock, 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, errors.New("insufficient funds for transfer")
}
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, 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, block *types.Block, 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 := callMsg{call}
txContext := core.NewEVMTxContext(msg)
evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb()
}
// 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")
}
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), 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
}
// callMsg implements core.Message to allow passing it as a transaction simulator.
type callMsg struct {
ethereum.CallMsg
}
func (m callMsg) From() common.Address { return m.CallMsg.From }
func (m callMsg) Nonce() uint64 { return 0 }
func (m callMsg) IsFake() bool { return true }
func (m callMsg) To() *common.Address { return m.CallMsg.To }
func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
// 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, block rpc.BlockNumber) (*types.Header, error) {
if block == rpc.LatestBlockNumber {
return fb.bc.CurrentHeader(), nil
}
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
}
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return fb.bc.GetHeaderByHash(hash), nil
}
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 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

@@ -32,13 +32,6 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
) )
const basefeeWiggleMultiplier = 2
var (
errNoEventSignature = errors.New("no event signature")
errEventSignatureMismatch = errors.New("event signature mismatch")
)
// SignerFn is a signer function callback when a contract requires a method to // SignerFn is a signer function callback when a contract requires a method to
// sign the transaction before submission. // sign the transaction before submission.
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error)
@@ -48,7 +41,6 @@ type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one Pending bool // Whether to operate on the pending state or the last known one
From common.Address // Optional the sender address, otherwise the first account is used From common.Address // Optional the sender address, otherwise the first account is used
BlockNumber *big.Int // Optional the block number on which the call should be performed BlockNumber *big.Int // Optional the block number on which the call should be performed
BlockHash common.Hash // Optional the block hash on which the call should be performed
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
} }
@@ -190,23 +182,6 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
return ErrNoCode return ErrNoCode
} }
} }
} else if opts.BlockHash != (common.Hash{}) {
bh, ok := c.caller.(BlockHashContractCaller)
if !ok {
return ErrNoBlockHashState
}
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
if err != nil {
return err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
return err
} else if len(code) == 0 {
return ErrNoCode
}
}
} else { } else {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
if err != nil { if err != nil {
@@ -238,7 +213,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
if err != nil { if err != nil {
return nil, err return nil, err
} }
// todo(rjl493456442) check whether the method is payable or not, // todo(rjl493456442) check the method is payable or not,
// reject invalid transaction at the first place // reject invalid transaction at the first place
return c.transact(opts, &c.address, input) return c.transact(opts, &c.address, input)
} }
@@ -246,7 +221,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
// RawTransact initiates a transaction with the given raw calldata as the input. // RawTransact initiates a transaction with the given raw calldata as the input.
// It's usually used to initiate transactions for invoking **Fallback** function. // It's usually used to initiate transactions for invoking **Fallback** function.
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
// todo(rjl493456442) check whether the method is payable or not, // todo(rjl493456442) check the method is payable or not,
// reject invalid transaction at the first place // reject invalid transaction at the first place
return c.transact(opts, &c.address, calldata) return c.transact(opts, &c.address, calldata)
} }
@@ -279,7 +254,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add
if gasFeeCap == nil { if gasFeeCap == nil {
gasFeeCap = new(big.Int).Add( gasFeeCap = new(big.Int).Add(
gasTipCap, gasTipCap,
new(big.Int).Mul(head.BaseFee, big.NewInt(basefeeWiggleMultiplier)), new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
) )
} }
if gasFeeCap.Cmp(gasTipCap) < 0 { if gasFeeCap.Cmp(gasTipCap) < 0 {
@@ -396,8 +371,6 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
) )
if opts.GasPrice != nil { if opts.GasPrice != nil {
rawTx, err = c.createLegacyTx(opts, contract, input) rawTx, err = c.createLegacyTx(opts, contract, input)
} else if opts.GasFeeCap != nil && opts.GasTipCap != nil {
rawTx, err = c.createDynamicTx(opts, contract, input, nil)
} else { } else {
// Only query for basefee if gasPrice not specified // Only query for basefee if gasPrice not specified
if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil { if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil {
@@ -511,12 +484,8 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
// UnpackLog unpacks a retrieved log into the provided output structure. // UnpackLog unpacks a retrieved log into the provided output structure.
func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error {
// Anonymous events are not supported.
if len(log.Topics) == 0 {
return errNoEventSignature
}
if log.Topics[0] != c.abi.Events[event].ID { if log.Topics[0] != c.abi.Events[event].ID {
return errEventSignatureMismatch return fmt.Errorf("event signature mismatch")
} }
if len(log.Data) > 0 { if len(log.Data) > 0 {
if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
@@ -534,12 +503,8 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
// UnpackLogIntoMap unpacks a retrieved log into the provided map. // UnpackLogIntoMap unpacks a retrieved log into the provided map.
func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error {
// Anonymous events are not supported.
if len(log.Topics) == 0 {
return errNoEventSignature
}
if log.Topics[0] != c.abi.Events[event].ID { if log.Topics[0] != c.abi.Events[event].ID {
return errEventSignatureMismatch return fmt.Errorf("event signature mismatch")
} }
if len(log.Data) > 0 { if len(log.Data) > 0 {
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {

View File

@@ -114,28 +114,7 @@ func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ether
return mc.pendingCallContractBytes, mc.pendingCallContractErr return mc.pendingCallContractBytes, mc.pendingCallContractErr
} }
type mockBlockHashCaller struct {
*mockCaller
codeAtHashBytes []byte
codeAtHashErr error
codeAtHashCalled bool
callContractAtHashCalled bool
callContractAtHashBytes []byte
callContractAtHashErr error
}
func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) {
mc.codeAtHashCalled = true
return mc.codeAtHashBytes, mc.codeAtHashErr
}
func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) {
mc.callContractAtHashCalled = true
return mc.callContractAtHashBytes, mc.callContractAtHashErr
}
func TestPassingBlockNumber(t *testing.T) { func TestPassingBlockNumber(t *testing.T) {
t.Parallel()
mc := &mockPendingCaller{ mc := &mockPendingCaller{
mockCaller: &mockCaller{ mockCaller: &mockCaller{
codeAtBytes: []byte{1, 2, 3}, codeAtBytes: []byte{1, 2, 3},
@@ -187,7 +166,6 @@ func TestPassingBlockNumber(t *testing.T) {
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
t.Parallel()
hash := crypto.Keccak256Hash([]byte("testName")) hash := crypto.Keccak256Hash([]byte("testName"))
topics := []common.Hash{ topics := []common.Hash{
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")), crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
@@ -208,26 +186,7 @@ func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
unpackAndCheck(t, bc, expectedReceivedMap, mockLog) unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
} }
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"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
var received map[string]interface{}
err := bc.UnpackLogIntoMap(received, "received", mockLog)
if err == nil {
t.Error("unpacking anonymous event is not supported")
}
if err.Error() != "no event signature" {
t.Errorf("expected error 'no event signature', got '%s'", err)
}
}
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
t.Parallel()
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"}) sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -253,7 +212,6 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
} }
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
t.Parallel()
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")}) arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -279,7 +237,6 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
} }
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
t.Parallel()
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
addrBytes := mockAddress.Bytes() addrBytes := mockAddress.Bytes()
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)")) hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
@@ -306,7 +263,6 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
} }
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
t.Parallel()
bytes := []byte{1, 2, 3, 4, 5} bytes := []byte{1, 2, 3, 4, 5}
hash := crypto.Keccak256Hash(bytes) hash := crypto.Keccak256Hash(bytes)
topics := []common.Hash{ topics := []common.Hash{
@@ -329,7 +285,6 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
} }
func TestTransactGasFee(t *testing.T) { func TestTransactGasFee(t *testing.T) {
t.Parallel()
assert := assert.New(t) assert := assert.New(t)
// GasTipCap and GasFeeCap // GasTipCap and GasFeeCap
@@ -405,7 +360,6 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
} }
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
t.Parallel()
var method, methodWithArg = "something", "somethingArrrrg" var method, methodWithArg = "something", "somethingArrrrg"
tests := []struct { tests := []struct {
name, method string name, method string
@@ -429,15 +383,6 @@ func TestCall(t *testing.T) {
Pending: true, Pending: true,
}, },
method: method, method: method,
}, {
name: "ok hash",
mc: &mockBlockHashCaller{
codeAtHashBytes: []byte{0},
},
opts: &bind.CallOpts{
BlockHash: common.Hash{0xaa},
},
method: method,
}, { }, {
name: "pack error, no method", name: "pack error, no method",
mc: new(mockCaller), mc: new(mockCaller),
@@ -451,14 +396,6 @@ func TestCall(t *testing.T) {
}, },
method: method, method: method,
wantErrExact: bind.ErrNoPendingState, wantErrExact: bind.ErrNoPendingState,
}, {
name: "interface error, blockHash but not a BlockHashContractCaller",
mc: new(mockCaller),
opts: &bind.CallOpts{
BlockHash: common.Hash{0xaa},
},
method: method,
wantErrExact: bind.ErrNoBlockHashState,
}, { }, {
name: "pending call canceled", name: "pending call canceled",
mc: &mockPendingCaller{ mc: &mockPendingCaller{
@@ -506,34 +443,6 @@ func TestCall(t *testing.T) {
mc: new(mockCaller), mc: new(mockCaller),
method: method, method: method,
wantErrExact: bind.ErrNoCode, wantErrExact: bind.ErrNoCode,
}, {
name: "call contract at hash error",
mc: &mockBlockHashCaller{
callContractAtHashErr: context.DeadlineExceeded,
},
opts: &bind.CallOpts{
BlockHash: common.Hash{0xaa},
},
method: method,
wantErrExact: context.DeadlineExceeded,
}, {
name: "code at error",
mc: &mockBlockHashCaller{
codeAtHashErr: errors.New(""),
},
opts: &bind.CallOpts{
BlockHash: common.Hash{0xaa},
},
method: method,
wantErr: true,
}, {
name: "no code at hash",
mc: new(mockBlockHashCaller),
opts: &bind.CallOpts{
BlockHash: common.Hash{0xaa},
},
method: method,
wantErrExact: bind.ErrNoCode,
}, { }, {
name: "unpack error missing arg", name: "unpack error missing arg",
mc: &mockCaller{ mc: &mockCaller{
@@ -581,7 +490,6 @@ func TestCall(t *testing.T) {
// TestCrashers contains some strings which previously caused the abi codec to crash. // TestCrashers contains some strings which previously caused the abi codec to crash.
func TestCrashers(t *testing.T) { func TestCrashers(t *testing.T) {
t.Parallel()
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`)) abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`)) abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`)) abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))

View File

@@ -22,6 +22,7 @@ package bind
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"go/format" "go/format"
"regexp" "regexp"
@@ -38,6 +39,8 @@ type Lang int
const ( const (
LangGo Lang = iota LangGo Lang = iota
LangJava
LangObjC
) )
func isKeyWord(arg string) bool { func isKeyWord(arg string) bool {
@@ -79,7 +82,7 @@ func isKeyWord(arg string) bool {
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
// to be used as is in client code, but rather as an intermediate struct which // to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention as opposed to having to // enforces compile time type safety and naming convention opposed to having to
// manually maintain hard coded strings that break on runtime. // manually maintain hard coded strings that break on runtime.
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
var ( var (
@@ -133,19 +136,12 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// Normalize the method for capital cases and non-anonymous inputs/outputs // Normalize the method for capital cases and non-anonymous inputs/outputs
normalized := original normalized := original
normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
// Ensure there is no duplicated identifier // Ensure there is no duplicated identifier
var identifiers = callIdentifiers var identifiers = callIdentifiers
if !original.IsConstant() { if !original.IsConstant() {
identifiers = transactIdentifiers identifiers = transactIdentifiers
} }
// Name shouldn't start with a digit. It will make the generated code invalid.
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
normalizedName = fmt.Sprintf("M%s", normalizedName)
normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool {
_, ok := identifiers[name]
return ok
})
}
if identifiers[normalizedName] { if identifiers[normalizedName] {
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
} }
@@ -189,14 +185,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// Ensure there is no duplicated identifier // Ensure there is no duplicated identifier
normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
// Name shouldn't start with a digit. It will make the generated code invalid.
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
normalizedName = fmt.Sprintf("E%s", normalizedName)
normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool {
_, ok := eventIdentifiers[name]
return ok
})
}
if eventIdentifiers[normalizedName] { if eventIdentifiers[normalizedName] {
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
} }
@@ -233,6 +221,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
if evmABI.HasReceive() { if evmABI.HasReceive() {
receive = &tmplMethod{Original: evmABI.Receive} receive = &tmplMethod{Original: evmABI.Receive}
} }
// There is no easy way to pass arbitrary java objects to the Go side.
if len(structs) > 0 && lang == LangJava {
return "", errors.New("java binding for tuple arguments is not supported yet")
}
contracts[types[i]] = &tmplContract{ contracts[types[i]] = &tmplContract{
Type: capitalise(types[i]), Type: capitalise(types[i]),
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
@@ -305,7 +298,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// bindType is a set of type binders that convert Solidity types to some supported // bindType is a set of type binders that convert Solidity types to some supported
// programming language types. // programming language types.
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
LangGo: bindTypeGo, LangGo: bindTypeGo,
LangJava: bindTypeJava,
} }
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. // bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones.
@@ -348,10 +342,86 @@ func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
} }
} }
// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java ones.
func bindBasicTypeJava(kind abi.Type) string {
switch kind.T {
case abi.AddressTy:
return "Address"
case abi.IntTy, abi.UintTy:
// Note that uint and int (without digits) are also matched,
// these are size 256, and will translate to BigInt (the default).
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String())
if len(parts) != 3 {
return kind.String()
}
// All unsigned integers should be translated to BigInt since gomobile doesn't
// support them.
if parts[1] == "u" {
return "BigInt"
}
namedSize := map[string]string{
"8": "byte",
"16": "short",
"32": "int",
"64": "long",
}[parts[2]]
// default to BigInt
if namedSize == "" {
namedSize = "BigInt"
}
return namedSize
case abi.FixedBytesTy, abi.BytesTy:
return "byte[]"
case abi.BoolTy:
return "boolean"
case abi.StringTy:
return "String"
case abi.FunctionTy:
return "byte[24]"
default:
return kind.String()
}
}
// pluralizeJavaType explicitly converts multidimensional types to predefined
// types in go side.
func pluralizeJavaType(typ string) string {
switch typ {
case "boolean":
return "Bools"
case "String":
return "Strings"
case "Address":
return "Addresses"
case "byte[]":
return "Binaries"
case "BigInt":
return "BigInts"
}
return typ + "[]"
}
// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
// mapped will use an upscaled type (e.g. BigDecimal).
func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T {
case abi.TupleTy:
return structs[kind.TupleRawName+kind.String()].Name
case abi.ArrayTy, abi.SliceTy:
return pluralizeJavaType(bindTypeJava(*kind.Elem, structs))
default:
return bindBasicTypeJava(kind)
}
}
// bindTopicType is a set of type binders that convert Solidity types to some // bindTopicType is a set of type binders that convert Solidity types to some
// supported programming language topic types. // supported programming language topic types.
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
LangGo: bindTopicTypeGo, LangGo: bindTopicTypeGo,
LangJava: bindTopicTypeJava,
} }
// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
@@ -363,7 +433,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
// parameters that are not value types i.e. arrays and structs are not // parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored. // stored directly but instead a keccak256-hash of an encoding is stored.
// //
// We only convert strings and bytes to hash, still need to deal with // We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct. // array(both fixed-size and dynamic-size) and struct.
if bound == "string" || bound == "[]byte" { if bound == "string" || bound == "[]byte" {
bound = "common.Hash" bound = "common.Hash"
@@ -371,10 +441,28 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
return bound return bound
} }
// bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same
// functionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
bound := bindTypeJava(kind, structs)
// todo(rjl493456442) according 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.
//
// 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 = "Hash"
}
return bound
}
// bindStructType is a set of type binders that convert Solidity tuple types to some supported // bindStructType is a set of type binders that convert Solidity tuple types to some supported
// programming language struct definition. // programming language struct definition.
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
LangGo: bindStructTypeGo, LangGo: bindStructTypeGo,
LangJava: bindStructTypeJava,
} }
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping // bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
@@ -423,10 +511,74 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
} }
} }
// bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping
// in the given map.
// Notably, this function will resolve and record nested struct recursively.
func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T {
case abi.TupleTy:
// We compose a raw struct name and a canonical parameter expression
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
// is empty, so we use canonical parameter expression to distinguish
// different struct definition. From the consideration of backward
// compatibility, we concat these two together so that if kind.TupleRawName
// is not empty, it can have unique id.
id := kind.TupleRawName + kind.String()
if s, exist := structs[id]; exist {
return s.Name
}
var fields []*tmplField
for i, elem := range kind.TupleElems {
field := bindStructTypeJava(*elem, structs)
fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem})
}
name := kind.TupleRawName
if name == "" {
name = fmt.Sprintf("Class%d", len(structs))
}
structs[id] = &tmplStruct{
Name: name,
Fields: fields,
}
return name
case abi.ArrayTy, abi.SliceTy:
return pluralizeJavaType(bindStructTypeJava(*kind.Elem, structs))
default:
return bindBasicTypeJava(kind)
}
}
// namedType is a set of functions that transform language specific types to // namedType is a set of functions that transform language specific types to
// named versions that may be used inside method names. // named versions that may be used inside method names.
var namedType = map[Lang]func(string, abi.Type) string{ var namedType = map[Lang]func(string, abi.Type) string{
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
LangJava: namedTypeJava,
}
// namedTypeJava converts some primitive data types to named variants that can
// be used as parts of method names.
func namedTypeJava(javaKind string, solKind abi.Type) string {
switch javaKind {
case "byte[]":
return "Binary"
case "boolean":
return "Bool"
default:
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
if len(parts) != 4 {
return javaKind
}
switch parts[2] {
case "8", "16", "32", "64":
if parts[3] == "" {
return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2]))
}
return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2]))
default:
return javaKind
}
}
} }
// alias returns an alias of the given string based on the aliasing rules // alias returns an alias of the given string based on the aliasing rules
@@ -441,7 +593,8 @@ func alias(aliases map[string]string, n string) string {
// methodNormalizer is a name transformer that modifies Solidity method names to // methodNormalizer is a name transformer that modifies Solidity method names to
// conform to target language naming conventions. // conform to target language naming conventions.
var methodNormalizer = map[Lang]func(string) string{ var methodNormalizer = map[Lang]func(string) string{
LangGo: abi.ToCamelCase, LangGo: abi.ToCamelCase,
LangJava: decapitalise,
} }
// capitalise makes a camel-case string which starts with an upper case character. // capitalise makes a camel-case string which starts with an upper case character.

File diff suppressed because one or more lines are too long

View File

@@ -75,7 +75,8 @@ type tmplStruct struct {
// tmplSource is language to template mapping containing all the supported // tmplSource is language to template mapping containing all the supported
// programming languages the package can generate to. // programming languages the package can generate to.
var tmplSource = map[Lang]string{ var tmplSource = map[Lang]string{
LangGo: tmplSourceGo, LangGo: tmplSourceGo,
LangJava: tmplSourceJava,
} }
// tmplSourceGo is the Go source template that the generated Go contract binding // tmplSourceGo is the Go source template that the generated Go contract binding
@@ -109,7 +110,6 @@ var (
_ = common.Big1 _ = common.Big1
_ = types.BloomLookup _ = types.BloomLookup
_ = event.NewSubscription _ = event.NewSubscription
_ = abi.ConvertType
) )
{{$structs := .Structs}} {{$structs := .Structs}}
@@ -268,11 +268,11 @@ var (
// bind{{.Type}} binds a generic wrapper to an already deployed contract. // bind{{.Type}} binds a generic wrapper to an already deployed contract.
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := {{.Type}}MetaData.GetAbi() parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
} }
// Call invokes the (constant) contract method with params as input values and // Call invokes the (constant) contract method with params as input values and
@@ -569,3 +569,140 @@ var (
{{end}} {{end}}
{{end}} {{end}}
` `
// tmplSourceJava is the Java source template that the generated Java contract binding
// is based on.
const tmplSourceJava = `
// This file is an automatically generated Java binding. Do not modify as any
// change will likely be lost upon the next re-generation!
package {{.Package}};
import org.ethereum.geth.*;
import java.util.*;
{{$structs := .Structs}}
{{range $contract := .Contracts}}
{{if not .Library}}public {{end}}class {{.Type}} {
// ABI is the input ABI used to generate the binding from.
public final static String ABI = "{{.InputABI}}";
{{if $contract.FuncSigs}}
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
public final static Map<String, String> {{.Type}}FuncSigs;
static {
Hashtable<String, String> temp = new Hashtable<String, String>();
{{range $strsig, $binsig := .FuncSigs}}temp.put("{{$binsig}}", "{{$strsig}}");
{{end}}
{{.Type}}FuncSigs = Collections.unmodifiableMap(temp);
}
{{end}}
{{if .InputBin}}
// BYTECODE is the compiled bytecode used for deploying new contracts.
public final static String BYTECODE = "0x{{.InputBin}}";
// deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}});
String bytecode = BYTECODE;
{{if .Libraries}}
// "link" contract to dependent libraries by deploying them first.
{{range $pattern, $name := .Libraries}}
{{capitalise $name}} {{decapitalise $name}}Inst = {{capitalise $name}}.deploy(auth, client);
bytecode = bytecode.replace("__${{$pattern}}$__", {{decapitalise $name}}Inst.Address.getHex().substring(2));
{{end}}
{{end}}
{{range $index, $element := .Constructor.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
{{end}}
return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args));
}
// Internal constructor used by contract deployment.
private {{.Type}}(BoundContract deployment) {
this.Address = deployment.getAddress();
this.Deployer = deployment.getDeployer();
this.Contract = deployment;
}
{{end}}
// Ethereum address where this contract is located at.
public final Address Address;
// Ethereum transaction in which this contract was deployed (if known!).
public final Transaction Deployer;
// Contract instance bound to a blockchain address.
private final BoundContract Contract;
// Creates a new instance of {{.Type}}, bound to a specific deployed contract.
public {{.Type}}(Address address, EthereumClient client) throws Exception {
this(Geth.bindContract(address, ABI, client));
}
{{range .Calls}}
{{if gt (len .Normalized.Outputs) 1}}
// {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}.
public class {{capitalise .Normalized.Name}}Results {
{{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type $structs}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}};
{{end}}
}
{{end}}
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{.Original.String}}
public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else if eq (len .Normalized.Outputs) 0}}void{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
{{end}}
Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}});
{{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type $structs) .Type}}(); results.set({{$index}}, result{{$index}});
{{end}}
if (opts == null) {
opts = Geth.newCallOpts();
}
this.Contract.call(opts, results, "{{.Original.Name}}", args);
{{if gt (len .Normalized.Outputs) 1}}
{{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results();
{{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type $structs) .Type}}();
{{end}}
return result;
{{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type $structs) .Type}}();{{end}}
{{end}}
}
{{end}}
{{range .Transacts}}
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{.Original.String}}
public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
{{end}}
return this.Contract.transact(opts, "{{.Original.Name}}" , args);
}
{{end}}
{{if .Fallback}}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{.Fallback.Original.String}}
public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception {
return this.Contract.rawTransact(opts, calldata);
}
{{end}}
{{if .Receive}}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{.Receive.Original.String}}
public Transaction Receive(TransactOpts opts) throws Exception {
return this.Contract.rawTransact(opts, null);
}
{{end}}
}
{{end}}
`

View File

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

View File

@@ -18,6 +18,7 @@ package abi
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"strings" "strings"
@@ -31,7 +32,7 @@ type Error struct {
str string str string
// Sig contains the string signature according to the ABI spec. // Sig contains the string signature according to the ABI spec.
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
// Please note that "int" is substitute for its canonical representation "int256" // Please note that "int" is substitute for its canonical representation "int256"
Sig string Sig string
@@ -77,16 +78,16 @@ func NewError(name string, inputs Arguments) Error {
} }
} }
func (e Error) String() string { func (e *Error) String() string {
return e.str return e.str
} }
func (e *Error) Unpack(data []byte) (interface{}, error) { func (e *Error) Unpack(data []byte) (interface{}, error) {
if len(data) < 4 { if len(data) < 4 {
return "", fmt.Errorf("insufficient data for unpacking: have %d, want at least 4", len(data)) return "", errors.New("invalid data for unpacking")
} }
if !bytes.Equal(data[:4], e.ID[:4]) { if !bytes.Equal(data[:4], e.ID[:4]) {
return "", fmt.Errorf("invalid identifier, have %#x want %#x", data[:4], e.ID[:4]) return "", errors.New("invalid data for unpacking")
} }
return e.Inputs.Unpack(data[4:]) return e.Inputs.Unpack(data[4:])
} }

View File

@@ -23,15 +23,7 @@ import (
) )
var ( var (
errBadBool = errors.New("abi: improperly encoded boolean value") errBadBool = errors.New("abi: improperly encoded boolean value")
errBadUint8 = errors.New("abi: improperly encoded uint8 value")
errBadUint16 = errors.New("abi: improperly encoded uint16 value")
errBadUint32 = errors.New("abi: improperly encoded uint32 value")
errBadUint64 = errors.New("abi: improperly encoded uint64 value")
errBadInt8 = errors.New("abi: improperly encoded int8 value")
errBadInt16 = errors.New("abi: improperly encoded int16 value")
errBadInt32 = errors.New("abi: improperly encoded int32 value")
errBadInt64 = errors.New("abi: improperly encoded int64 value")
) )
// formatSliceString formats the reflection kind with the given slice size // formatSliceString formats the reflection kind with the given slice size

View File

@@ -81,7 +81,6 @@ var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241" var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
func TestEventId(t *testing.T) { func TestEventId(t *testing.T) {
t.Parallel()
var table = []struct { var table = []struct {
definition string definition string
expectations map[string]common.Hash expectations map[string]common.Hash
@@ -113,7 +112,6 @@ func TestEventId(t *testing.T) {
} }
func TestEventString(t *testing.T) { func TestEventString(t *testing.T) {
t.Parallel()
var table = []struct { var table = []struct {
definition string definition string
expectations map[string]string expectations map[string]string
@@ -148,7 +146,6 @@ func TestEventString(t *testing.T) {
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. // TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
func TestEventMultiValueWithArrayUnpack(t *testing.T) { func TestEventMultiValueWithArrayUnpack(t *testing.T) {
t.Parallel()
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
require.NoError(t, err) require.NoError(t, err)
@@ -164,7 +161,6 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) {
} }
func TestEventTupleUnpack(t *testing.T) { func TestEventTupleUnpack(t *testing.T) {
t.Parallel()
type EventTransfer struct { type EventTransfer struct {
Value *big.Int Value *big.Int
} }
@@ -355,7 +351,6 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. // TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
func TestEventUnpackIndexed(t *testing.T) { func TestEventUnpackIndexed(t *testing.T) {
t.Parallel()
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
type testStruct struct { type testStruct struct {
Value1 uint8 // indexed Value1 uint8 // indexed
@@ -373,7 +368,6 @@ func TestEventUnpackIndexed(t *testing.T) {
// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input. // TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input.
func TestEventIndexedWithArrayUnpack(t *testing.T) { func TestEventIndexedWithArrayUnpack(t *testing.T) {
t.Parallel()
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
type testStruct struct { type testStruct struct {
Value1 [2]uint8 // indexed Value1 [2]uint8 // indexed

View File

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

View File

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

View File

@@ -57,7 +57,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
reflectValue = mustArrayToByteSlice(reflectValue) reflectValue = mustArrayToByteSlice(reflectValue)
} }
if reflectValue.Type() != reflect.TypeOf([]byte{}) { if reflectValue.Type() != reflect.TypeOf([]byte{}) {
return []byte{}, errors.New("bytes type is neither slice nor array") return []byte{}, errors.New("Bytes type is neither slice nor array")
} }
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
case FixedBytesTy, FunctionTy: case FixedBytesTy, FunctionTy:
@@ -66,7 +66,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
} }
return common.RightPadBytes(reflectValue.Bytes(), 32), nil return common.RightPadBytes(reflectValue.Bytes(), 32), nil
default: default:
return []byte{}, fmt.Errorf("could not pack element, unknown type: %v", t.T) return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T)
} }
} }

View File

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

View File

@@ -25,19 +25,16 @@ import (
) )
// ConvertType converts an interface of a runtime type into a interface of the // ConvertType converts an interface of a runtime type into a interface of the
// given type, e.g. turn this code: // given type
// // e.g. turn
// var fields []reflect.StructField // var fields []reflect.StructField
// // fields = append(fields, reflect.StructField{
// fields = append(fields, reflect.StructField{ // Name: "X",
// Name: "X", // Type: reflect.TypeOf(new(big.Int)),
// Type: reflect.TypeOf(new(big.Int)), // Tag: reflect.StructTag("json:\"" + "x" + "\""),
// Tag: reflect.StructTag("json:\"" + "x" + "\""), // }
// } // into
// // type TupleT struct { X *big.Int }
// into:
//
// type TupleT struct { X *big.Int }
func ConvertType(in interface{}, proto interface{}) interface{} { func ConvertType(in interface{}, proto interface{}) interface{} {
protoType := reflect.TypeOf(proto) protoType := reflect.TypeOf(proto)
if reflect.TypeOf(in).ConvertibleTo(protoType) { if reflect.TypeOf(in).ConvertibleTo(protoType) {
@@ -134,7 +131,7 @@ func setSlice(dst, src reflect.Value) error {
dst.Set(slice) dst.Set(slice)
return nil return nil
} }
return errors.New("cannot set slice, destination not settable") return errors.New("Cannot set slice, destination not settable")
} }
func setArray(dst, src reflect.Value) error { func setArray(dst, src reflect.Value) error {
@@ -155,7 +152,7 @@ func setArray(dst, src reflect.Value) error {
dst.Set(array) dst.Set(array)
return nil return nil
} }
return errors.New("cannot set array, destination not settable") return errors.New("Cannot set array, destination not settable")
} }
func setStruct(dst, src reflect.Value) error { func setStruct(dst, src reflect.Value) error {
@@ -163,7 +160,7 @@ func setStruct(dst, src reflect.Value) error {
srcField := src.Field(i) srcField := src.Field(i)
dstField := dst.Field(i) dstField := dst.Field(i)
if !dstField.IsValid() || !srcField.IsValid() { if !dstField.IsValid() || !srcField.IsValid() {
return fmt.Errorf("could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField)
} }
if err := set(dstField, srcField); err != nil { if err := set(dstField, srcField); err != nil {
return err return err
@@ -173,13 +170,11 @@ func setStruct(dst, src reflect.Value) error {
} }
// mapArgNamesToStructFields maps a slice of argument names to struct fields. // mapArgNamesToStructFields maps a slice of argument names to struct fields.
// // first round: for each Exportable field that contains a `abi:""` tag
// first round: for each Exportable field that contains a `abi:""` tag and this field name // and this field name exists in the given argument name list, pair them together.
// exists in the given argument name list, pair them together. // second round: for each argument name that has not been already linked,
// // find what variable is expected to be mapped into, if it exists and has not been
// second round: for each argument name that has not been already linked, find what // used, pair them.
// variable is expected to be mapped into, if it exists and has not been used, pair them.
//
// Note this function assumes the given value is a struct value. // Note this function assumes the given value is a struct value.
func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) { func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) {
typ := value.Type() typ := value.Type()
@@ -228,7 +223,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
structFieldName := ToCamelCase(argName) structFieldName := ToCamelCase(argName)
if structFieldName == "" { if structFieldName == "" {
return nil, errors.New("abi: purely underscored output cannot unpack to struct") return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
} }
// this abi has already been paired, skip it... unless there exists another, yet unassigned // this abi has already been paired, skip it... unless there exists another, yet unassigned

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ import (
"reflect" "reflect"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
@@ -42,7 +41,8 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
case common.Address: case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:]) copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int: case *big.Int:
copy(topic[:], math.U256Bytes(rule)) blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool: case bool:
if rule { if rule {
topic[common.HashLength-1] = 1 topic[common.HashLength-1] = 1
@@ -75,7 +75,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
copy(topic[:], hash[:]) copy(topic[:], hash[:])
default: default:
// todo(rjl493456442) according to solidity documentation, indexed event // todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not // parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored. // stored directly but instead a keccak256-hash of an encoding is stored.
// //

View File

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

View File

@@ -70,7 +70,7 @@ var (
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) { func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
// check that array brackets are equal if they exist // check that array brackets are equal if they exist
if strings.Count(t, "[") != strings.Count(t, "]") { if strings.Count(t, "[") != strings.Count(t, "]") {
return Type{}, errors.New("invalid arg type in abi") return Type{}, fmt.Errorf("invalid arg type in abi")
} }
typ.stringKind = t typ.stringKind = t
@@ -109,7 +109,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
} }
typ.stringKind = embeddedType.stringKind + sliced typ.stringKind = embeddedType.stringKind + sliced
} else { } else {
return Type{}, errors.New("invalid formatting of array type") return Type{}, fmt.Errorf("invalid formatting of array type")
} }
return typ, err return typ, err
} }
@@ -154,9 +154,6 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
if varSize == 0 { if varSize == 0 {
typ.T = BytesTy typ.T = BytesTy
} else { } else {
if varSize > 32 {
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
}
typ.T = FixedBytesTy typ.T = FixedBytesTy
typ.Size = varSize typ.Size = varSize
} }
@@ -348,7 +345,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
} }
} }
// requiresLengthPrefix returns whether the type requires any sort of length // requireLengthPrefix returns whether the type requires any sort of length
// prefixing. // prefixing.
func (t Type) requiresLengthPrefix() bool { func (t Type) requiresLengthPrefix() bool {
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy return t.T == StringTy || t.T == BytesTy || t.T == SliceTy

View File

@@ -31,7 +31,6 @@ type typeWithoutStringer Type
// Tests that all allowed types get recognized by the type parser. // Tests that all allowed types get recognized by the type parser.
func TestTypeRegexp(t *testing.T) { func TestTypeRegexp(t *testing.T) {
t.Parallel()
tests := []struct { tests := []struct {
blob string blob string
components []ArgumentMarshaling components []ArgumentMarshaling
@@ -118,7 +117,6 @@ func TestTypeRegexp(t *testing.T) {
} }
func TestTypeCheck(t *testing.T) { func TestTypeCheck(t *testing.T) {
t.Parallel()
for i, test := range []struct { for i, test := range []struct {
typ string typ string
components []ArgumentMarshaling components []ArgumentMarshaling
@@ -310,7 +308,6 @@ func TestTypeCheck(t *testing.T) {
} }
func TestInternalType(t *testing.T) { func TestInternalType(t *testing.T) {
t.Parallel()
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}} components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
internalType := "struct a.b[]" internalType := "struct a.b[]"
kind := Type{ kind := Type{
@@ -335,7 +332,6 @@ func TestInternalType(t *testing.T) {
} }
func TestGetTypeSize(t *testing.T) { func TestGetTypeSize(t *testing.T) {
t.Parallel()
var testCases = []struct { var testCases = []struct {
typ string typ string
components []ArgumentMarshaling components []ArgumentMarshaling
@@ -370,11 +366,3 @@ 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,9 +18,7 @@ package abi
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"reflect" "reflect"
@@ -35,72 +33,43 @@ var (
) )
// ReadInteger reads the integer based on its kind and returns the appropriate value. // ReadInteger reads the integer based on its kind and returns the appropriate value.
func ReadInteger(typ Type, b []byte) (interface{}, error) { func ReadInteger(typ Type, b []byte) interface{} {
ret := new(big.Int).SetBytes(b)
if typ.T == UintTy { if typ.T == UintTy {
u64, isu64 := ret.Uint64(), ret.IsUint64()
switch typ.Size { switch typ.Size {
case 8: case 8:
if !isu64 || u64 > math.MaxUint8 { return b[len(b)-1]
return nil, errBadUint8
}
return byte(u64), nil
case 16: case 16:
if !isu64 || u64 > math.MaxUint16 { return binary.BigEndian.Uint16(b[len(b)-2:])
return nil, errBadUint16
}
return uint16(u64), nil
case 32: case 32:
if !isu64 || u64 > math.MaxUint32 { return binary.BigEndian.Uint32(b[len(b)-4:])
return nil, errBadUint32
}
return uint32(u64), nil
case 64: case 64:
if !isu64 { return binary.BigEndian.Uint64(b[len(b)-8:])
return nil, errBadUint64
}
return u64, nil
default: default:
// the only case left for unsigned integer is uint256. // the only case left for unsigned integer is uint256.
return ret, nil return new(big.Int).SetBytes(b)
} }
} }
// big.SetBytes can't tell if a number is negative or positive in itself.
// On EVM, if the returned number > max int256, it is negative.
// A number is > max int256 if the bit at position 255 is set.
if ret.Bit(255) == 1 {
ret.Add(MaxUint256, new(big.Int).Neg(ret))
ret.Add(ret, common.Big1)
ret.Neg(ret)
}
i64, isi64 := ret.Int64(), ret.IsInt64()
switch typ.Size { switch typ.Size {
case 8: case 8:
if !isi64 || i64 < math.MinInt8 || i64 > math.MaxInt8 { return int8(b[len(b)-1])
return nil, errBadInt8
}
return int8(i64), nil
case 16: case 16:
if !isi64 || i64 < math.MinInt16 || i64 > math.MaxInt16 { return int16(binary.BigEndian.Uint16(b[len(b)-2:]))
return nil, errBadInt16
}
return int16(i64), nil
case 32: case 32:
if !isi64 || i64 < math.MinInt32 || i64 > math.MaxInt32 { return int32(binary.BigEndian.Uint32(b[len(b)-4:]))
return nil, errBadInt32
}
return int32(i64), nil
case 64: case 64:
if !isi64 { return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
return nil, errBadInt64
}
return i64, nil
default: default:
// the only case left for integer is int256 // the only case left for integer is int256
// big.SetBytes can't tell if a number is negative or positive in itself.
return ret, nil // On EVM, if the returned number > max int256, it is negative.
// A number is > max int256 if the bit at position 255 is set.
ret := new(big.Int).SetBytes(b)
if ret.Bit(255) == 1 {
ret.Add(MaxUint256, new(big.Int).Neg(ret))
ret.Add(ret, common.Big1)
ret.Neg(ret)
}
return ret
} }
} }
@@ -126,7 +95,7 @@ func readBool(word []byte) (bool, error) {
// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) // readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
if t.T != FunctionTy { if t.T != FunctionTy {
return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array") return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
} }
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
@@ -139,7 +108,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
// ReadFixedBytes uses reflection to create a fixed array to be read from. // ReadFixedBytes uses reflection to create a fixed array to be read from.
func ReadFixedBytes(t Type, word []byte) (interface{}, error) { func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
if t.T != FixedBytesTy { if t.T != FixedBytesTy {
return nil, errors.New("abi: invalid type in call to make fixed byte array") return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
} }
// convert // convert
array := reflect.New(t.GetType()).Elem() array := reflect.New(t.GetType()).Elem()
@@ -154,21 +123,20 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size)
} }
if start+32*size > len(output) { if start+32*size > len(output) {
return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
} }
// this value will become our slice or our array, depending on the type // this value will become our slice or our array, depending on the type
var refSlice reflect.Value var refSlice reflect.Value
switch t.T { if t.T == SliceTy {
case SliceTy:
// declare our slice // declare our slice
refSlice = reflect.MakeSlice(t.GetType(), size, size) refSlice = reflect.MakeSlice(t.GetType(), size, size)
case ArrayTy: } else if t.T == ArrayTy {
// declare our array // declare our array
refSlice = reflect.New(t.GetType()).Elem() refSlice = reflect.New(t.GetType()).Elem()
default: } else {
return nil, errors.New("abi: invalid type in array/slice unpacking stage") return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
} }
// Arrays have packed elements, resulting in longer unpack steps. // Arrays have packed elements, resulting in longer unpack steps.
@@ -194,9 +162,6 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) {
virtualArgs := 0 virtualArgs := 0
for index, elem := range t.TupleElems { for index, elem := range t.TupleElems {
marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output)
if err != nil {
return nil, err
}
if elem.T == ArrayTy && !isDynamicType(*elem) { if elem.T == ArrayTy && !isDynamicType(*elem) {
// If we have a static array, like [3]uint256, these are coded as // If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256. // just like uint256,uint256,uint256.
@@ -214,6 +179,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) {
// coded as just like uint256,bool,uint256 // coded as just like uint256,bool,uint256
virtualArgs += getTypeSize(*elem)/32 - 1 virtualArgs += getTypeSize(*elem)/32 - 1
} }
if err != nil {
return nil, err
}
retval.Field(index).Set(reflect.ValueOf(marshalledValue)) retval.Field(index).Set(reflect.ValueOf(marshalledValue))
} }
return retval.Interface(), nil return retval.Interface(), nil
@@ -266,7 +234,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
case StringTy: // variable arrays are written at the end of the return bytes case StringTy: // variable arrays are written at the end of the return bytes
return string(output[begin : begin+length]), nil return string(output[begin : begin+length]), nil
case IntTy, UintTy: case IntTy, UintTy:
return ReadInteger(t, returnOutput) return ReadInteger(t, returnOutput), nil
case BoolTy: case BoolTy:
return readBool(returnOutput) return readBool(returnOutput)
case AddressTy: case AddressTy:

View File

@@ -20,7 +20,6 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"math"
"math/big" "math/big"
"reflect" "reflect"
"strconv" "strconv"
@@ -33,7 +32,6 @@ import (
// TestUnpack tests the general pack/unpack tests in packing_test.go // TestUnpack tests the general pack/unpack tests in packing_test.go
func TestUnpack(t *testing.T) { func TestUnpack(t *testing.T) {
t.Parallel()
for i, test := range packUnpackTests { for i, test := range packUnpackTests {
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) { t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
//Unpack //Unpack
@@ -207,13 +205,13 @@ var unpackTests = []unpackTest{
def: `[{"type":"bool"}]`, def: `[{"type":"bool"}]`,
enc: "", enc: "",
want: false, want: false,
err: "abi: attempting to unmarshal an empty string while arguments are expected", err: "abi: attempting to unmarshall an empty string while arguments are expected",
}, },
{ {
def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`, def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`,
enc: "", enc: "",
want: false, want: false,
err: "abi: attempting to unmarshal an empty string while arguments are expected", err: "abi: attempting to unmarshall an empty string while arguments are expected",
}, },
{ {
def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`, def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`,
@@ -225,7 +223,6 @@ var unpackTests = []unpackTest{
// TestLocalUnpackTests runs test specially designed only for unpacking. // TestLocalUnpackTests runs test specially designed only for unpacking.
// All test cases that can be used to test packing and unpacking should move to packing_test.go // All test cases that can be used to test packing and unpacking should move to packing_test.go
func TestLocalUnpackTests(t *testing.T) { func TestLocalUnpackTests(t *testing.T) {
t.Parallel()
for i, test := range unpackTests { for i, test := range unpackTests {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
//Unpack //Unpack
@@ -253,7 +250,6 @@ func TestLocalUnpackTests(t *testing.T) {
} }
func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) {
t.Parallel()
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`)) abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -324,7 +320,6 @@ func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOut
} }
func TestMethodMultiReturn(t *testing.T) { func TestMethodMultiReturn(t *testing.T) {
t.Parallel()
type reversed struct { type reversed struct {
String string String string
Int *big.Int Int *big.Int
@@ -404,7 +399,6 @@ func TestMethodMultiReturn(t *testing.T) {
} }
func TestMultiReturnWithArray(t *testing.T) { func TestMultiReturnWithArray(t *testing.T) {
t.Parallel()
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
@@ -428,7 +422,6 @@ func TestMultiReturnWithArray(t *testing.T) {
} }
func TestMultiReturnWithStringArray(t *testing.T) { func TestMultiReturnWithStringArray(t *testing.T) {
t.Parallel()
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
@@ -459,7 +452,6 @@ func TestMultiReturnWithStringArray(t *testing.T) {
} }
func TestMultiReturnWithStringSlice(t *testing.T) { func TestMultiReturnWithStringSlice(t *testing.T) {
t.Parallel()
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
abi, err := JSON(strings.NewReader(definition)) abi, err := JSON(strings.NewReader(definition))
if err != nil { if err != nil {
@@ -492,7 +484,6 @@ func TestMultiReturnWithStringSlice(t *testing.T) {
} }
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
t.Parallel()
// Similar to TestMultiReturnWithArray, but with a special case in mind: // Similar to TestMultiReturnWithArray, but with a special case in mind:
// values of nested static arrays count towards the size as well, and any element following // values of nested static arrays count towards the size as well, and any element following
// after such nested array argument should be read with the correct offset, // after such nested array argument should be read with the correct offset,
@@ -533,7 +524,6 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
} }
func TestUnmarshal(t *testing.T) { func TestUnmarshal(t *testing.T) {
t.Parallel()
const definition = `[ const definition = `[
{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] }, { "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] }, { "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
@@ -783,7 +773,6 @@ func TestUnmarshal(t *testing.T) {
} }
func TestUnpackTuple(t *testing.T) { func TestUnpackTuple(t *testing.T) {
t.Parallel()
const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
abi, err := JSON(strings.NewReader(simpleTuple)) abi, err := JSON(strings.NewReader(simpleTuple))
if err != nil { if err != nil {
@@ -886,7 +875,6 @@ func TestUnpackTuple(t *testing.T) {
} }
func TestOOMMaliciousInput(t *testing.T) { func TestOOMMaliciousInput(t *testing.T) {
t.Parallel()
oomTests := []unpackTest{ oomTests := []unpackTest{
{ {
def: `[{"type": "uint8[]"}]`, def: `[{"type": "uint8[]"}]`,
@@ -955,165 +943,3 @@ func TestOOMMaliciousInput(t *testing.T) {
} }
} }
} }
func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
t.Parallel()
var encodeABI Arguments
uint256Ty, err := NewType("uint256", "", nil)
if err != nil {
panic(err)
}
encodeABI = Arguments{
{Type: uint256Ty},
}
maxU64, ok := new(big.Int).SetString(strconv.FormatUint(math.MaxUint64, 10), 10)
if !ok {
panic("bug")
}
maxU64Plus1 := new(big.Int).Add(maxU64, big.NewInt(1))
cases := []struct {
decodeType string
inputValue *big.Int
err error
expectValue interface{}
}{
{
decodeType: "uint8",
inputValue: big.NewInt(math.MaxUint8 + 1),
err: errBadUint8,
},
{
decodeType: "uint8",
inputValue: big.NewInt(math.MaxUint8),
err: nil,
expectValue: uint8(math.MaxUint8),
},
{
decodeType: "uint16",
inputValue: big.NewInt(math.MaxUint16 + 1),
err: errBadUint16,
},
{
decodeType: "uint16",
inputValue: big.NewInt(math.MaxUint16),
err: nil,
expectValue: uint16(math.MaxUint16),
},
{
decodeType: "uint32",
inputValue: big.NewInt(math.MaxUint32 + 1),
err: errBadUint32,
},
{
decodeType: "uint32",
inputValue: big.NewInt(math.MaxUint32),
err: nil,
expectValue: uint32(math.MaxUint32),
},
{
decodeType: "uint64",
inputValue: maxU64Plus1,
err: errBadUint64,
},
{
decodeType: "uint64",
inputValue: maxU64,
err: nil,
expectValue: uint64(math.MaxUint64),
},
{
decodeType: "uint256",
inputValue: maxU64Plus1,
err: nil,
expectValue: maxU64Plus1,
},
{
decodeType: "int8",
inputValue: big.NewInt(math.MaxInt8 + 1),
err: errBadInt8,
},
{
decodeType: "int8",
inputValue: big.NewInt(math.MinInt8 - 1),
err: errBadInt8,
},
{
decodeType: "int8",
inputValue: big.NewInt(math.MaxInt8),
err: nil,
expectValue: int8(math.MaxInt8),
},
{
decodeType: "int16",
inputValue: big.NewInt(math.MaxInt16 + 1),
err: errBadInt16,
},
{
decodeType: "int16",
inputValue: big.NewInt(math.MinInt16 - 1),
err: errBadInt16,
},
{
decodeType: "int16",
inputValue: big.NewInt(math.MaxInt16),
err: nil,
expectValue: int16(math.MaxInt16),
},
{
decodeType: "int32",
inputValue: big.NewInt(math.MaxInt32 + 1),
err: errBadInt32,
},
{
decodeType: "int32",
inputValue: big.NewInt(math.MinInt32 - 1),
err: errBadInt32,
},
{
decodeType: "int32",
inputValue: big.NewInt(math.MaxInt32),
err: nil,
expectValue: int32(math.MaxInt32),
},
{
decodeType: "int64",
inputValue: new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)),
err: errBadInt64,
},
{
decodeType: "int64",
inputValue: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)),
err: errBadInt64,
},
{
decodeType: "int64",
inputValue: big.NewInt(math.MaxInt64),
err: nil,
expectValue: int64(math.MaxInt64),
},
}
for i, testCase := range cases {
packed, err := encodeABI.Pack(testCase.inputValue)
if err != nil {
panic(err)
}
ty, err := NewType(testCase.decodeType, "", nil)
if err != nil {
panic(err)
}
decodeABI := Arguments{
{Type: ty},
}
decoded, err := decodeABI.Unpack(packed)
if err != testCase.err {
t.Fatalf("Expected error %v, actual error %v. case %d", testCase.err, err, i)
}
if err != nil {
continue
}
if !reflect.DeepEqual(decoded[0], testCase.expectValue) {
t.Fatalf("Expected value %v, actual value %v", testCase.expectValue, decoded[0])
}
}
}

View File

@@ -21,14 +21,15 @@ import "fmt"
// ResolveNameConflict returns the next available name for a given thing. // ResolveNameConflict returns the next available name for a given thing.
// This helper can be used for lots of purposes: // This helper can be used for lots of purposes:
// //
// - In solidity function overloading is supported, this function can fix // - In solidity function overloading is supported, this function can fix
// the name conflicts of overloaded functions. // the name conflicts of overloaded functions.
// - In golang binding generation, the parameter(in function, event, error, // - In golang binding generation, the parameter(in function, event, error,
// and struct definition) name will be converted to camelcase style which // and struct definition) name will be converted to camelcase style which
// may eventually lead to name conflicts. // may eventually lead to name conflicts.
// //
// Name conflicts are mostly resolved by adding number suffix. e.g. if the abi contains // Name conflicts are mostly resolved by adding number suffix.
// Methods "send" and "send1", ResolveNameConflict would return "send2" for input "send". // e.g. if the abi contains Methods send, send1
// ResolveNameConflict would return send2 for input send.
func ResolveNameConflict(rawName string, used func(string) bool) string { func ResolveNameConflict(rawName string, used func(string) bool) string {
name := rawName name := rawName
ok := used(name) ok := used(name)

View File

@@ -177,8 +177,7 @@ type Backend interface {
// safely used to calculate a signature from. // safely used to calculate a signature from.
// //
// The hash is calculated as // The hash is calculated as
// // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
// //
// This gives context to the signed message and prevents signing of transactions. // This gives context to the signed message and prevents signing of transactions.
func TextHash(data []byte) []byte { func TextHash(data []byte) []byte {
@@ -190,8 +189,7 @@ func TextHash(data []byte) []byte {
// safely used to calculate a signature from. // safely used to calculate a signature from.
// //
// The hash is calculated as // The hash is calculated as
// // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
// //
// This gives context to the signed message and prevents signing of transactions. // This gives context to the signed message and prevents signing of transactions.
func TextAndHash(data []byte) ([]byte, string) { func TextAndHash(data []byte) ([]byte, string) {

View File

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

View File

@@ -17,7 +17,6 @@
package external package external
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
"sync" "sync"
@@ -99,11 +98,11 @@ func (api *ExternalSigner) Status() (string, error) {
} }
func (api *ExternalSigner) Open(passphrase string) error { func (api *ExternalSigner) Open(passphrase string) error {
return errors.New("operation not supported on external signers") return fmt.Errorf("operation not supported on external signers")
} }
func (api *ExternalSigner) Close() error { func (api *ExternalSigner) Close() error {
return errors.New("operation not supported on external signers") return fmt.Errorf("operation not supported on external signers")
} }
func (api *ExternalSigner) Accounts() []accounts.Account { func (api *ExternalSigner) Accounts() []accounts.Account {
@@ -146,7 +145,7 @@ func (api *ExternalSigner) Contains(account accounts.Account) bool {
} }
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
return accounts.Account{}, errors.New("operation not supported on external signers") return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
} }
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
@@ -243,14 +242,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
} }
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
return []byte{}, errors.New("password-operations not supported on external signers") return []byte{}, fmt.Errorf("password-operations not supported on external signers")
} }
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
return nil, errors.New("password-operations not supported on external signers") return nil, fmt.Errorf("password-operations not supported on external signers")
} }
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
return nil, errors.New("password-operations not supported on external signers") return nil, fmt.Errorf("password-operations not supported on external signers")
} }
func (api *ExternalSigner) listAccounts() ([]common.Address, error) { func (api *ExternalSigner) listAccounts() ([]common.Address, error) {

View File

@@ -46,7 +46,7 @@ var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki // The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
// defines derivation paths to be of the form: // defines derivation paths to be of the form:
// //
// m / purpose' / coin_type' / account' / change / address_index // m / purpose' / coin_type' / account' / change / address_index
// //
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki // The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and // defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and

View File

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

View File

@@ -27,11 +27,10 @@ import (
"sync" "sync"
"time" "time"
mapset "github.com/deckarep/golang-set/v2" mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"golang.org/x/exp/slices"
) )
// Minimum amount of time between cache reloads. This limit applies if the platform does // Minimum amount of time between cache reloads. This limit applies if the platform does
@@ -39,10 +38,11 @@ import (
// exist yet, the code will attempt to create a watcher at most this often. // exist yet, the code will attempt to create a watcher at most this often.
const minReloadInterval = 2 * time.Second const minReloadInterval = 2 * time.Second
// byURL defines the sorting order for accounts. type accountsByURL []accounts.Account
func byURL(a, b accounts.Account) int {
return a.URL.Cmp(b.URL) 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] }
// AmbiguousAddrError is returned when attempting to unlock // AmbiguousAddrError is returned when attempting to unlock
// an address for which more than one file exists. // an address for which more than one file exists.
@@ -67,7 +67,7 @@ type accountCache struct {
keydir string keydir string
watcher *watcher watcher *watcher
mu sync.Mutex mu sync.Mutex
all []accounts.Account all accountsByURL
byAddr map[common.Address][]accounts.Account byAddr map[common.Address][]accounts.Account
throttle *time.Timer throttle *time.Timer
notify chan struct{} notify chan struct{}
@@ -79,7 +79,7 @@ func newAccountCache(keydir string) (*accountCache, chan struct{}) {
keydir: keydir, keydir: keydir,
byAddr: make(map[common.Address][]accounts.Account), byAddr: make(map[common.Address][]accounts.Account),
notify: make(chan struct{}, 1), notify: make(chan struct{}, 1),
fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()}, fileC: fileCache{all: mapset.NewThreadUnsafeSet()},
} }
ac.watcher = newWatcher(ac) ac.watcher = newWatcher(ac)
return ac, ac.notify return ac, ac.notify
@@ -146,14 +146,6 @@ func (ac *accountCache) deleteByFile(path string) {
} }
} }
// watcherStarted returns true if the watcher loop started running (even if it
// has since also ended).
func (ac *accountCache) watcherStarted() bool {
ac.mu.Lock()
defer ac.mu.Unlock()
return ac.watcher.running || ac.watcher.runEnded
}
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
for i := range slice { for i := range slice {
if slice[i] == elem { if slice[i] == elem {
@@ -194,7 +186,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
default: default:
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
copy(err.Matches, matches) copy(err.Matches, matches)
slices.SortFunc(err.Matches, byURL) sort.Sort(accountsByURL(err.Matches))
return accounts.Account{}, err return accounts.Account{}, err
} }
} }
@@ -283,15 +275,16 @@ func (ac *accountCache) scanAccounts() error {
// Process all the file diffs // Process all the file diffs
start := time.Now() start := time.Now()
for _, path := range creates.ToSlice() { for _, p := range creates.ToSlice() {
if a := readAccount(path); a != nil { if a := readAccount(p.(string)); a != nil {
ac.add(*a) ac.add(*a)
} }
} }
for _, path := range deletes.ToSlice() { for _, p := range deletes.ToSlice() {
ac.deleteByFile(path) ac.deleteByFile(p.(string))
} }
for _, path := range updates.ToSlice() { for _, p := range updates.ToSlice() {
path := p.(string)
ac.deleteByFile(path) ac.deleteByFile(path)
if a := readAccount(path); a != nil { if a := readAccount(path); a != nil {
ac.add(*a) ac.add(*a)

View File

@@ -17,12 +17,12 @@
package keystore package keystore
import ( import (
"errors"
"fmt" "fmt"
"math/rand" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"sort"
"testing" "testing"
"time" "time"
@@ -30,7 +30,6 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/slices"
) )
var ( var (
@@ -51,38 +50,6 @@ var (
} }
) )
// waitWatcherStarts waits up to 1s for the keystore watcher to start.
func waitWatcherStart(ks *KeyStore) bool {
// On systems where file watch is not supported, just return "ok".
if !ks.cache.watcher.enabled() {
return true
}
// The watcher should start, and then exit.
for t0 := time.Now(); time.Since(t0) < 1*time.Second; time.Sleep(100 * time.Millisecond) {
if ks.cache.watcherStarted() {
return true
}
}
return false
}
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
var list []accounts.Account
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 errors.New("wasn't notified of new accounts")
}
return nil
}
}
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
}
func TestWatchNewFile(t *testing.T) { func TestWatchNewFile(t *testing.T) {
t.Parallel() t.Parallel()
@@ -90,9 +57,8 @@ func TestWatchNewFile(t *testing.T) {
// Ensure the watcher is started before adding any files. // Ensure the watcher is started before adding any files.
ks.Accounts() ks.Accounts()
if !waitWatcherStart(ks) { time.Sleep(1000 * time.Millisecond)
t.Fatal("keystore watcher didn't start in time")
}
// Move in the files. // Move in the files.
wantAccounts := make([]accounts.Account, len(cachetestAccounts)) wantAccounts := make([]accounts.Account, len(cachetestAccounts))
for i := range cachetestAccounts { for i := range cachetestAccounts {
@@ -106,24 +72,37 @@ func TestWatchNewFile(t *testing.T) {
} }
// ks should see the accounts. // ks should see the accounts.
if err := waitForAccounts(wantAccounts, ks); err != nil { var list []accounts.Account
t.Error(err) for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
list = ks.Accounts()
if reflect.DeepEqual(list, wantAccounts) {
// ks should have also received change notifications
select {
case <-ks.changes:
default:
t.Fatalf("wasn't notified of new accounts")
}
return
}
time.Sleep(d)
} }
t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
} }
func TestWatchNoDir(t *testing.T) { func TestWatchNoDir(t *testing.T) {
t.Parallel() t.Parallel()
// Create ks but not the directory that it watches. // Create ks but not the directory that it watches.
rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int())) dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int()))
ks := NewKeyStore(dir, LightScryptN, LightScryptP) ks := NewKeyStore(dir, LightScryptN, LightScryptP)
list := ks.Accounts() list := ks.Accounts()
if len(list) > 0 { if len(list) > 0 {
t.Error("initial account list not empty:", list) t.Error("initial account list not empty:", list)
} }
// The watcher should start, and then exit. time.Sleep(100 * time.Millisecond)
if !waitWatcherStart(ks) {
t.Fatal("keystore watcher didn't start in time")
}
// Create the directory and copy a key file into it. // Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700) os.MkdirAll(dir, 0700)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
@@ -152,7 +131,6 @@ func TestWatchNoDir(t *testing.T) {
} }
func TestCacheInitialReload(t *testing.T) { func TestCacheInitialReload(t *testing.T) {
t.Parallel()
cache, _ := newAccountCache(cachetestDir) cache, _ := newAccountCache(cachetestDir)
accounts := cache.accounts() accounts := cache.accounts()
if !reflect.DeepEqual(accounts, cachetestAccounts) { if !reflect.DeepEqual(accounts, cachetestAccounts) {
@@ -161,7 +139,6 @@ func TestCacheInitialReload(t *testing.T) {
} }
func TestCacheAddDeleteOrder(t *testing.T) { func TestCacheAddDeleteOrder(t *testing.T) {
t.Parallel()
cache, _ := newAccountCache("testdata/no-such-dir") cache, _ := newAccountCache("testdata/no-such-dir")
cache.watcher.running = true // prevent unexpected reloads cache.watcher.running = true // prevent unexpected reloads
@@ -205,7 +182,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
// Check that the account list is sorted by filename. // Check that the account list is sorted by filename.
wantAccounts := make([]accounts.Account, len(accs)) wantAccounts := make([]accounts.Account, len(accs))
copy(wantAccounts, accs) copy(wantAccounts, accs)
slices.SortFunc(wantAccounts, byURL) sort.Sort(accountsByURL(wantAccounts))
list := cache.accounts() list := cache.accounts()
if !reflect.DeepEqual(list, wantAccounts) { if !reflect.DeepEqual(list, wantAccounts) {
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
@@ -246,7 +223,6 @@ func TestCacheAddDeleteOrder(t *testing.T) {
} }
func TestCacheFind(t *testing.T) { func TestCacheFind(t *testing.T) {
t.Parallel()
dir := filepath.Join("testdata", "dir") dir := filepath.Join("testdata", "dir")
cache, _ := newAccountCache(dir) cache, _ := newAccountCache(dir)
cache.watcher.running = true // prevent unexpected reloads cache.watcher.running = true // prevent unexpected reloads
@@ -319,12 +295,31 @@ func TestCacheFind(t *testing.T) {
} }
} }
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
var list []accounts.Account
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
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 nil
}
time.Sleep(d)
}
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
}
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file // TestUpdatedKeyfileContents tests that updating the contents of a keystore file
// is noticed by the watcher, and the account cache is updated accordingly // is noticed by the watcher, and the account cache is updated accordingly
func TestUpdatedKeyfileContents(t *testing.T) { func TestUpdatedKeyfileContents(t *testing.T) {
t.Parallel() t.Parallel()
// Create a temporary keystore to test with // Create a temporary keystore to test with
rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int())) dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int()))
ks := NewKeyStore(dir, LightScryptN, LightScryptP) ks := NewKeyStore(dir, LightScryptN, LightScryptP)
@@ -332,9 +327,8 @@ func TestUpdatedKeyfileContents(t *testing.T) {
if len(list) > 0 { if len(list) > 0 {
t.Error("initial account list not empty:", list) t.Error("initial account list not empty:", list)
} }
if !waitWatcherStart(ks) { time.Sleep(100 * time.Millisecond)
t.Fatal("keystore watcher didn't start in time")
}
// Create the directory and copy a key file into it. // Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700) os.MkdirAll(dir, 0700)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
@@ -352,8 +346,9 @@ func TestUpdatedKeyfileContents(t *testing.T) {
t.Error(err) t.Error(err)
return return
} }
// needed so that modTime of `file` is different to its current value after forceCopyFile // needed so that modTime of `file` is different to its current value after forceCopyFile
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second)) time.Sleep(1000 * time.Millisecond)
// Now replace file contents // Now replace file contents
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
@@ -369,7 +364,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
} }
// needed so that modTime of `file` is different to its current value after forceCopyFile // needed so that modTime of `file` is different to its current value after forceCopyFile
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second)) time.Sleep(1000 * time.Millisecond)
// Now replace file contents again // Now replace file contents again
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
@@ -385,7 +380,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
} }
// needed so that modTime of `file` is different to its current value after os.WriteFile // needed so that modTime of `file` is different to its current value after os.WriteFile
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second)) time.Sleep(1000 * time.Millisecond)
// Now replace file contents with crap // Now replace file contents with crap
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil { if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {

View File

@@ -23,20 +23,20 @@ import (
"sync" "sync"
"time" "time"
mapset "github.com/deckarep/golang-set/v2" mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// fileCache is a cache of files seen during scan of keystore. // fileCache is a cache of files seen during scan of keystore.
type fileCache struct { type fileCache struct {
all mapset.Set[string] // Set of all files from the keystore folder all mapset.Set // Set of all files from the keystore folder
lastMod time.Time // Last time instance when a file was modified lastMod time.Time // Last time instance when a file was modified
mu sync.Mutex mu sync.Mutex
} }
// scan performs a new scan on the given directory, compares against the already // scan performs a new scan on the given directory, compares against the already
// cached filenames, and returns file sets: creates, deletes, updates. // cached filenames, and returns file sets: creates, deletes, updates.
func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string], mapset.Set[string], error) { func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) {
t0 := time.Now() t0 := time.Now()
// List all the files from the keystore folder // List all the files from the keystore folder
@@ -50,8 +50,8 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string]
defer fc.mu.Unlock() defer fc.mu.Unlock()
// Iterate all the files and gather their metadata // Iterate all the files and gather their metadata
all := mapset.NewThreadUnsafeSet[string]() all := mapset.NewThreadUnsafeSet()
mods := mapset.NewThreadUnsafeSet[string]() mods := mapset.NewThreadUnsafeSet()
var newLastMod time.Time var newLastMod time.Time
for _, fi := range files { for _, fi := range files {

View File

@@ -498,14 +498,6 @@ func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (account
return a, nil return a, nil
} }
// isUpdating returns whether the event notification loop is running.
// This method is mainly meant for tests.
func (ks *KeyStore) isUpdating() bool {
ks.mu.RLock()
defer ks.mu.RUnlock()
return ks.updating
}
// zeroKey zeroes a private key in memory. // zeroKey zeroes a private key in memory.
func zeroKey(k *ecdsa.PrivateKey) { func zeroKey(k *ecdsa.PrivateKey) {
b := k.D.Bits() b := k.D.Bits()

View File

@@ -20,6 +20,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -30,13 +31,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"golang.org/x/exp/slices"
) )
var testSigData = make([]byte, 32) var testSigData = make([]byte, 32)
func TestKeyStore(t *testing.T) { func TestKeyStore(t *testing.T) {
t.Parallel()
dir, ks := tmpKeyStore(t, true) dir, ks := tmpKeyStore(t, true)
a, err := ks.NewAccount("foo") a, err := ks.NewAccount("foo")
@@ -71,7 +70,6 @@ func TestKeyStore(t *testing.T) {
} }
func TestSign(t *testing.T) { func TestSign(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true) _, ks := tmpKeyStore(t, true)
pass := "" // not used but required by API pass := "" // not used but required by API
@@ -88,7 +86,6 @@ func TestSign(t *testing.T) {
} }
func TestSignWithPassphrase(t *testing.T) { func TestSignWithPassphrase(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true) _, ks := tmpKeyStore(t, true)
pass := "passwd" pass := "passwd"
@@ -116,7 +113,6 @@ func TestSignWithPassphrase(t *testing.T) {
} }
func TestTimedUnlock(t *testing.T) { func TestTimedUnlock(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true) _, ks := tmpKeyStore(t, true)
pass := "foo" pass := "foo"
@@ -151,7 +147,6 @@ func TestTimedUnlock(t *testing.T) {
} }
func TestOverrideUnlock(t *testing.T) { func TestOverrideUnlock(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false) _, ks := tmpKeyStore(t, false)
pass := "foo" pass := "foo"
@@ -192,7 +187,6 @@ func TestOverrideUnlock(t *testing.T) {
// This test should fail under -race if signing races the expiration goroutine. // This test should fail under -race if signing races the expiration goroutine.
func TestSignRace(t *testing.T) { func TestSignRace(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false) _, ks := tmpKeyStore(t, false)
// Create a test account. // Create a test account.
@@ -217,33 +211,19 @@ func TestSignRace(t *testing.T) {
t.Errorf("Account did not lock within the timeout") t.Errorf("Account did not lock within the timeout")
} }
// waitForKsUpdating waits until the updating-status of the ks reaches the
// desired wantStatus.
// It waits for a maximum time of maxTime, and returns false if it does not
// finish in time
func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time.Duration) bool {
t.Helper()
// Wait max 250 ms, then return false
for t0 := time.Now(); time.Since(t0) < maxTime; {
if ks.isUpdating() == wantStatus {
return true
}
time.Sleep(25 * time.Millisecond)
}
return false
}
// Tests that the wallet notifier loop starts and stops correctly based on the // Tests that the wallet notifier loop starts and stops correctly based on the
// addition and removal of wallet event subscriptions. // addition and removal of wallet event subscriptions.
func TestWalletNotifierLifecycle(t *testing.T) { func TestWalletNotifierLifecycle(t *testing.T) {
t.Parallel()
// Create a temporary keystore to test with // Create a temporary keystore to test with
_, ks := tmpKeyStore(t, false) _, ks := tmpKeyStore(t, false)
// Ensure that the notification updater is not running yet // Ensure that the notification updater is not running yet
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
ks.mu.RLock()
updating := ks.updating
ks.mu.RUnlock()
if ks.isUpdating() { if updating {
t.Errorf("wallet notifier running without subscribers") t.Errorf("wallet notifier running without subscribers")
} }
// Subscribe to the wallet feed and ensure the updater boots up // Subscribe to the wallet feed and ensure the updater boots up
@@ -253,26 +233,38 @@ func TestWalletNotifierLifecycle(t *testing.T) {
for i := 0; i < len(subs); i++ { for i := 0; i < len(subs); i++ {
// Create a new subscription // Create a new subscription
subs[i] = ks.Subscribe(updates) subs[i] = ks.Subscribe(updates)
if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) {
// Ensure the notifier comes online
time.Sleep(250 * time.Millisecond)
ks.mu.RLock()
updating = ks.updating
ks.mu.RUnlock()
if !updating {
t.Errorf("sub %d: wallet notifier not running after subscription", i) t.Errorf("sub %d: wallet notifier not running after subscription", i)
} }
} }
// Close all but one sub // Unsubscribe and ensure the updater terminates eventually
for i := 0; i < len(subs)-1; i++ { for i := 0; i < len(subs); i++ {
// Close an existing subscription // Close an existing subscription
subs[i].Unsubscribe() subs[i].Unsubscribe()
}
// Check that it is still running
time.Sleep(250 * time.Millisecond)
if !ks.isUpdating() { // Ensure the notifier shuts down at and only at the last close
t.Fatal("event notifier stopped prematurely") for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
} ks.mu.RLock()
// Unsubscribe the last one and ensure the updater terminates eventually. updating = ks.updating
subs[len(subs)-1].Unsubscribe() ks.mu.RUnlock()
if !waitForKsUpdating(t, ks, false, 4*time.Second) {
t.Errorf("wallet notifier didn't terminate after unsubscribe") if i < len(subs)-1 && !updating {
t.Fatalf("sub %d: event notifier stopped prematurely", i)
}
if i == len(subs)-1 && !updating {
return
}
time.Sleep(250 * time.Millisecond)
}
} }
t.Errorf("wallet notifier didn't terminate after unsubscribe")
} }
type walletEvent struct { type walletEvent struct {
@@ -283,7 +275,6 @@ type walletEvent struct {
// Tests that wallet notifications and correctly fired when accounts are added // Tests that wallet notifications and correctly fired when accounts are added
// or deleted from the keystore. // or deleted from the keystore.
func TestWalletNotifications(t *testing.T) { func TestWalletNotifications(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, false) _, ks := tmpKeyStore(t, false)
// Subscribe to the wallet feed and collect events. // Subscribe to the wallet feed and collect events.
@@ -345,7 +336,6 @@ func TestWalletNotifications(t *testing.T) {
// TestImportExport tests the import functionality of a keystore. // TestImportExport tests the import functionality of a keystore.
func TestImportECDSA(t *testing.T) { func TestImportECDSA(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true) _, ks := tmpKeyStore(t, true)
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {
@@ -364,7 +354,6 @@ func TestImportECDSA(t *testing.T) {
// TestImportECDSA tests the import and export functionality of a keystore. // TestImportECDSA tests the import and export functionality of a keystore.
func TestImportExport(t *testing.T) { func TestImportExport(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true) _, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old") acc, err := ks.NewAccount("old")
if err != nil { if err != nil {
@@ -393,7 +382,6 @@ func TestImportExport(t *testing.T) {
// TestImportRace tests the keystore on races. // TestImportRace tests the keystore on races.
// This test should fail under -race if importing races. // This test should fail under -race if importing races.
func TestImportRace(t *testing.T) { func TestImportRace(t *testing.T) {
t.Parallel()
_, ks := tmpKeyStore(t, true) _, ks := tmpKeyStore(t, true)
acc, err := ks.NewAccount("old") acc, err := ks.NewAccount("old")
if err != nil { if err != nil {
@@ -404,19 +392,19 @@ func TestImportRace(t *testing.T) {
t.Fatalf("failed to export account: %v", acc) t.Fatalf("failed to export account: %v", acc)
} }
_, ks2 := tmpKeyStore(t, true) _, ks2 := tmpKeyStore(t, true)
var atom atomic.Uint32 var atom uint32
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
go func() { go func() {
defer wg.Done() defer wg.Done()
if _, err := ks2.Import(json, "new", "new"); err != nil { if _, err := ks2.Import(json, "new", "new"); err != nil {
atom.Add(1) atomic.AddUint32(&atom, 1)
} }
}() }()
} }
wg.Wait() wg.Wait()
if atom.Load() != 1 { if atom != 1 {
t.Errorf("Import is racy") t.Errorf("Import is racy")
} }
} }
@@ -431,7 +419,7 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle
for _, account := range live { for _, account := range live {
liveList = append(liveList, account) liveList = append(liveList, account)
} }
slices.SortFunc(liveList, byURL) sort.Sort(accountsByURL(liveList))
for j, wallet := range wallets { for j, wallet := range wallets {
if accs := wallet.Accounts(); len(accs) != 1 { if accs := wallet.Accounts(); len(accs) != 1 {
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))

View File

@@ -136,7 +136,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
return filepath.Join(ks.keysDirPath, filename) return filepath.Join(ks.keysDirPath, filename)
} }
// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'. // Encryptdata encrypts the data given as 'data' with the password 'auth'.
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) { func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
salt := make([]byte, 32) salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil { if _, err := io.ReadFull(rand.Reader, salt); err != nil {
@@ -225,13 +225,10 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
key, err := crypto.ToECDSA(keyBytes) key := crypto.ToECDSAUnsafe(keyBytes)
if err != nil {
return nil, fmt.Errorf("invalid key: %w", err)
}
id, err := uuid.FromBytes(keyId) id, err := uuid.FromBytes(keyId)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid UUID: %w", err) return nil, err
} }
return &Key{ return &Key{
Id: id, Id: id,

View File

@@ -30,7 +30,6 @@ const (
// Tests that a json key file can be decrypted and encrypted in multiple rounds. // Tests that a json key file can be decrypted and encrypted in multiple rounds.
func TestKeyEncryptDecrypt(t *testing.T) { func TestKeyEncryptDecrypt(t *testing.T) {
t.Parallel()
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json") keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -55,7 +54,7 @@ func TestKeyEncryptDecrypt(t *testing.T) {
// Recrypt with a new password and start over // Recrypt with a new password and start over
password += "new data appended" // nolint: gosec password += "new data appended" // nolint: gosec
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil { if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
t.Errorf("test %d: failed to re-encrypt key %v", i, err) t.Errorf("test %d: failed to recrypt key %v", i, err)
} }
} }
} }

View File

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

View File

@@ -20,31 +20,28 @@
package keystore package keystore
import ( import (
"os"
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/fsnotify/fsnotify" "github.com/rjeczalik/notify"
) )
type watcher struct { type watcher struct {
ac *accountCache ac *accountCache
running bool // set to true when runloop begins starting bool
runEnded bool // set to true when runloop ends running bool
starting bool // set to true prior to runloop starting ev chan notify.EventInfo
quit chan struct{} quit chan struct{}
} }
func newWatcher(ac *accountCache) *watcher { func newWatcher(ac *accountCache) *watcher {
return &watcher{ return &watcher{
ac: ac, ac: ac,
ev: make(chan notify.EventInfo, 10),
quit: make(chan struct{}), quit: make(chan struct{}),
} }
} }
// enabled returns false on systems not supported.
func (*watcher) enabled() bool { return true }
// starts the watcher loop in the background. // starts the watcher loop in the background.
// Start a watcher in the background if that's not already in progress. // Start a watcher in the background if that's not already in progress.
// The caller must hold w.ac.mu. // The caller must hold w.ac.mu.
@@ -65,26 +62,16 @@ func (w *watcher) loop() {
w.ac.mu.Lock() w.ac.mu.Lock()
w.running = false w.running = false
w.starting = false w.starting = false
w.runEnded = true
w.ac.mu.Unlock() w.ac.mu.Unlock()
}() }()
logger := log.New("path", w.ac.keydir) logger := log.New("path", w.ac.keydir)
// Create new watcher. if err := notify.Watch(w.ac.keydir, w.ev, notify.All); err != nil {
watcher, err := fsnotify.NewWatcher() logger.Trace("Failed to watch keystore folder", "err", err)
if err != nil {
log.Error("Failed to start filesystem watcher", "err", err)
return return
} }
defer watcher.Close() defer notify.Stop(w.ev)
if err := watcher.Add(w.ac.keydir); err != nil { logger.Trace("Started watching keystore folder")
if !os.IsNotExist(err) {
logger.Warn("Failed to watch keystore folder", "err", err)
}
return
}
logger.Trace("Started watching keystore folder", "folder", w.ac.keydir)
defer logger.Trace("Stopped watching keystore folder") defer logger.Trace("Stopped watching keystore folder")
w.ac.mu.Lock() w.ac.mu.Lock()
@@ -108,24 +95,12 @@ func (w *watcher) loop() {
select { select {
case <-w.quit: case <-w.quit:
return return
case _, ok := <-watcher.Events: case <-w.ev:
if !ok {
return
}
// Trigger the scan (with delay), if not already triggered // Trigger the scan (with delay), if not already triggered
if !rescanTriggered { if !rescanTriggered {
debounce.Reset(debounceDuration) debounce.Reset(debounceDuration)
rescanTriggered = true rescanTriggered = true
} }
// The fsnotify library does provide more granular event-info, it
// would be possible to refresh individual affected files instead
// of scheduling a full rescan. For most cases though, the
// full rescan is quick and obviously simplest.
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Info("Filesystem watcher error", "err", err)
case <-debounce.C: case <-debounce.C:
w.ac.scanAccounts() w.ac.scanAccounts()
rescanTriggered = false rescanTriggered = false

View File

@@ -22,14 +22,8 @@
package keystore package keystore
type watcher struct { type watcher struct{ running bool }
running bool
runEnded bool
}
func newWatcher(*accountCache) *watcher { return new(watcher) } func newWatcher(*accountCache) *watcher { return new(watcher) }
func (*watcher) start() {} func (*watcher) start() {}
func (*watcher) close() {} func (*watcher) close() {}
// enabled returns false on systems not supported.
func (*watcher) enabled() bool { return false }

View File

@@ -98,9 +98,6 @@ func NewManager(config *Config, backends ...Backend) *Manager {
// Close terminates the account manager's internal notification processes. // Close terminates the account manager's internal notification processes.
func (am *Manager) Close() error { func (am *Manager) Close() error {
for _, w := range am.wallets {
w.Close()
}
errc := make(chan error) errc := make(chan error)
am.quit <- errc am.quit <- errc
return <-errc return <-errc
@@ -260,7 +257,7 @@ func merge(slice []Wallet, wallets ...Wallet) []Wallet {
return slice return slice
} }
// drop is the counterpart of merge, which looks up wallets from within the sorted // drop is the couterpart of merge, which looks up wallets from within the sorted
// cache and removes the ones specified. // cache and removes the ones specified.
func drop(slice []Wallet, wallets ...Wallet) []Wallet { func drop(slice []Wallet, wallets ...Wallet) []Wallet {
for _, wallet := range wallets { for _, wallet := range wallets {

View File

@@ -8,7 +8,7 @@
## Preparing the smartcard ## Preparing the smartcard
**WARNING: FOLLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS** **WARNING: FOILLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap) You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)

View File

@@ -24,7 +24,6 @@ import (
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@@ -126,7 +125,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
// Unpair disestablishes an existing pairing. // Unpair disestablishes an existing pairing.
func (s *SecureChannelSession) Unpair() error { func (s *SecureChannelSession) Unpair() error {
if s.PairingKey == nil { if s.PairingKey == nil {
return errors.New("cannot unpair: not paired") return fmt.Errorf("cannot unpair: not paired")
} }
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{}) _, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
@@ -142,7 +141,7 @@ func (s *SecureChannelSession) Unpair() error {
// Open initializes the secure channel. // Open initializes the secure channel.
func (s *SecureChannelSession) Open() error { func (s *SecureChannelSession) Open() error {
if s.iv != nil { if s.iv != nil {
return errors.New("session already opened") return fmt.Errorf("session already opened")
} }
response, err := s.open() response, err := s.open()
@@ -216,7 +215,7 @@ func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error
// transmitEncrypted sends an encrypted message, and decrypts and returns the response. // transmitEncrypted sends an encrypted message, and decrypts and returns the response.
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) { func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
if s.iv == nil { if s.iv == nil {
return nil, errors.New("channel not open") return nil, fmt.Errorf("channel not open")
} }
data, err := s.encryptAPDU(data) data, err := s.encryptAPDU(data)
@@ -255,7 +254,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b
return nil, err return nil, err
} }
if !bytes.Equal(s.iv, rmac) { if !bytes.Equal(s.iv, rmac) {
return nil, errors.New("invalid MAC in response") return nil, fmt.Errorf("invalid MAC in response")
} }
rapdu := &responseAPDU{} rapdu := &responseAPDU{}
@@ -320,7 +319,7 @@ func unpad(data []byte, terminator byte) ([]byte, error) {
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i]) return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
} }
} }
return nil, errors.New("expected end of padding, got 0") return nil, fmt.Errorf("expected end of padding, got 0")
} }
// updateIV is an internal method that updates the initialization vector after // updateIV is an internal method that updates the initialization vector after

View File

@@ -99,8 +99,8 @@ const (
P1DeriveKeyFromCurrent = uint8(0x10) P1DeriveKeyFromCurrent = uint8(0x10)
statusP1WalletStatus = uint8(0x00) statusP1WalletStatus = uint8(0x00)
statusP1Path = uint8(0x01) statusP1Path = uint8(0x01)
signP1PrecomputedHash = uint8(0x00) signP1PrecomputedHash = uint8(0x01)
signP2OnlyBlock = uint8(0x00) signP2OnlyBlock = uint8(0x81)
exportP1Any = uint8(0x00) exportP1Any = uint8(0x00)
exportP2Pubkey = uint8(0x01) exportP2Pubkey = uint8(0x01)
) )
@@ -252,7 +252,7 @@ func (w *Wallet) release() error {
// with the wallet. // with the wallet.
func (w *Wallet) pair(puk []byte) error { func (w *Wallet) pair(puk []byte) error {
if w.session.paired() { if w.session.paired() {
return errors.New("wallet already paired") return fmt.Errorf("wallet already paired")
} }
pairing, err := w.session.pair(puk) pairing, err := w.session.pair(puk)
if err != nil { if err != nil {
@@ -776,16 +776,16 @@ func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationP
return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme) return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme)
} }
url, path, found := strings.Cut(account.URL.Path, "/") parts := strings.SplitN(account.URL.Path, "/", 2)
if !found { if len(parts) != 2 {
return nil, fmt.Errorf("invalid URL format: %s", account.URL) return nil, fmt.Errorf("invalid URL format: %s", account.URL)
} }
if url != fmt.Sprintf("%x", w.PublicKey[1:3]) { if parts[0] != fmt.Sprintf("%x", w.PublicKey[1:3]) {
return nil, fmt.Errorf("URL %s is not for this wallet", account.URL) return nil, fmt.Errorf("URL %s is not for this wallet", account.URL)
} }
return accounts.ParseDerivationPath(path) return accounts.ParseDerivationPath(parts[1])
} }
// Session represents a secured communication session with the wallet. // Session represents a secured communication session with the wallet.
@@ -813,7 +813,7 @@ func (s *Session) pair(secret []byte) (smartcardPairing, error) {
// unpair deletes an existing pairing. // unpair deletes an existing pairing.
func (s *Session) unpair() error { func (s *Session) unpair() error {
if !s.verified { if !s.verified {
return errors.New("unpair requires that the PIN be verified") return fmt.Errorf("unpair requires that the PIN be verified")
} }
return s.Channel.Unpair() return s.Channel.Unpair()
} }
@@ -879,7 +879,6 @@ func (s *Session) walletStatus() (*walletStatus, error) {
} }
// derivationPath fetches the wallet's current derivation path from the card. // derivationPath fetches the wallet's current derivation path from the card.
//
//lint:ignore U1000 needs to be added to the console interface //lint:ignore U1000 needs to be added to the console interface
func (s *Session) derivationPath() (accounts.DerivationPath, error) { func (s *Session) derivationPath() (accounts.DerivationPath, error) {
response, err := s.Channel.transmitEncrypted(claSCWallet, insStatus, statusP1Path, 0, nil) response, err := s.Channel.transmitEncrypted(claSCWallet, insStatus, statusP1Path, 0, nil)
@@ -907,7 +906,7 @@ func (s *Session) initialize(seed []byte) error {
return err return err
} }
if status == "Online" { if status == "Online" {
return errors.New("card is already initialized, cowardly refusing to proceed") return fmt.Errorf("card is already initialized, cowardly refusing to proceed")
} }
s.Wallet.lock.Lock() s.Wallet.lock.Lock()
@@ -995,7 +994,6 @@ func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error)
} }
// keyExport contains information on an exported keypair. // keyExport contains information on an exported keypair.
//
//lint:ignore U1000 needs to be added to the console interface //lint:ignore U1000 needs to be added to the console interface
type keyExport struct { type keyExport struct {
PublicKey []byte `asn1:"tag:0"` PublicKey []byte `asn1:"tag:0"`
@@ -1003,7 +1001,6 @@ type keyExport struct {
} }
// publicKey returns the public key for the current derivation path. // publicKey returns the public key for the current derivation path.
//
//lint:ignore U1000 needs to be added to the console interface //lint:ignore U1000 needs to be added to the console interface
func (s *Session) publicKey() ([]byte, error) { func (s *Session) publicKey() ([]byte, error) {
response, err := s.Channel.transmitEncrypted(claSCWallet, insExportKey, exportP1Any, exportP2Pubkey, nil) response, err := s.Channel.transmitEncrypted(claSCWallet, insExportKey, exportP1Any, exportP2Pubkey, nil)

View File

@@ -92,9 +92,10 @@ func (u *URL) UnmarshalJSON(input []byte) error {
// Cmp compares x and y and returns: // Cmp compares x and y and returns:
// //
// -1 if x < y // -1 if x < y
// 0 if x == y // 0 if x == y
// +1 if x > y // +1 if x > y
//
func (u URL) Cmp(url URL) int { func (u URL) Cmp(url URL) int {
if u.Scheme == url.Scheme { if u.Scheme == url.Scheme {
return strings.Compare(u.Path, url.Path) return strings.Compare(u.Path, url.Path)

View File

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

View File

@@ -63,36 +63,26 @@ type Hub struct {
stateLock sync.RWMutex // Protects the internals of the hub from racey access stateLock sync.RWMutex // Protects the internals of the hub from racey access
// TODO(karalabe): remove if hotplug lands on Windows // TODO(karalabe): remove if hotplug lands on Windows
commsPend int // Number of operations blocking enumeration commsPend int // Number of operations blocking enumeration
commsLock sync.Mutex // Lock protecting the pending counter and enumeration commsLock sync.Mutex // Lock protecting the pending counter and enumeration
enumFails atomic.Uint32 // Number of times enumeration has failed enumFails uint32 // Number of times enumeration has failed
} }
// NewLedgerHub creates a new hardware wallet manager for Ledger devices. // NewLedgerHub creates a new hardware wallet manager for Ledger devices.
func NewLedgerHub() (*Hub, error) { func NewLedgerHub() (*Hub, error) {
return newHub(LedgerScheme, 0x2c97, []uint16{ return newHub(LedgerScheme, 0x2c97, []uint16{
// Device definitions taken from
// https://github.com/LedgerHQ/ledger-live/blob/38012bc8899e0f07149ea9cfe7e64b2c146bc92b/libs/ledgerjs/packages/devices/src/index.ts
// Original product IDs // Original product IDs
0x0000, /* Ledger Blue */ 0x0000, /* Ledger Blue */
0x0001, /* Ledger Nano S */ 0x0001, /* Ledger Nano S */
0x0004, /* Ledger Nano X */ 0x0004, /* Ledger Nano X */
0x0005, /* Ledger Nano S Plus */
0x0006, /* Ledger Nano FTS */
// Upcoming product IDs: https://www.ledger.com/2019/05/17/windows-10-update-sunsetting-u2f-tunnel-transport-for-ledger-devices/
0x0015, /* HID + U2F + WebUSB Ledger Blue */ 0x0015, /* HID + U2F + WebUSB Ledger Blue */
0x1015, /* HID + U2F + WebUSB Ledger Nano S */ 0x1015, /* HID + U2F + WebUSB Ledger Nano S */
0x4015, /* HID + U2F + WebUSB Ledger Nano X */ 0x4015, /* HID + U2F + WebUSB Ledger Nano X */
0x5015, /* HID + U2F + WebUSB Ledger Nano S Plus */
0x6015, /* HID + U2F + WebUSB Ledger Nano FTS */
0x0011, /* HID + WebUSB Ledger Blue */ 0x0011, /* HID + WebUSB Ledger Blue */
0x1011, /* HID + WebUSB Ledger Nano S */ 0x1011, /* HID + WebUSB Ledger Nano S */
0x4011, /* HID + WebUSB Ledger Nano X */ 0x4011, /* HID + WebUSB Ledger Nano X */
0x5011, /* HID + WebUSB Ledger Nano S Plus */
0x6011, /* HID + WebUSB Ledger Nano FTS */
}, 0xffa0, 0, newLedgerDriver) }, 0xffa0, 0, newLedgerDriver)
} }
@@ -151,7 +141,7 @@ func (hub *Hub) refreshWallets() {
return return
} }
// If USB enumeration is continually failing, don't keep trying indefinitely // If USB enumeration is continually failing, don't keep trying indefinitely
if hub.enumFails.Load() > 2 { if atomic.LoadUint32(&hub.enumFails) > 2 {
return return
} }
// Retrieve the current list of USB wallet devices // Retrieve the current list of USB wallet devices
@@ -172,7 +162,7 @@ func (hub *Hub) refreshWallets() {
} }
infos, err := usb.Enumerate(hub.vendorID, 0) infos, err := usb.Enumerate(hub.vendorID, 0)
if err != nil { if err != nil {
failcount := hub.enumFails.Add(1) failcount := atomic.AddUint32(&hub.enumFails, 1)
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// See rationale before the enumeration why this is needed and only on Linux. // See rationale before the enumeration why this is needed and only on Linux.
hub.commsLock.Unlock() hub.commsLock.Unlock()
@@ -181,7 +171,7 @@ func (hub *Hub) refreshWallets() {
"vendor", hub.vendorID, "failcount", failcount, "err", err) "vendor", hub.vendorID, "failcount", failcount, "err", err)
return return
} }
hub.enumFails.Store(0) atomic.StoreUint32(&hub.enumFails, 0)
for _, info := range infos { for _, info := range infos {
for _, id := range hub.productIDs { for _, id := range hub.productIDs {

View File

@@ -59,8 +59,6 @@ const (
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
ledgerEip155Size int = 3 // Size of the EIP-155 chain_id,r,s in unsigned transactions
) )
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange // errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
@@ -197,18 +195,18 @@ func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash
// //
// The version retrieval protocol is defined as follows: // The version retrieval protocol is defined as follows:
// //
// CLA | INS | P1 | P2 | Lc | Le // CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+----+--- // ----+-----+----+----+----+---
// E0 | 06 | 00 | 00 | 00 | 04 // E0 | 06 | 00 | 00 | 00 | 04
// //
// With no input data, and the output data being: // With no input data, and the output data being:
// //
// Description | Length // Description | Length
// ---------------------------------------------------+-------- // ---------------------------------------------------+--------
// Flags 01: arbitrary data signature enabled by user | 1 byte // Flags 01: arbitrary data signature enabled by user | 1 byte
// Application major version | 1 byte // Application major version | 1 byte
// Application minor version | 1 byte // Application minor version | 1 byte
// Application patch version | 1 byte // Application patch version | 1 byte
func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
// Send the request and wait for the response // Send the request and wait for the response
reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil)
@@ -229,32 +227,32 @@ func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
// //
// The address derivation protocol is defined as follows: // The address derivation protocol is defined as follows:
// //
// CLA | INS | P1 | P2 | Lc | Le // CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+-----+--- // ----+-----+----+----+-----+---
// E0 | 02 | 00 return address // E0 | 02 | 00 return address
// 01 display address and confirm before returning // 01 display address and confirm before returning
// | 00: do not return the chain code // | 00: do not return the chain code
// | 01: return the chain code // | 01: return the chain code
// | var | 00 // | var | 00
// //
// Where the input data is: // Where the input data is:
// //
// Description | Length // Description | Length
// -------------------------------------------------+-------- // -------------------------------------------------+--------
// Number of BIP 32 derivations to perform (max 10) | 1 byte // Number of BIP 32 derivations to perform (max 10) | 1 byte
// First derivation index (big endian) | 4 bytes // First derivation index (big endian) | 4 bytes
// ... | 4 bytes // ... | 4 bytes
// Last derivation index (big endian) | 4 bytes // Last derivation index (big endian) | 4 bytes
// //
// And the output data is: // And the output data is:
// //
// Description | Length // Description | Length
// ------------------------+------------------- // ------------------------+-------------------
// Public Key length | 1 byte // Public Key length | 1 byte
// Uncompressed Public Key | arbitrary // Uncompressed Public Key | arbitrary
// Ethereum address length | 1 byte // Ethereum address length | 1 byte
// Ethereum address | 40 bytes hex ascii // Ethereum address | 40 bytes hex ascii
// Chain code if requested | 32 bytes // Chain code if requested | 32 bytes
func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) { func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) {
// Flatten the derivation path into the Ledger request // Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath)) path := make([]byte, 1+4*len(derivationPath))
@@ -279,7 +277,7 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
} }
hexstr := reply[1 : 1+int(reply[0])] hexstr := reply[1 : 1+int(reply[0])]
// Decode the hex string into an Ethereum address and return // Decode the hex sting into an Ethereum address and return
var address common.Address var address common.Address
if _, err = hex.Decode(address[:], hexstr); err != nil { if _, err = hex.Decode(address[:], hexstr); err != nil {
return common.Address{}, err return common.Address{}, err
@@ -292,35 +290,35 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
// //
// The transaction signing protocol is defined as follows: // The transaction signing protocol is defined as follows:
// //
// CLA | INS | P1 | P2 | Lc | Le // CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+-----+--- // ----+-----+----+----+-----+---
// E0 | 04 | 00: first transaction data block // E0 | 04 | 00: first transaction data block
// 80: subsequent transaction data block // 80: subsequent transaction data block
// | 00 | variable | variable // | 00 | variable | variable
// //
// Where the input for the first transaction block (first 255 bytes) is: // Where the input for the first transaction block (first 255 bytes) is:
// //
// Description | Length // Description | Length
// -------------------------------------------------+---------- // -------------------------------------------------+----------
// Number of BIP 32 derivations to perform (max 10) | 1 byte // Number of BIP 32 derivations to perform (max 10) | 1 byte
// First derivation index (big endian) | 4 bytes // First derivation index (big endian) | 4 bytes
// ... | 4 bytes // ... | 4 bytes
// Last derivation index (big endian) | 4 bytes // Last derivation index (big endian) | 4 bytes
// RLP transaction chunk | arbitrary // RLP transaction chunk | arbitrary
// //
// And the input for subsequent transaction blocks (first 255 bytes) are: // And the input for subsequent transaction blocks (first 255 bytes) are:
// //
// Description | Length // Description | Length
// ----------------------+---------- // ----------------------+----------
// RLP transaction chunk | arbitrary // RLP transaction chunk | arbitrary
// //
// And the output data is: // And the output data is:
// //
// Description | Length // Description | Length
// ------------+--------- // ------------+---------
// signature V | 1 byte // signature V | 1 byte
// signature R | 32 bytes // signature R | 32 bytes
// signature S | 32 bytes // signature S | 32 bytes
func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
// Flatten the derivation path into the Ledger request // Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath)) path := make([]byte, 1+4*len(derivationPath))
@@ -349,15 +347,9 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
op = ledgerP1InitTransactionData op = ledgerP1InitTransactionData
reply []byte reply []byte
) )
// Chunk size selection to mitigate an underlying RLP deserialization issue on the ledger app.
// https://github.com/LedgerHQ/app-ethereum/issues/409
chunk := 255
for ; len(payload)%chunk <= ledgerEip155Size; chunk-- {
}
for len(payload) > 0 { for len(payload) > 0 {
// Calculate the size of the next data chunk // Calculate the size of the next data chunk
chunk := 255
if chunk > len(payload) { if chunk > len(payload) {
chunk = len(payload) chunk = len(payload)
} }
@@ -400,28 +392,30 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
// //
// The signing protocol is defined as follows: // The signing protocol is defined as follows:
// //
// CLA | INS | P1 | P2 | Lc | Le // CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+-----------------------------+-----+--- // ----+-----+----+-----------------------------+-----+---
// E0 | 0C | 00 | implementation version : 00 | variable | variable // E0 | 0C | 00 | implementation version : 00 | variable | variable
// //
// Where the input is: // Where the input is:
// //
// Description | Length // Description | Length
// -------------------------------------------------+---------- // -------------------------------------------------+----------
// Number of BIP 32 derivations to perform (max 10) | 1 byte // Number of BIP 32 derivations to perform (max 10) | 1 byte
// First derivation index (big endian) | 4 bytes // First derivation index (big endian) | 4 bytes
// ... | 4 bytes // ... | 4 bytes
// Last derivation index (big endian) | 4 bytes // Last derivation index (big endian) | 4 bytes
// domain hash | 32 bytes // domain hash | 32 bytes
// message hash | 32 bytes // message hash | 32 bytes
//
//
// //
// And the output data is: // And the output data is:
// //
// Description | Length // Description | Length
// ------------+--------- // ------------+---------
// signature V | 1 byte // signature V | 1 byte
// signature R | 32 bytes // signature R | 32 bytes
// signature S | 32 bytes // signature S | 32 bytes
func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) { func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) {
// Flatten the derivation path into the Ledger request // Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath)) path := make([]byte, 1+4*len(derivationPath))
@@ -460,12 +454,12 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas
// //
// The common transport header is defined as follows: // The common transport header is defined as follows:
// //
// Description | Length // Description | Length
// --------------------------------------+---------- // --------------------------------------+----------
// Communication channel ID (big endian) | 2 bytes // Communication channel ID (big endian) | 2 bytes
// Command tag | 1 byte // Command tag | 1 byte
// Packet sequence index (big endian) | 2 bytes // Packet sequence index (big endian) | 2 bytes
// Payload | arbitrary // Payload | arbitrary
// //
// The Communication channel ID allows commands multiplexing over the same // The Communication channel ID allows commands multiplexing over the same
// physical link. It is not used for the time being, and should be set to 0101 // physical link. It is not used for the time being, and should be set to 0101
@@ -479,15 +473,15 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas
// //
// APDU Command payloads are encoded as follows: // APDU Command payloads are encoded as follows:
// //
// Description | Length // Description | Length
// ----------------------------------- // -----------------------------------
// APDU length (big endian) | 2 bytes // APDU length (big endian) | 2 bytes
// APDU CLA | 1 byte // APDU CLA | 1 byte
// APDU INS | 1 byte // APDU INS | 1 byte
// APDU P1 | 1 byte // APDU P1 | 1 byte
// APDU P2 | 1 byte // APDU P2 | 1 byte
// APDU length | 1 byte // APDU length | 1 byte
// Optional APDU data | arbitrary // Optional APDU data | arbitrary
func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
// Construct the message payload, possibly split into multiple chunks // Construct the message payload, possibly split into multiple chunks
apdu := make([]byte, 2, 7+len(data)) apdu := make([]byte, 2, 7+len(data))

View File

@@ -84,15 +84,15 @@ func (w *trezorDriver) Status() (string, error) {
// Open implements usbwallet.driver, attempting to initialize the connection to // Open implements usbwallet.driver, attempting to initialize the connection to
// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation: // the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
// - The first phase is to initialize the connection and read the wallet's // * The first phase is to initialize the connection and read the wallet's
// features. This phase is invoked if the provided passphrase is empty. The // features. This phase is invoked if the provided passphrase is empty. The
// device will display the pinpad as a result and will return an appropriate // device will display the pinpad as a result and will return an appropriate
// error to notify the user that a second open phase is needed. // error to notify the user that a second open phase is needed.
// - The second phase is to unlock access to the Trezor, which is done by the // * The second phase is to unlock access to the Trezor, which is done by the
// user actually providing a passphrase mapping a keyboard keypad to the pin // user actually providing a passphrase mapping a keyboard keypad to the pin
// number of the user (shuffled according to the pinpad displayed). // number of the user (shuffled according to the pinpad displayed).
// - If needed the device will ask for passphrase which will require calling // * If needed the device will ask for passphrase which will require calling
// open again with the actual passphrase (3rd phase) // open again with the actual passphrase (3rd phase)
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.device, w.failure = device, nil w.device, w.failure = device, nil

View File

@@ -94,7 +94,7 @@ func (Failure_FailureType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_aaf30d059fdbc38d, []int{1, 0} return fileDescriptor_aaf30d059fdbc38d, []int{1, 0}
} }
// * //*
// Type of button request // Type of button request
type ButtonRequest_ButtonRequestType int32 type ButtonRequest_ButtonRequestType int32
@@ -175,7 +175,7 @@ func (ButtonRequest_ButtonRequestType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_aaf30d059fdbc38d, []int{2, 0} return fileDescriptor_aaf30d059fdbc38d, []int{2, 0}
} }
// * //*
// Type of PIN request // Type of PIN request
type PinMatrixRequest_PinMatrixRequestType int32 type PinMatrixRequest_PinMatrixRequestType int32
@@ -220,7 +220,7 @@ func (PinMatrixRequest_PinMatrixRequestType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_aaf30d059fdbc38d, []int{4, 0} return fileDescriptor_aaf30d059fdbc38d, []int{4, 0}
} }
// * //*
// Response: Success of the previous request // Response: Success of the previous request
// @end // @end
type Success struct { type Success struct {
@@ -262,7 +262,7 @@ func (m *Success) GetMessage() string {
return "" return ""
} }
// * //*
// Response: Failure of the previous request // Response: Failure of the previous request
// @end // @end
type Failure struct { type Failure struct {
@@ -312,7 +312,7 @@ func (m *Failure) GetMessage() string {
return "" return ""
} }
// * //*
// Response: Device is waiting for HW button press. // Response: Device is waiting for HW button press.
// @auxstart // @auxstart
// @next ButtonAck // @next ButtonAck
@@ -363,7 +363,7 @@ func (m *ButtonRequest) GetData() string {
return "" return ""
} }
// * //*
// Request: Computer agrees to wait for HW button press // Request: Computer agrees to wait for HW button press
// @auxend // @auxend
type ButtonAck struct { type ButtonAck struct {
@@ -397,7 +397,7 @@ func (m *ButtonAck) XXX_DiscardUnknown() {
var xxx_messageInfo_ButtonAck proto.InternalMessageInfo var xxx_messageInfo_ButtonAck proto.InternalMessageInfo
// * //*
// Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme // Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
// @auxstart // @auxstart
// @next PinMatrixAck // @next PinMatrixAck
@@ -440,7 +440,7 @@ func (m *PinMatrixRequest) GetType() PinMatrixRequest_PinMatrixRequestType {
return PinMatrixRequest_PinMatrixRequestType_Current return PinMatrixRequest_PinMatrixRequestType_Current
} }
// * //*
// Request: Computer responds with encoded PIN // Request: Computer responds with encoded PIN
// @auxend // @auxend
type PinMatrixAck struct { type PinMatrixAck struct {
@@ -482,7 +482,7 @@ func (m *PinMatrixAck) GetPin() string {
return "" return ""
} }
// * //*
// Response: Device awaits encryption passphrase // Response: Device awaits encryption passphrase
// @auxstart // @auxstart
// @next PassphraseAck // @next PassphraseAck
@@ -525,7 +525,7 @@ func (m *PassphraseRequest) GetOnDevice() bool {
return false return false
} }
// * //*
// Request: Send passphrase back // Request: Send passphrase back
// @next PassphraseStateRequest // @next PassphraseStateRequest
type PassphraseAck struct { type PassphraseAck struct {
@@ -575,7 +575,7 @@ func (m *PassphraseAck) GetState() []byte {
return nil return nil
} }
// * //*
// Response: Device awaits passphrase state // Response: Device awaits passphrase state
// @next PassphraseStateAck // @next PassphraseStateAck
type PassphraseStateRequest struct { type PassphraseStateRequest struct {
@@ -617,7 +617,7 @@ func (m *PassphraseStateRequest) GetState() []byte {
return nil return nil
} }
// * //*
// Request: Send passphrase state back // Request: Send passphrase state back
// @auxend // @auxend
type PassphraseStateAck struct { type PassphraseStateAck struct {
@@ -651,7 +651,7 @@ func (m *PassphraseStateAck) XXX_DiscardUnknown() {
var xxx_messageInfo_PassphraseStateAck proto.InternalMessageInfo var xxx_messageInfo_PassphraseStateAck proto.InternalMessageInfo
// * //*
// Structure representing BIP32 (hierarchical deterministic) node // Structure representing BIP32 (hierarchical deterministic) node
// Used for imports of private key into the device and exporting public key out of device // Used for imports of private key into the device and exporting public key out of device
// @embed // @embed

View File

@@ -21,7 +21,7 @@ var _ = math.Inf
// proto package needs to be updated. // proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// * //*
// Request: Ask device for public key corresponding to address_n path // Request: Ask device for public key corresponding to address_n path
// @start // @start
// @next EthereumPublicKey // @next EthereumPublicKey
@@ -73,7 +73,7 @@ func (m *EthereumGetPublicKey) GetShowDisplay() bool {
return false return false
} }
// * //*
// Response: Contains public key derived from device private seed // Response: Contains public key derived from device private seed
// @end // @end
type EthereumPublicKey struct { type EthereumPublicKey struct {
@@ -123,7 +123,7 @@ func (m *EthereumPublicKey) GetXpub() string {
return "" return ""
} }
// * //*
// Request: Ask device for Ethereum address corresponding to address_n path // Request: Ask device for Ethereum address corresponding to address_n path
// @start // @start
// @next EthereumAddress // @next EthereumAddress
@@ -175,7 +175,7 @@ func (m *EthereumGetAddress) GetShowDisplay() bool {
return false return false
} }
// * //*
// Response: Contains an Ethereum address derived from device private seed // Response: Contains an Ethereum address derived from device private seed
// @end // @end
type EthereumAddress struct { type EthereumAddress struct {
@@ -225,7 +225,7 @@ func (m *EthereumAddress) GetAddressHex() string {
return "" return ""
} }
// * //*
// Request: Ask device to sign transaction // Request: Ask device to sign transaction
// All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing. // All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
// Note: the first at most 1024 bytes of data MUST be transmitted as part of this message. // Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
@@ -351,7 +351,7 @@ func (m *EthereumSignTx) GetTxType() uint32 {
return 0 return 0
} }
// * //*
// Response: Device asks for more data from transaction payload, or returns the signature. // Response: Device asks for more data from transaction payload, or returns the signature.
// If data_length is set, device awaits that many more bytes of payload. // If data_length is set, device awaits that many more bytes of payload.
// Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present. // Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
@@ -420,7 +420,7 @@ func (m *EthereumTxRequest) GetSignatureS() []byte {
return nil return nil
} }
// * //*
// Request: Transaction payload data. // Request: Transaction payload data.
// @next EthereumTxRequest // @next EthereumTxRequest
type EthereumTxAck struct { type EthereumTxAck struct {
@@ -462,7 +462,7 @@ func (m *EthereumTxAck) GetDataChunk() []byte {
return nil return nil
} }
// * //*
// Request: Ask device to sign message // Request: Ask device to sign message
// @start // @start
// @next EthereumMessageSignature // @next EthereumMessageSignature
@@ -514,7 +514,7 @@ func (m *EthereumSignMessage) GetMessage() []byte {
return nil return nil
} }
// * //*
// Response: Signed message // Response: Signed message
// @end // @end
type EthereumMessageSignature struct { type EthereumMessageSignature struct {
@@ -572,7 +572,7 @@ func (m *EthereumMessageSignature) GetAddressHex() string {
return "" return ""
} }
// * //*
// Request: Ask device to verify message // Request: Ask device to verify message
// @start // @start
// @next Success // @next Success

View File

@@ -21,7 +21,7 @@ var _ = math.Inf
// proto package needs to be updated. // proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// * //*
// Structure representing passphrase source // Structure representing passphrase source
type ApplySettings_PassphraseSourceType int32 type ApplySettings_PassphraseSourceType int32
@@ -66,7 +66,7 @@ func (ApplySettings_PassphraseSourceType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_0c720c20d27aa029, []int{4, 0} return fileDescriptor_0c720c20d27aa029, []int{4, 0}
} }
// * //*
// Type of recovery procedure. These should be used as bitmask, e.g., // Type of recovery procedure. These should be used as bitmask, e.g.,
// `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` // `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
// listing every method supported by the host computer. // listing every method supported by the host computer.
@@ -114,7 +114,7 @@ func (RecoveryDevice_RecoveryDeviceType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_0c720c20d27aa029, []int{17, 0} return fileDescriptor_0c720c20d27aa029, []int{17, 0}
} }
// * //*
// Type of Recovery Word request // Type of Recovery Word request
type WordRequest_WordRequestType int32 type WordRequest_WordRequestType int32
@@ -159,7 +159,7 @@ func (WordRequest_WordRequestType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_0c720c20d27aa029, []int{18, 0} return fileDescriptor_0c720c20d27aa029, []int{18, 0}
} }
// * //*
// Request: Reset device to default state and ask for device details // Request: Reset device to default state and ask for device details
// @start // @start
// @next Features // @next Features
@@ -210,7 +210,7 @@ func (m *Initialize) GetSkipPassphrase() bool {
return false return false
} }
// * //*
// Request: Ask for device details (no device reset) // Request: Ask for device details (no device reset)
// @start // @start
// @next Features // @next Features
@@ -245,7 +245,7 @@ func (m *GetFeatures) XXX_DiscardUnknown() {
var xxx_messageInfo_GetFeatures proto.InternalMessageInfo var xxx_messageInfo_GetFeatures proto.InternalMessageInfo
// * //*
// Response: Reports various information about the device // Response: Reports various information about the device
// @end // @end
type Features struct { type Features struct {
@@ -495,7 +495,7 @@ func (m *Features) GetNoBackup() bool {
return false return false
} }
// * //*
// Request: clear session (removes cached PIN, passphrase, etc). // Request: clear session (removes cached PIN, passphrase, etc).
// @start // @start
// @next Success // @next Success
@@ -530,7 +530,7 @@ func (m *ClearSession) XXX_DiscardUnknown() {
var xxx_messageInfo_ClearSession proto.InternalMessageInfo var xxx_messageInfo_ClearSession proto.InternalMessageInfo
// * //*
// Request: change language and/or label of the device // Request: change language and/or label of the device
// @start // @start
// @next Success // @next Success
@@ -622,7 +622,7 @@ func (m *ApplySettings) GetDisplayRotation() uint32 {
return 0 return 0
} }
// * //*
// Request: set flags of the device // Request: set flags of the device
// @start // @start
// @next Success // @next Success
@@ -666,7 +666,7 @@ func (m *ApplyFlags) GetFlags() uint32 {
return 0 return 0
} }
// * //*
// Request: Starts workflow for setting/changing/removing the PIN // Request: Starts workflow for setting/changing/removing the PIN
// @start // @start
// @next Success // @next Success
@@ -710,7 +710,7 @@ func (m *ChangePin) GetRemove() bool {
return false return false
} }
// * //*
// Request: Test if the device is alive, device sends back the message in Success response // Request: Test if the device is alive, device sends back the message in Success response
// @start // @start
// @next Success // @next Success
@@ -777,7 +777,7 @@ func (m *Ping) GetPassphraseProtection() bool {
return false return false
} }
// * //*
// Request: Abort last operation that required user interaction // Request: Abort last operation that required user interaction
// @start // @start
// @next Failure // @next Failure
@@ -812,7 +812,7 @@ func (m *Cancel) XXX_DiscardUnknown() {
var xxx_messageInfo_Cancel proto.InternalMessageInfo var xxx_messageInfo_Cancel proto.InternalMessageInfo
// * //*
// Request: Request a sample of random data generated by hardware RNG. May be used for testing. // Request: Request a sample of random data generated by hardware RNG. May be used for testing.
// @start // @start
// @next Entropy // @next Entropy
@@ -856,7 +856,7 @@ func (m *GetEntropy) GetSize() uint32 {
return 0 return 0
} }
// * //*
// Response: Reply with random data generated by internal RNG // Response: Reply with random data generated by internal RNG
// @end // @end
type Entropy struct { type Entropy struct {
@@ -898,7 +898,7 @@ func (m *Entropy) GetEntropy() []byte {
return nil return nil
} }
// * //*
// Request: Request device to wipe all sensitive data and settings // Request: Request device to wipe all sensitive data and settings
// @start // @start
// @next Success // @next Success
@@ -934,7 +934,7 @@ func (m *WipeDevice) XXX_DiscardUnknown() {
var xxx_messageInfo_WipeDevice proto.InternalMessageInfo var xxx_messageInfo_WipeDevice proto.InternalMessageInfo
// * //*
// Request: Load seed and related internal settings from the computer // Request: Load seed and related internal settings from the computer
// @start // @start
// @next Success // @next Success
@@ -1036,7 +1036,7 @@ func (m *LoadDevice) GetU2FCounter() uint32 {
return 0 return 0
} }
// * //*
// Request: Ask device to do initialization involving user interaction // Request: Ask device to do initialization involving user interaction
// @start // @start
// @next EntropyRequest // @next EntropyRequest
@@ -1147,7 +1147,7 @@ func (m *ResetDevice) GetNoBackup() bool {
return false return false
} }
// * //*
// Request: Perform backup of the device seed if not backed up using ResetDevice // Request: Perform backup of the device seed if not backed up using ResetDevice
// @start // @start
// @next Success // @next Success
@@ -1182,7 +1182,7 @@ func (m *BackupDevice) XXX_DiscardUnknown() {
var xxx_messageInfo_BackupDevice proto.InternalMessageInfo var xxx_messageInfo_BackupDevice proto.InternalMessageInfo
// * //*
// Response: Ask for additional entropy from host computer // Response: Ask for additional entropy from host computer
// @next EntropyAck // @next EntropyAck
type EntropyRequest struct { type EntropyRequest struct {
@@ -1216,7 +1216,7 @@ func (m *EntropyRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_EntropyRequest proto.InternalMessageInfo var xxx_messageInfo_EntropyRequest proto.InternalMessageInfo
// * //*
// Request: Provide additional entropy for seed generation function // Request: Provide additional entropy for seed generation function
// @next Success // @next Success
type EntropyAck struct { type EntropyAck struct {
@@ -1258,7 +1258,7 @@ func (m *EntropyAck) GetEntropy() []byte {
return nil return nil
} }
// * //*
// Request: Start recovery workflow asking user for specific words of mnemonic // Request: Start recovery workflow asking user for specific words of mnemonic
// Used to recovery device safely even on untrusted computer. // Used to recovery device safely even on untrusted computer.
// @start // @start
@@ -1369,7 +1369,7 @@ func (m *RecoveryDevice) GetDryRun() bool {
return false return false
} }
// * //*
// Response: Device is waiting for user to enter word of the mnemonic // Response: Device is waiting for user to enter word of the mnemonic
// Its position is shown only on device's internal display. // Its position is shown only on device's internal display.
// @next WordAck // @next WordAck
@@ -1412,7 +1412,7 @@ func (m *WordRequest) GetType() WordRequest_WordRequestType {
return WordRequest_WordRequestType_Plain return WordRequest_WordRequestType_Plain
} }
// * //*
// Request: Computer replies with word from the mnemonic // Request: Computer replies with word from the mnemonic
// @next WordRequest // @next WordRequest
// @next Success // @next Success
@@ -1456,7 +1456,7 @@ func (m *WordAck) GetWord() string {
return "" return ""
} }
// * //*
// Request: Set U2F counter // Request: Set U2F counter
// @start // @start
// @next Success // @next Success

View File

@@ -22,7 +22,7 @@ var _ = math.Inf
// proto package needs to be updated. // proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// * //*
// Mapping between TREZOR wire identifier (uint) and a protobuf message // Mapping between TREZOR wire identifier (uint) and a protobuf message
type MessageType int32 type MessageType int32

View File

@@ -483,10 +483,6 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
w.stateLock.Lock() w.stateLock.Lock()
defer w.stateLock.Unlock() defer w.stateLock.Unlock()
if w.device == nil {
return accounts.Account{}, accounts.ErrWalletClosed
}
if _, ok := w.paths[address]; !ok { if _, ok := w.paths[address]; !ok {
w.accounts = append(w.accounts, account) w.accounts = append(w.accounts, account)
w.paths[address] = make(accounts.DerivationPath, len(path)) w.paths[address] = make(accounts.DerivationPath, len(path))
@@ -628,7 +624,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
return signed, nil return signed, nil
} }
// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary // SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
// data is not supported for Ledger wallets, so this method will always return // data is not supported for Ledger wallets, so this method will always return
// an error. // an error.
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {

View File

@@ -26,7 +26,7 @@ for:
- go run build/ci.go lint - go run build/ci.go lint
- go run build/ci.go install -dlgo - go run build/ci.go install -dlgo
test_script: test_script:
- go run build/ci.go test -dlgo - go run build/ci.go test -dlgo -coverage
# linux/386 is disabled. # linux/386 is disabled.
- matrix: - matrix:
@@ -54,4 +54,4 @@ for:
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
test_script: test_script:
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -coverage

View File

@@ -1,66 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package engine
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
var _ = (*payloadAttributesMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
type PayloadAttributes struct {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
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)
}
// UnmarshalJSON unmarshals from JSON.
func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
type PayloadAttributes struct {
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
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 {
return err
}
if dec.Timestamp == nil {
return errors.New("missing required field 'timestamp' for PayloadAttributes")
}
p.Timestamp = uint64(*dec.Timestamp)
if dec.Random == nil {
return errors.New("missing required field 'prevRandao' for PayloadAttributes")
}
p.Random = *dec.Random
if dec.SuggestedFeeRecipient == nil {
return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributes")
}
p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient
if dec.Withdrawals != nil {
p.Withdrawals = dec.Withdrawals
}
if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot
}
return nil
}

View File

@@ -1,158 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package engine
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
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
enc.FeeRecipient = e.FeeRecipient
enc.StateRoot = e.StateRoot
enc.ReceiptsRoot = e.ReceiptsRoot
enc.LogsBloom = e.LogsBloom
enc.Random = e.Random
enc.Number = hexutil.Uint64(e.Number)
enc.GasLimit = hexutil.Uint64(e.GasLimit)
enc.GasUsed = hexutil.Uint64(e.GasUsed)
enc.Timestamp = hexutil.Uint64(e.Timestamp)
enc.ExtraData = e.ExtraData
enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas)
enc.BlockHash = e.BlockHash
if e.Transactions != nil {
enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
for k, v := range e.Transactions {
enc.Transactions[k] = v
}
}
enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableData struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
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 {
return err
}
if dec.ParentHash == nil {
return errors.New("missing required field 'parentHash' for ExecutableData")
}
e.ParentHash = *dec.ParentHash
if dec.FeeRecipient == nil {
return errors.New("missing required field 'feeRecipient' for ExecutableData")
}
e.FeeRecipient = *dec.FeeRecipient
if dec.StateRoot == nil {
return errors.New("missing required field 'stateRoot' for ExecutableData")
}
e.StateRoot = *dec.StateRoot
if dec.ReceiptsRoot == nil {
return errors.New("missing required field 'receiptsRoot' for ExecutableData")
}
e.ReceiptsRoot = *dec.ReceiptsRoot
if dec.LogsBloom == nil {
return errors.New("missing required field 'logsBloom' for ExecutableData")
}
e.LogsBloom = *dec.LogsBloom
if dec.Random == nil {
return errors.New("missing required field 'prevRandao' for ExecutableData")
}
e.Random = *dec.Random
if dec.Number == nil {
return errors.New("missing required field 'blockNumber' for ExecutableData")
}
e.Number = uint64(*dec.Number)
if dec.GasLimit == nil {
return errors.New("missing required field 'gasLimit' for ExecutableData")
}
e.GasLimit = uint64(*dec.GasLimit)
if dec.GasUsed == nil {
return errors.New("missing required field 'gasUsed' for ExecutableData")
}
e.GasUsed = uint64(*dec.GasUsed)
if dec.Timestamp == nil {
return errors.New("missing required field 'timestamp' for ExecutableData")
}
e.Timestamp = uint64(*dec.Timestamp)
if dec.ExtraData == nil {
return errors.New("missing required field 'extraData' for ExecutableData")
}
e.ExtraData = *dec.ExtraData
if dec.BaseFeePerGas == nil {
return errors.New("missing required field 'baseFeePerGas' for ExecutableData")
}
e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
if dec.BlockHash == nil {
return errors.New("missing required field 'blockHash' for ExecutableData")
}
e.BlockHash = *dec.BlockHash
if dec.Transactions == nil {
return errors.New("missing required field 'transactions' for ExecutableData")
}
e.Transactions = make([][]byte, len(dec.Transactions))
for k, v := range dec.Transactions {
e.Transactions[k] = v
}
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

@@ -1,58 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package engine
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*executionPayloadEnvelopeMarshaling)(nil)
// MarshalJSON marshals as JSON.
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)
}
// UnmarshalJSON unmarshals from JSON.
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 {
return err
}
if dec.ExecutionPayload == nil {
return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope")
}
e.ExecutionPayload = dec.ExecutionPayload
if dec.BlockValue == nil {
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

@@ -1,305 +0,0 @@
// 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 engine
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"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
// be built.
type PayloadAttributes struct {
Timestamp uint64 `json:"timestamp" gencodec:"required"`
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.
type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64
}
//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
// ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
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.
type executableDataMarshaling struct {
Number hexutil.Uint64
GasLimit hexutil.Uint64
GasUsed hexutil.Uint64
Timestamp hexutil.Uint64
BaseFeePerGas *hexutil.Big
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
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.
type executionPayloadEnvelopeMarshaling struct {
BlockValue *hexutil.Big
}
type PayloadStatusV1 struct {
Status string `json:"status"`
LatestValidHash *common.Hash `json:"latestValidHash"`
ValidationError *string `json:"validationError"`
}
type TransitionConfigurationV1 struct {
TerminalTotalDifficulty *hexutil.Big `json:"terminalTotalDifficulty"`
TerminalBlockHash common.Hash `json:"terminalBlockHash"`
TerminalBlockNumber hexutil.Uint64 `json:"terminalBlockNumber"`
}
// 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[:])
}
func (b PayloadID) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}
func (b *PayloadID) UnmarshalText(input []byte) error {
err := hexutil.UnmarshalFixedText("PayloadID", input, b[:])
if err != nil {
return fmt.Errorf("invalid payload id %q: %w", input, err)
}
return nil
}
type ForkChoiceResponse struct {
PayloadStatus PayloadStatusV1 `json:"payloadStatus"`
PayloadID *PayloadID `json:"payloadId"`
}
type ForkchoiceStateV1 struct {
HeadBlockHash common.Hash `json:"headBlockHash"`
SafeBlockHash common.Hash `json:"safeBlockHash"`
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
}
func encodeTransactions(txs []*types.Transaction) [][]byte {
var enc = make([][]byte, len(txs))
for i, tx := range txs {
enc[i], _ = tx.MarshalBinary()
}
return enc
}
func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
var txs = make([]*types.Transaction, len(enc))
for i, encTx := range enc {
var tx types.Transaction
if err := tx.UnmarshalBinary(encTx); err != nil {
return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
}
txs[i] = &tx
}
return txs, nil
}
// ExecutableDataToBlock constructs a block from executable data.
// It verifies that the following fields:
//
// 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, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
txs, err := decodeTransactions(params.Transactions)
if err != nil {
return nil, err
}
if len(params.ExtraData) > 32 {
return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData))
}
if len(params.LogsBloom) != 256 {
return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom))
}
// Check that baseFeePerGas is not negative or too big
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.
var withdrawalsRoot *common.Hash
if params.Withdrawals != nil {
h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil))
withdrawalsRoot = &h
}
header := &types.Header{
ParentHash: params.ParentHash,
UncleHash: types.EmptyUncleHash,
Coinbase: params.FeeRecipient,
Root: params.StateRoot,
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
ReceiptHash: params.ReceiptsRoot,
Bloom: types.BytesToBloom(params.LogsBloom),
Difficulty: common.Big0,
Number: new(big.Int).SetUint64(params.Number),
GasLimit: params.GasLimit,
GasUsed: params.GasUsed,
Time: params.Timestamp,
BaseFee: params.BaseFeePerGas,
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 {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
}
return block, nil
}
// 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, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope {
data := &ExecutableData{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(),
StateRoot: block.Root(),
Number: block.NumberU64(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(),
ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(),
}
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
type ExecutionPayloadBodyV1 struct {
TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}

View File

@@ -1,125 +0,0 @@
// 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

@@ -1,514 +0,0 @@
// 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

@@ -1,356 +0,0 @@
// 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)
}
}

View File

@@ -1,78 +0,0 @@
// 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

@@ -1,152 +0,0 @@
// 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
}

View File

@@ -1,67 +0,0 @@
// 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,44 +0,0 @@
// 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 params
const (
EpochLength = 32
SyncPeriodLength = 8192
BLSSignatureSize = 96
BLSPubkeySize = 48
SyncCommitteeSize = 512
SyncCommitteeBitmaskSize = SyncCommitteeSize / 8
SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3
)
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
)

View File

@@ -1,191 +0,0 @@
// 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
}

View File

@@ -1,176 +0,0 @@
// 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

@@ -1,66 +0,0 @@
// 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

@@ -1,51 +0,0 @@
// 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
}

View File

@@ -1,121 +0,0 @@
// 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
}

View File

@@ -1,136 +0,0 @@
// 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,65 +1,38 @@
# This file contains sha256 checksums of optional build dependencies. # This file contains sha256 checksums of optional build dependencies.
# version:spec-tests 1.0.6 9920d3306a1ac536cdd2c796d6cb3c54bc559c226fc3cc39c32f1e0bd7f50d2a go1.18.5.src.tar.gz
# https://github.com/ethereum/execution-spec-tests/releases 828eeca8b5abea3e56921df8fa4b1101380a5ebcfee10acbc8ffe7ec0bf5876b go1.18.5.darwin-amd64.tar.gz
# https://github.com/ethereum/execution-spec-tests/releases/download/v1.0.6/ 923a377c6fc9a2c789f5db61c24b8f64133f7889056897449891f256af34065f go1.18.5.darwin-arm64.tar.gz
485af7b66cf41eb3a8c1bd46632913b8eb95995df867cf665617bbc9b4beedd1 fixtures_develop.tar.gz c3d90264a706e2d88cfb44126dc6f0d008a48f00732e04bc377cea1a2b716a7c go1.18.5.freebsd-386.tar.gz
0de23843c568d388bc0f0e390a8966938cccaae0d74b698325f7175bac04e0c6 go1.18.5.freebsd-amd64.tar.gz
0c44f85d146c6f98c34e8ff436a42af22e90e36fe232d3d9d3101f23fd61362b go1.18.5.linux-386.tar.gz
9e5de37f9c49942c601b191ac5fba404b868bfc21d446d6960acc12283d6e5f2 go1.18.5.linux-amd64.tar.gz
006f6622718212363fa1ff004a6ab4d87bbbe772ec5631bab7cac10be346e4f1 go1.18.5.linux-arm64.tar.gz
d5ac34ac5f060a5274319aa04b7b11e41b123bd7887d64efb5f44ead236957af go1.18.5.linux-armv6l.tar.gz
2e37fb9c7cbaedd4e729492d658aa4cde821fc94117391a8105c13b25ca1c84b go1.18.5.linux-ppc64le.tar.gz
e3d536e7873639f85353e892444f83b14cb6670603961f215986ae8e28e8e07a go1.18.5.linux-s390x.tar.gz
7b3142ec0c5db991e7f73a231662a92429b90ee151fe47557acb566d8d9ae4d3 go1.18.5.windows-386.zip
73753620602d4b4469770040c53db55e5dd6af2ad07ecc18f71f164c3224eaad go1.18.5.windows-amd64.zip
4d154626affff12ef73ea1017af0e5b52dbc839ef92f6f9e76cf4f71278a5744 go1.18.5.windows-arm64.zip
# version:golang 1.21.6 658078aaaf7608693f37c4cf1380b2af418ab8b2d23fdb33e7e2d4339328590e golangci-lint-1.46.2-darwin-amd64.tar.gz
# https://go.dev/dl/ 81f9b4afd62ec5e612ef8bc3b1d612a88b56ff289874831845cdad394427385f golangci-lint-1.46.2-darwin-arm64.tar.gz
124926a62e45f78daabbaedb9c011d97633186a33c238ffc1e25320c02046248 go1.21.6.src.tar.gz 943486e703e62ec55ecd90caeb22bcd39f8cc3962a93eec18c06b7bae12cb46f golangci-lint-1.46.2-freebsd-386.tar.gz
31d6ecca09010ab351e51343a5af81d678902061fee871f912bdd5ef4d778850 go1.21.6.darwin-amd64.tar.gz a75dd9ba7e08e8315c411697171db5375c0f6a1ece9e6fbeb9e9a4386822e17d golangci-lint-1.46.2-freebsd-amd64.tar.gz
0ff541fb37c38e5e5c5bcecc8f4f43c5ffd5e3a6c33a5d3e4003ded66fcfb331 go1.21.6.darwin-arm64.tar.gz 83eedca1af72e8be055a1235177eb1b33524fbf08bec5730df2e6c3efade2b23 golangci-lint-1.46.2-freebsd-armv6.tar.gz
a1d1a149b34bf0f53965a237682c6da1140acabb131bf0e597240e4a140b0e5e go1.21.6.freebsd-386.tar.gz 513d276c490de6f82baa01f9346d8d78b385f2ae97608f42f05d1f0f1314cd54 golangci-lint-1.46.2-freebsd-armv7.tar.gz
de59e1217e4398b1522eed8dddabab2fa1b97aecbdca3af08e34832b4f0e3f81 go1.21.6.freebsd-amd64.tar.gz 461a60016d516c69d406dc3e2d4957b722dbe684b7085dfac4802d0f84409e27 golangci-lint-1.46.2-linux-386.tar.gz
05d09041b5a1193c14e4b2db3f7fcc649b236c567f5eb93305c537851b72dd95 go1.21.6.linux-386.tar.gz 242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b golangci-lint-1.46.2-linux-amd64.tar.gz
3f934f40ac360b9c01f616a9aa1796d227d8b0328bf64cb045c7b8c4ee9caea4 go1.21.6.linux-amd64.tar.gz ff5448ada2b3982581984d64b0dec614dba0a3ea4cab2d6a343c77927fc89f7e golangci-lint-1.46.2-linux-arm64.tar.gz
e2e8aa88e1b5170a0d495d7d9c766af2b2b6c6925a8f8956d834ad6b4cacbd9a go1.21.6.linux-arm64.tar.gz 177f5210ef04aee282bfbc6ec519d36af5fb7d2b2c8d3f4ea5e59fdba71b0a27 golangci-lint-1.46.2-linux-armv6.tar.gz
6a8eda6cc6a799ff25e74ce0c13fdc1a76c0983a0bb07c789a2a3454bf6ec9b2 go1.21.6.linux-armv6l.tar.gz 10dd512a36ee978a1009edbca3ba3af410f0fda8df4d85f0e4793a24213870cc golangci-lint-1.46.2-linux-armv7.tar.gz
e872b1e9a3f2f08fd4554615a32ca9123a4ba877ab6d19d36abc3424f86bc07f go1.21.6.linux-ppc64le.tar.gz 67779fa517c688c9db1090c3c456117d95c6b92979c623fe8cce8fb84251f21e golangci-lint-1.46.2-linux-mips64.tar.gz
92894d0f732d3379bc414ffdd617eaadad47e1d72610e10d69a1156db03fc052 go1.21.6.linux-s390x.tar.gz c085f0f57bdccbb2c902a41b72ce210a3dfff16ca856789374745ab52004b6ee golangci-lint-1.46.2-linux-mips64le.tar.gz
65b38857135cf45c80e1d267e0ce4f80fe149326c68835217da4f2da9b7943fe go1.21.6.windows-386.zip abecef6421499248e58ed75d2938bc12b4b1f98b057f25060680b77bb51a881e golangci-lint-1.46.2-linux-ppc64le.tar.gz
27ac9dd6e66fb3fd0acfa6792ff053c86e7d2c055b022f4b5d53bfddec9e3301 go1.21.6.windows-amd64.zip 134843a8f5c5c182c11979ea75f5866945d54757b2a04f3e5e04a0cf4fbf3a39 golangci-lint-1.46.2-linux-riscv64.tar.gz
b93aff8f3c882c764c66a39b7a1483b0460e051e9992bf3435479129e5051bcd go1.21.6.windows-arm64.zip 9fe21a9476567aafe7a2e1a926b9641a39f920d4c0ea8eda9d968bc6136337f9 golangci-lint-1.46.2-linux-s390x.tar.gz
b48a421ec12a43f8fc8f977b9cf7d4a1ea1c4b97f803a238de7d3ce4ab23a84b golangci-lint-1.46.2-windows-386.zip
# version:golangci 1.55.2 604acc1378a566abb0eac799362f3a37b7fcb5fa2268aeb2d5d954c829367301 golangci-lint-1.46.2-windows-amd64.zip
# https://github.com/golangci/golangci-lint/releases/ 927def10db073da9687594072e6a3d9c891f67fa897105a2cfd715e018e7386c golangci-lint-1.46.2-windows-arm64.zip
# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/ 729b76ed1d8b4e2612e38772b211503cb940e00a137bbaace1aa066f7c943737 golangci-lint-1.46.2-windows-armv6.zip
632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz ea27c86d91e0b245ecbcfbf6cdb4ac0522d4bc6dca56bba02ea1bc77ad2917ac golangci-lint-1.46.2-windows-armv7.zip
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

@@ -24,20 +24,24 @@ Usage: go run build/ci.go <command> <command flags/arguments>
Available commands are: Available commands are:
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
test [ -coverage ] [ packages... ] -- runs the tests test [ -coverage ] [ packages... ] -- runs the tests
lint -- runs certain pre-selected linters lint -- runs certain pre-selected linters
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts
importkeys -- imports signing keys from env importkeys -- imports signing keys from env
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
nsis -- creates a Windows NSIS installer nsis -- creates a Windows NSIS installer
purge [ -store blobstore ] [ -days threshold ] -- purges old archives from the blobstore aar [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an Android archive
xcode [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an iOS XCode framework
purge [ -store blobstore ] [ -days threshold ] -- purges old archives from the blobstore
For all commands, -n prevents execution of external programs (dry run mode). For all commands, -n prevents execution of external programs (dry run mode).
*/ */
package main package main
import ( import (
"bufio"
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"flag" "flag"
@@ -47,6 +51,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@@ -73,6 +78,7 @@ var (
executablePath("bootnode"), executablePath("bootnode"),
executablePath("evm"), executablePath("evm"),
executablePath("geth"), executablePath("geth"),
executablePath("puppeth"),
executablePath("rlpdump"), executablePath("rlpdump"),
executablePath("clef"), executablePath("clef"),
} }
@@ -95,6 +101,10 @@ var (
BinaryName: "geth", BinaryName: "geth",
Description: "Ethereum CLI client.", Description: "Ethereum CLI client.",
}, },
{
BinaryName: "puppeth",
Description: "Ethereum private network manager.",
},
{ {
BinaryName: "rlpdump", BinaryName: "rlpdump",
Description: "Developer utility tool that prints RLP structures.", Description: "Developer utility tool that prints RLP structures.",
@@ -120,16 +130,15 @@ var (
// Distros for which packages are created. // Distros for which packages are created.
// Note: vivid is unsupported because there is no golang-1.6 package for it. // Note: vivid is unsupported because there is no golang-1.6 package for it.
// Note: the following Ubuntu releases have been officially deprecated on Launchpad: // Note: the following Ubuntu releases have been officially deprecated on Launchpad:
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish, // wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite
// kinetic
debDistroGoBoots = map[string]string{ debDistroGoBoots = map[string]string{
"trusty": "golang-1.11", // 14.04, EOL: 04/2024 "trusty": "golang-1.11", // EOL: 04/2024
"xenial": "golang-go", // 16.04, EOL: 04/2026 "xenial": "golang-go", // EOL: 04/2026
"bionic": "golang-go", // 18.04, EOL: 04/2028 "bionic": "golang-go", // EOL: 04/2028
"focal": "golang-go", // 20.04, EOL: 04/2030 "focal": "golang-go", // EOL: 04/2030
"jammy": "golang-go", // 22.04, EOL: 04/2032 "impish": "golang-go", // EOL: 07/2022
"lunar": "golang-go", // 23.04, EOL: 01/2024 "jammy": "golang-go", // EOL: 04/2032
"mantic": "golang-go", // 23.10, EOL: 07/2024 //"kinetic": "golang-go", // EOL: 07/2023
} }
debGoBootPaths = map[string]string{ debGoBootPaths = map[string]string{
@@ -137,8 +146,10 @@ var (
"golang-go": "/usr/lib/go", "golang-go": "/usr/lib/go",
} }
// This is where the tests should be unpacked. // This is the version of go that will be downloaded by
executionSpecTestsDir = "tests/spec-tests" //
// go run ci.go install -dlgo
dlgoVersion = "1.18.5"
) )
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
@@ -174,10 +185,12 @@ func main() {
doDebianSource(os.Args[2:]) doDebianSource(os.Args[2:])
case "nsis": case "nsis":
doWindowsInstaller(os.Args[2:]) doWindowsInstaller(os.Args[2:])
case "aar":
doAndroidArchive(os.Args[2:])
case "xcode":
doXCodeFramework(os.Args[2:])
case "purge": case "purge":
doPurge(os.Args[2:]) doPurge(os.Args[2:])
case "sanitycheck":
doSanityCheck()
default: default:
log.Fatal("unknown command ", os.Args[1]) log.Fatal("unknown command ", os.Args[1])
} }
@@ -193,23 +206,19 @@ func doInstall(cmdline []string) {
staticlink = flag.Bool("static", false, "Create statically-linked executable") staticlink = flag.Bool("static", false, "Create statically-linked executable")
) )
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
env := build.Env()
// Configure the toolchain. // Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc} tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
if *dlgo { if *dlgo {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := build.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb) tc.Root = build.DownloadGo(csdb, dlgoVersion)
} }
// Disable CLI markdown doc generation in release builds. // Disable CLI markdown doc generation in release builds.
buildTags := []string{"urfave_cli_no_docs"} buildTags := []string{"urfave_cli_no_docs"}
// Enable linking the CKZG library since we can make it work with additional flags.
if env.UbuntuVersion != "trusty" {
buildTags = append(buildTags, "ckzg")
}
// Configure the build. // Configure the build.
env := build.Env()
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...) gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
// arm64 CI builders are memory-constrained and can't handle concurrent builds, // arm64 CI builders are memory-constrained and can't handle concurrent builds,
@@ -218,6 +227,7 @@ func doInstall(cmdline []string) {
if env.CI && runtime.GOARCH == "arm64" { if env.CI && runtime.GOARCH == "arm64" {
gobuild.Args = append(gobuild.Args, "-p", "1") gobuild.Args = append(gobuild.Args, "-p", "1")
} }
// We use -trimpath to avoid leaking local paths into the built executables. // We use -trimpath to avoid leaking local paths into the built executables.
gobuild.Args = append(gobuild.Args, "-trimpath") gobuild.Args = append(gobuild.Args, "-trimpath")
@@ -245,8 +255,8 @@ func doInstall(cmdline []string) {
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) { func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
var ld []string var ld []string
if env.Commit != "" { if env.Commit != "" {
ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitCommit="+env.Commit) ld = append(ld, "-X", "main.gitCommit="+env.Commit)
ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitDate="+env.Date) ld = append(ld, "-X", "main.gitDate="+env.Date)
} }
// Strip DWARF on darwin. This used to be required for certain things, // Strip DWARF on darwin. This used to be required for certain things,
// and there is no downside to this, so we just keep doing it. // and there is no downside to this, so we just keep doing it.
@@ -286,31 +296,17 @@ func doTest(cmdline []string) {
coverage = flag.Bool("coverage", false, "Whether to record code coverage") coverage = flag.Bool("coverage", false, "Whether to record code coverage")
verbose = flag.Bool("v", false, "Whether to log verbosely") verbose = flag.Bool("v", false, "Whether to log verbosely")
race = flag.Bool("race", false, "Execute the race detector") race = flag.Bool("race", false, "Execute the race detector")
short = flag.Bool("short", false, "Pass the 'short'-flag to go test")
cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads")
) )
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
// Get test fixtures.
csdb := build.MustLoadChecksums("build/checksums.txt")
downloadSpecTestFixtures(csdb, *cachedir)
// Configure the toolchain. // Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc} tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
if *dlgo { if *dlgo {
tc.Root = build.DownloadGo(csdb) csdb := build.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb, dlgoVersion)
} }
gotest := tc.Go("test") gotest := tc.Go("test")
// CI needs a bit more time for the statetests (default 10m).
gotest.Args = append(gotest.Args, "-timeout=20m")
// Enable CKZG backend in CI.
gotest.Args = append(gotest.Args, "-tags=ckzg")
// Enable integration-tests
gotest.Args = append(gotest.Args, "-tags=integrationtests")
// Test a single package at a time. CI builders are slow // Test a single package at a time. CI builders are slow
// and some tests run into timeouts under load. // and some tests run into timeouts under load.
gotest.Args = append(gotest.Args, "-p", "1") gotest.Args = append(gotest.Args, "-p", "1")
@@ -323,9 +319,6 @@ func doTest(cmdline []string) {
if *race { if *race {
gotest.Args = append(gotest.Args, "-race") gotest.Args = append(gotest.Args, "-race")
} }
if *short {
gotest.Args = append(gotest.Args, "-short")
}
packages := []string{"./..."} packages := []string{"./..."}
if len(flag.CommandLine.Args()) > 0 { if len(flag.CommandLine.Args()) > 0 {
@@ -335,25 +328,6 @@ func doTest(cmdline []string) {
build.MustRun(gotest) build.MustRun(gotest)
} }
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string {
executionSpecTestsVersion, err := build.Version(csdb, "spec-tests")
if err != nil {
log.Fatal(err)
}
ext := ".tar.gz"
base := "fixtures_develop" // TODO(MariusVanDerWijden) rename once the version becomes part of the filename
url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/v%s/%s%s", executionSpecTestsVersion, base, ext)
archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFile(url, archivePath); err != nil {
log.Fatal(err)
}
if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil {
log.Fatal(err)
}
return filepath.Join(cachedir, base)
}
// doLint runs golangci-lint on requested packages. // doLint runs golangci-lint on requested packages.
func doLint(cmdline []string) { func doLint(cmdline []string) {
var ( var (
@@ -367,17 +341,15 @@ func doLint(cmdline []string) {
linter := downloadLinter(*cachedir) linter := downloadLinter(*cachedir)
lflags := []string{"run", "--config", ".golangci.yml"} lflags := []string{"run", "--config", ".golangci.yml"}
build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) build.MustRunCommand(linter, append(lflags, packages...)...)
fmt.Println("You have achieved perfection.") fmt.Println("You have achieved perfection.")
} }
// downloadLinter downloads and unpacks golangci-lint. // downloadLinter downloads and unpacks golangci-lint.
func downloadLinter(cachedir string) string { func downloadLinter(cachedir string) string {
const version = "1.46.2"
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := build.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "golangci")
if err != nil {
log.Fatal(err)
}
arch := runtime.GOARCH arch := runtime.GOARCH
ext := ".tar.gz" ext := ".tar.gz"
@@ -499,6 +471,10 @@ func maybeSkipArchive(env build.Environment) {
log.Printf("skipping archive creation because this is a PR build") log.Printf("skipping archive creation because this is a PR build")
os.Exit(0) os.Exit(0)
} }
if env.IsCronJob {
log.Printf("skipping archive creation because this is a cron job")
os.Exit(0)
}
if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") {
log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag) log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag)
os.Exit(0) os.Exit(0)
@@ -693,11 +669,10 @@ func doDebianSource(cmdline []string) {
gpg.Stdin = bytes.NewReader(key) gpg.Stdin = bytes.NewReader(key)
build.MustRun(gpg) build.MustRun(gpg)
} }
// Download and verify the Go source packages.
var ( // Download and verify the Go source package.
gobootbundle = downloadGoBootstrapSources(*cachedir) gobundle := downloadGoSources(*cachedir)
gobundle = downloadGoSources(*cachedir)
)
// Download all the dependencies needed to build the sources and run the ci script // Download all the dependencies needed to build the sources and run the ci script
srcdepfetch := tc.Go("mod", "download") srcdepfetch := tc.Go("mod", "download")
srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath"))
@@ -714,19 +689,12 @@ func doDebianSource(cmdline []string) {
meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
pkgdir := stageDebianSource(*workdir, meta) pkgdir := stageDebianSource(*workdir, meta)
// Add bootstrapper Go source code // Add Go source code
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
log.Fatalf("Failed to extract bootstrapper Go sources: %v", err)
}
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".goboot")); err != nil {
log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err)
}
// Add builder Go source code
if err := build.ExtractArchive(gobundle, pkgdir); err != nil { if err := build.ExtractArchive(gobundle, pkgdir); err != nil {
log.Fatalf("Failed to extract builder Go sources: %v", err) log.Fatalf("Failed to extract Go sources: %v", err)
} }
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil {
log.Fatalf("Failed to rename builder Go source folder: %v", err) log.Fatalf("Failed to rename Go source folder: %v", err)
} }
// Add all dependency modules in compressed form // Add all dependency modules in compressed form
os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755) os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755)
@@ -755,30 +723,9 @@ func doDebianSource(cmdline []string) {
} }
} }
// downloadGoBootstrapSources downloads the Go source tarball that will be used
// 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)
if err := csdb.DownloadFile(url, dst); err != nil {
log.Fatal(err)
}
return dst
}
// downloadGoSources downloads the Go source tarball. // downloadGoSources downloads the Go source tarball.
func downloadGoSources(cachedir string) string { func downloadGoSources(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := build.MustLoadChecksums("build/checksums.txt")
dlgoVersion, err := build.Version(csdb, "golang")
if err != nil {
log.Fatal(err)
}
file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
url := "https://dl.google.com/go/" + file url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file) dst := filepath.Join(cachedir, file)
@@ -1035,10 +982,7 @@ func doWindowsInstaller(cmdline []string) {
if env.Commit != "" { if env.Commit != "" {
version[2] += "-" + env.Commit[:8] version[2] += "-" + env.Commit[:8]
} }
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe") installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe")
if err != nil {
log.Fatalf("Failed to convert installer file path: %v", err)
}
build.MustRunCommand("makensis.exe", build.MustRunCommand("makensis.exe",
"/DOUTPUTFILE="+installer, "/DOUTPUTFILE="+installer,
"/DMAJORVERSION="+version[0], "/DMAJORVERSION="+version[0],
@@ -1053,6 +997,236 @@ func doWindowsInstaller(cmdline []string) {
} }
} }
// Android archives
func doAndroidArchive(cmdline []string) {
var (
local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`)
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. ANDROID_SIGNIFY_KEY)`)
deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`)
upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`)
)
flag.CommandLine.Parse(cmdline)
env := build.Env()
tc := new(build.GoToolchain)
// Sanity check that the SDK and NDK are installed and set
if os.Getenv("ANDROID_HOME") == "" {
log.Fatal("Please ensure ANDROID_HOME points to your Android SDK")
}
// Build gomobile.
install := tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest")
install.Env = append(install.Env)
build.MustRun(install)
// Ensure all dependencies are available. This is required to make
// gomobile bind work because it expects go.sum to contain all checksums.
build.MustRun(tc.Go("mod", "download"))
// Build the Android archive and Maven resources
build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
if *local {
// If we're building locally, copy bundle to build dir and skip Maven
os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar"))
os.Rename("geth-sources.jar", filepath.Join(GOBIN, "geth-sources.jar"))
return
}
meta := newMavenMetadata(env)
build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta)
// Skip Maven deploy and Azure upload for PR builds
maybeSkipArchive(env)
// Sign and upload the archive to Azure
archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar"
os.Rename("geth.aar", archive)
if err := archiveUpload(archive, *upload, *signer, *signify); err != nil {
log.Fatal(err)
}
// Sign and upload all the artifacts to Maven Central
os.Rename(archive, meta.Package+".aar")
if *signer != "" && *deploy != "" {
// Import the signing key into the local GPG instance
key := getenvBase64(*signer)
gpg := exec.Command("gpg", "--import")
gpg.Stdin = bytes.NewReader(key)
build.MustRun(gpg)
keyID, err := build.PGPKeyID(string(key))
if err != nil {
log.Fatal(err)
}
// Upload the artifacts to Sonatype and/or Maven Central
repo := *deploy + "/service/local/staging/deploy/maven2"
if meta.Develop {
repo = *deploy + "/content/repositories/snapshots"
}
build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X",
"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
"-Dgpg.keyname="+keyID,
"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
}
}
func gomobileTool(subcmd string, args ...string) *exec.Cmd {
cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd)
cmd.Args = append(cmd.Args, args...)
cmd.Env = []string{
"PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"),
}
for _, e := range os.Environ() {
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") || strings.HasPrefix(e, "GOBIN=") {
continue
}
cmd.Env = append(cmd.Env, e)
}
cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
return cmd
}
type mavenMetadata struct {
Version string
Package string
Develop bool
Contributors []mavenContributor
}
type mavenContributor struct {
Name string
Email string
}
func newMavenMetadata(env build.Environment) mavenMetadata {
// Collect the list of authors from the repo root
contribs := []mavenContributor{}
if authors, err := os.Open("AUTHORS"); err == nil {
defer authors.Close()
scanner := bufio.NewScanner(authors)
for scanner.Scan() {
// Skip any whitespace from the authors list
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
// Split the author and insert as a contributor
re := regexp.MustCompile("([^<]+) <(.+)>")
parts := re.FindStringSubmatch(line)
if len(parts) == 3 {
contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]})
}
}
}
// Render the version and package strings
version := params.Version
if isUnstableBuild(env) {
version += "-SNAPSHOT"
}
return mavenMetadata{
Version: version,
Package: "geth-" + version,
Develop: isUnstableBuild(env),
Contributors: contribs,
}
}
// XCode frameworks
func doXCodeFramework(cmdline []string) {
var (
local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`)
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. IOS_SIGNIFY_KEY)`)
deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
)
flag.CommandLine.Parse(cmdline)
env := build.Env()
tc := new(build.GoToolchain)
// Build gomobile.
build.MustRun(tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
// Build the iOS XCode framework
bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/ethereum/go-ethereum/mobile")
if *local {
// If we're building locally, use the build folder and stop afterwards
bind.Dir = GOBIN
build.MustRun(bind)
return
}
// Create the archive.
maybeSkipArchive(env)
archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit))
if err := os.MkdirAll(archive, 0755); err != nil {
log.Fatal(err)
}
bind.Dir, _ = filepath.Abs(archive)
build.MustRun(bind)
build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive)
// Sign and upload the framework to Azure
if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil {
log.Fatal(err)
}
// Prepare and upload a PodSpec to CocoaPods
if *deploy != "" {
meta := newPodMetadata(env, archive)
build.Render("build/pod.podspec", "Geth.podspec", 0755, meta)
build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings")
}
}
type podMetadata struct {
Version string
Commit string
Archive string
Contributors []podContributor
}
type podContributor struct {
Name string
Email string
}
func newPodMetadata(env build.Environment, archive string) podMetadata {
// Collect the list of authors from the repo root
contribs := []podContributor{}
if authors, err := os.Open("AUTHORS"); err == nil {
defer authors.Close()
scanner := bufio.NewScanner(authors)
for scanner.Scan() {
// Skip any whitespace from the authors list
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
// Split the author and insert as a contributor
re := regexp.MustCompile("([^<]+) <(.+)>")
parts := re.FindStringSubmatch(line)
if len(parts) == 3 {
contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]})
}
}
}
version := params.Version
if isUnstableBuild(env) {
version += "-unstable." + env.Buildnum
}
return podMetadata{
Archive: archive,
Version: version,
Commit: env.Commit,
Contributors: contribs,
}
}
// Binary distribution cleanups // Binary distribution cleanups
func doPurge(cmdline []string) { func doPurge(cmdline []string) {
@@ -1105,7 +1279,3 @@ func doPurge(cmdline []string) {
log.Fatal(err) log.Fatal(err)
} }
} }
func doSanityCheck() {
build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt"))
}

View File

@@ -16,11 +16,7 @@ override_dh_auto_build:
# We can't download a fresh Go within Launchpad, so we're shipping and building # We can't download a fresh Go within Launchpad, so we're shipping and building
# one on the fly. However, we can't build it inside the go-ethereum folder as # one on the fly. However, we can't build it inside the go-ethereum folder as
# bootstrapping clashes with go modules, so build in a sibling folder. # bootstrapping clashes with go modules, so build in a sibling folder.
# (mv .go ../ && cd ../.go/src && ./make.bash)
# We're also shipping the bootstrapper as of Go 1.20 as it had minimum version
# requirements opposed to older versions of Go.
(mv .goboot ../ && cd ../.goboot/src && ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot ./make.bash)
# We can't download external go modules within Launchpad, so we're shipping the # We can't download external go modules within Launchpad, so we're shipping the
# entire dependency source cache with go-ethereum. # entire dependency source cache with go-ethereum.
@@ -28,7 +24,7 @@ override_dh_auto_build:
mv .mod $(GOPATH)/pkg/mod mv .mod $(GOPATH)/pkg/mod
# A fresh Go was built, all dependency downloads faked, hope build works now # A fresh Go was built, all dependency downloads faked, hope build works now
../.go/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}} -ubuntu {{.Distro}} ../.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}}
override_dh_auto_test: override_dh_auto_test:

57
build/mvn.pom Normal file
View File

@@ -0,0 +1,57 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ethereum</groupId>
<artifactId>geth</artifactId>
<version>{{.Version}}</version>
<packaging>aar</packaging>
<name>Android Ethereum Client</name>
<description>Android port of the go-ethereum libraries and node</description>
<url>https://github.com/ethereum/go-ethereum</url>
<inceptionYear>2015</inceptionYear>
<licenses>
<license>
<name>GNU Lesser General Public License, Version 3.0</name>
<url>https://www.gnu.org/licenses/lgpl-3.0.en.html</url>
<distribution>repo</distribution>
</license>
</licenses>
<organization>
<name>Ethereum</name>
<url>https://ethereum.org</url>
</organization>
<developers>
<developer>
<id>karalabe</id>
<name>Péter Szilágyi</name>
<email>peterke@gmail.com</email>
<url>https://github.com/karalabe</url>
<properties>
<picUrl>https://www.gravatar.com/avatar/2ecbf0f5b4b79eebf8c193e5d324357f?s=256</picUrl>
</properties>
</developer>
</developers>
<contributors>{{range .Contributors}}
<contributor>
<name>{{.Name}}</name>
<email>{{.Email}}</email>
</contributor>{{end}}
</contributors>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/ethereum/go-ethereum/issues/</url>
</issueManagement>
<scm>
<url>https://github.com/ethereum/go-ethereum</url>
</scm>
</project>

24
build/mvn.settings Normal file
View File

@@ -0,0 +1,24 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>ossrh</id>
<username>${env.ANDROID_SONATYPE_USERNAME}</username>
<password>${env.ANDROID_SONATYPE_PASSWORD}</password>
</server>
</servers>
<profiles>
<profile>
<id>ossrh</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.passphrase></gpg.passphrase>
</properties>
</profile>
</profiles>
</settings>

View File

@@ -20,7 +20,7 @@
# - NSIS Large Strings build, http://nsis.sourceforge.net/Special_Builds # - NSIS Large Strings build, http://nsis.sourceforge.net/Special_Builds
# - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi) # - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi)
# #
# After installing NSIS extra the NSIS Large Strings build zip and replace the makensis.exe and the # After intalling NSIS extra the NSIS Large Strings build zip and replace the makensis.exe and the
# files found in Stub. # files found in Stub.
# #
# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller # based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller

22
build/pod.podspec Normal file
View File

@@ -0,0 +1,22 @@
Pod::Spec.new do |spec|
spec.name = 'Geth'
spec.version = '{{.Version}}'
spec.license = { :type => 'GNU Lesser General Public License, Version 3.0' }
spec.homepage = 'https://github.com/ethereum/go-ethereum'
spec.authors = { {{range .Contributors}}
'{{.Name}}' => '{{.Email}}',{{end}}
}
spec.summary = 'iOS Ethereum Client'
spec.source = { :git => 'https://github.com/ethereum/go-ethereum.git', :commit => '{{.Commit}}' }
spec.platform = :ios
spec.ios.deployment_target = '9.0'
spec.ios.vendored_frameworks = 'Frameworks/Geth.framework'
spec.prepare_command = <<-CMD
curl https://gethstore.blob.core.windows.net/builds/{{.Archive}}.tar.gz | tar -xvz
mkdir Frameworks
mv {{.Archive}}/Geth.framework Frameworks
rm -rf {{.Archive}}
CMD
end

View File

@@ -24,4 +24,8 @@ import (
_ "github.com/fjl/gencodec" _ "github.com/fjl/gencodec"
_ "github.com/golang/protobuf/protoc-gen-go" _ "github.com/golang/protobuf/protoc-gen-go"
_ "golang.org/x/tools/cmd/stringer" _ "golang.org/x/tools/cmd/stringer"
// Tool imports for mobile build.
_ "golang.org/x/mobile/cmd/gobind"
_ "golang.org/x/mobile/cmd/gomobile"
) )

View File

@@ -46,13 +46,12 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
"time" "time"
"golang.org/x/exp/slices"
) )
var ( var (
@@ -65,8 +64,10 @@ var (
"vendor/", "tests/testdata/", "build/", "vendor/", "tests/testdata/", "build/",
// don't relicense vendored sources // don't relicense vendored sources
"cmd/internal/browser",
"common/bitutil/bitutil", "common/bitutil/bitutil",
"common/prque/", "common/prque/",
"consensus/ethash/xor.go",
"crypto/blake2b/", "crypto/blake2b/",
"crypto/bn256/", "crypto/bn256/",
"crypto/bls12381/", "crypto/bls12381/",
@@ -76,7 +77,6 @@ var (
"log/", "log/",
"metrics/", "metrics/",
"signer/rules/deps", "signer/rules/deps",
"internal/reexec",
// skip special licenses // skip special licenses
"crypto/secp256k1", // Relicensed to BSD-3 via https://github.com/ethereum/go-ethereum/pull/17225 "crypto/secp256k1", // Relicensed to BSD-3 via https://github.com/ethereum/go-ethereum/pull/17225
@@ -152,6 +152,13 @@ func (i info) gpl() bool {
return false return false
} }
// authors implements the sort.Interface for strings in case-insensitive mode.
type authors []string
func (as authors) Len() int { return len(as) }
func (as authors) Less(i, j int) bool { return strings.ToLower(as[i]) < strings.ToLower(as[j]) }
func (as authors) Swap(i, j int) { as[i], as[j] = as[j], as[i] }
func main() { func main() {
var ( var (
files = getFiles() files = getFiles()
@@ -292,9 +299,7 @@ func writeAuthors(files []string) {
} }
} }
// Write sorted list of authors back to the file. // Write sorted list of authors back to the file.
slices.SortFunc(list, func(a, b string) bool { sort.Sort(authors(list))
return strings.ToLower(a) < strings.ToLower(b)
})
content := new(bytes.Buffer) content := new(bytes.Buffer)
content.WriteString(authorsFileHeader) content.WriteString(authorsFileHeader)
for _, a := range list { for _, a := range list {
@@ -337,10 +342,7 @@ func isGenerated(file string) bool {
} }
defer fd.Close() defer fd.Close()
buf := make([]byte, 2048) buf := make([]byte, 2048)
n, err := fd.Read(buf) n, _ := fd.Read(buf)
if err != nil {
return false
}
buf = buf[:n] buf = buf[:n]
for _, l := range bytes.Split(buf, []byte("\n")) { for _, l := range bytes.Split(buf, []byte("\n")) {
if bytes.HasPrefix(l, []byte("// Code generated")) { if bytes.HasPrefix(l, []byte("// Code generated")) {

View File

@@ -33,6 +33,14 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var (
// Git SHA1 commit hash of the release (set via linker flags)
gitCommit = ""
gitDate = ""
app *cli.App
)
var ( var (
// Flags needed by abigen // Flags needed by abigen
abiFlag = &cli.StringFlag{ abiFlag = &cli.StringFlag{
@@ -65,7 +73,7 @@ var (
} }
langFlag = &cli.StringFlag{ langFlag = &cli.StringFlag{
Name: "lang", Name: "lang",
Usage: "Destination language for the bindings (go)", Usage: "Destination language for the bindings (go, java, objc)",
Value: "go", Value: "go",
} }
aliasFlag = &cli.StringFlag{ aliasFlag = &cli.StringFlag{
@@ -74,9 +82,8 @@ var (
} }
) )
var app = flags.NewApp("Ethereum ABI wrapper code generator")
func init() { func init() {
app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
app.Name = "abigen" app.Name = "abigen"
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
abiFlag, abiFlag,
@@ -102,6 +109,11 @@ func abigen(c *cli.Context) error {
switch c.String(langFlag.Name) { switch c.String(langFlag.Name) {
case "go": case "go":
lang = bind.LangGo lang = bind.LangGo
case "java":
lang = bind.LangJava
case "objc":
lang = bind.LangObjC
utils.Fatalf("Objc binding generation is uncompleted")
default: default:
utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.String(langFlag.Name)) utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.String(langFlag.Name))
} }
@@ -149,12 +161,9 @@ func abigen(c *cli.Context) error {
types = append(types, kind) types = append(types, kind)
} else { } else {
// Generate the list of types to exclude from binding // Generate the list of types to exclude from binding
var exclude *nameFilter exclude := make(map[string]bool)
if c.IsSet(excFlag.Name) { for _, kind := range strings.Split(c.String(excFlag.Name), ",") {
var err error exclude[strings.ToLower(kind)] = true
if exclude, err = newNameFilter(strings.Split(c.String(excFlag.Name), ",")...); err != nil {
utils.Fatalf("Failed to parse excludes: %v", err)
}
} }
var contracts map[string]*compiler.Contract var contracts map[string]*compiler.Contract
@@ -179,11 +188,7 @@ func abigen(c *cli.Context) error {
} }
// Gather all non-excluded contract for binding // Gather all non-excluded contract for binding
for name, contract := range contracts { for name, contract := range contracts {
// fully qualified name is of the form <solFilePath>:<type> if exclude[strings.ToLower(name)] {
nameParts := strings.Split(name, ":")
typeName := nameParts[len(nameParts)-1]
if exclude != nil && exclude.Matches(name) {
fmt.Fprintf(os.Stderr, "excluding: %v\n", name)
continue continue
} }
abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
@@ -193,14 +198,15 @@ func abigen(c *cli.Context) error {
abis = append(abis, string(abi)) abis = append(abis, string(abi))
bins = append(bins, contract.Code) bins = append(bins, contract.Code)
sigs = append(sigs, contract.Hashes) sigs = append(sigs, contract.Hashes)
types = append(types, typeName) nameParts := strings.Split(name, ":")
types = append(types, nameParts[len(nameParts)-1])
// Derive the library placeholder which is a 34 character prefix of the // Derive the library placeholder which is a 34 character prefix of the
// hex encoding of the keccak256 hash of the fully qualified library name. // hex encoding of the keccak256 hash of the fully qualified library name.
// Note that the fully qualified library name is the path of its source // Note that the fully qualified library name is the path of its source
// file and the library name separated by ":". // file and the library name separated by ":".
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x
libs[libPattern] = typeName libs[libPattern] = nameParts[len(nameParts)-1]
} }
} }
// Extract all aliases from the flags // Extract all aliases from the flags
@@ -232,7 +238,7 @@ func abigen(c *cli.Context) error {
} }
func main() { func main() {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)

View File

@@ -1,58 +0,0 @@
package main
import (
"fmt"
"strings"
)
type nameFilter struct {
fulls map[string]bool // path/to/contract.sol:Type
files map[string]bool // path/to/contract.sol:*
types map[string]bool // *:Type
}
func newNameFilter(patterns ...string) (*nameFilter, error) {
f := &nameFilter{
fulls: make(map[string]bool),
files: make(map[string]bool),
types: make(map[string]bool),
}
for _, pattern := range patterns {
if err := f.add(pattern); err != nil {
return nil, err
}
}
return f, nil
}
func (f *nameFilter) add(pattern string) error {
ft := strings.Split(pattern, ":")
if len(ft) != 2 {
// filenames and types must not include ':' symbol
return fmt.Errorf("invalid pattern: %s", pattern)
}
file, typ := ft[0], ft[1]
if file == "*" {
f.types[typ] = true
return nil
} else if typ == "*" {
f.files[file] = true
return nil
}
f.fulls[pattern] = true
return nil
}
func (f *nameFilter) Matches(name string) bool {
ft := strings.Split(name, ":")
if len(ft) != 2 {
// If contract names are always of the fully-qualified form
// <filePath>:<type>, then this case will never happen.
return false
}
file, typ := ft[0], ft[1]
// full paths > file paths > types
return f.fulls[name] || f.files[file] || f.types[typ]
}

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