Compare commits
No commits in common. "master" and "v1.17.0" have entirely different histories.
|
|
@ -145,7 +145,7 @@ jobs:
|
|||
|
||||
windows:
|
||||
name: Windows Build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: "win-11"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -155,49 +155,24 @@ jobs:
|
|||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
- name: Install cross toolchain
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install \
|
||||
gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 nsis
|
||||
# Note: gcc.exe only works properly if the corresponding bin/ directory is
|
||||
# contained in PATH.
|
||||
|
||||
- name: "Build (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (amd64)"
|
||||
run: |
|
||||
go run build/ci.go archive -os windows -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -dlgo -arch amd64 -cc %GETH_MINGW%\bin\gcc.exe
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (amd64)"
|
||||
run: |
|
||||
go run build/ci.go nsis -arch amd64 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
GETH_MINGW: 'C:\msys64\mingw64'
|
||||
|
||||
- name: "Build (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (386)"
|
||||
run: |
|
||||
go run build/ci.go archive -os windows -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -dlgo -arch 386 -cc %GETH_MINGW%\bin\gcc.exe
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (386)"
|
||||
run: |
|
||||
go run build/ci.go nsis -arch 386 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
GETH_MINGW: 'C:\msys64\mingw32'
|
||||
|
||||
docker:
|
||||
name: Docker Image
|
||||
|
|
|
|||
6
.github/CODEOWNERS
vendored
|
|
@ -10,18 +10,20 @@ beacon/merkle/ @zsfelfoldi
|
|||
beacon/types/ @zsfelfoldi @fjl
|
||||
beacon/params/ @zsfelfoldi @fjl
|
||||
cmd/evm/ @MariusVanDerWijden @lightclient
|
||||
cmd/keeper/ @gballet
|
||||
core/state/ @rjl493456442
|
||||
crypto/ @gballet @jwasinger @fjl
|
||||
core/ @rjl493456442
|
||||
eth/ @rjl493456442
|
||||
eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger
|
||||
eth/tracers/ @s1na
|
||||
ethclient/ @fjl
|
||||
ethdb/ @rjl493456442
|
||||
event/ @fjl
|
||||
trie/ @rjl493456442 @gballet
|
||||
triedb/ @rjl493456442
|
||||
internal/ethapi/ @fjl @lightclient
|
||||
core/tracing/ @s1na
|
||||
graphql/ @s1na
|
||||
internal/ethapi/ @fjl @s1na @lightclient
|
||||
internal/era/ @lightclient
|
||||
miner/ @MariusVanDerWijden @fjl @rjl493456442
|
||||
node/ @fjl
|
||||
|
|
|
|||
29
.github/workflows/freebsd.yml
vendored
|
|
@ -1,29 +0,0 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- freebsd-github-action
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: FreeBSD-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Test in FreeBSD
|
||||
id: test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y go
|
||||
run: |
|
||||
freebsd-version
|
||||
uname -a
|
||||
go version
|
||||
go run ./build/ci.go test -p 8
|
||||
50
.github/workflows/go.yml
vendored
|
|
@ -7,11 +7,6 @@ on:
|
|||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
# Free runner capacity by cancelling superseded PR runs.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
|
|
@ -74,8 +69,8 @@ jobs:
|
|||
|
||||
- name: Install cross toolchain
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
|
||||
|
||||
- name: Build
|
||||
run: go run build/ci.go test -arch 386 -short -p 8
|
||||
|
|
@ -102,44 +97,3 @@ jobs:
|
|||
|
||||
- name: Run tests
|
||||
run: go run build/ci.go test -p 8
|
||||
|
||||
windows:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
needs: lint
|
||||
runs-on: [self-hosted, windows, x64]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
mingw: 'C:\msys64\mingw64'
|
||||
test: true
|
||||
- arch: '386'
|
||||
mingw: 'C:\msys64\mingw32'
|
||||
test: false
|
||||
env:
|
||||
GETH_MINGW: ${{ matrix.mingw }}
|
||||
GETH_CC: ${{ matrix.mingw }}\bin\gcc.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: false
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -arch ${{ matrix.arch }} -cc %GETH_CC%
|
||||
|
||||
- name: Run tests
|
||||
if: matrix.test
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go test -arch ${{ matrix.arch }} -cc %GETH_CC% -short -p 8
|
||||
|
|
|
|||
3
.mailmap
|
|
@ -48,9 +48,6 @@ Bas van Kervel <bas@ethdev.com> <bas-vk@users.noreply.github.com>
|
|||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
||||
Boqin Qin <bobbqqin@bupt.edu.cn> <Bobbqqin@gmail.com>
|
||||
|
||||
Bosul Mun <bsbs8645@snu.ac.kr>
|
||||
Bosul Mun <bsbs8645@snu.ac.kr> <bosul.mun@ethereum.org>
|
||||
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
|
||||
Charlotte <tqpcharlie@proton.me>
|
||||
|
|
|
|||
102
AGENTS.md
|
|
@ -1,102 +0,0 @@
|
|||
# AGENTS
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Keep changes minimal and focused.** Only modify code directly related to the task at hand. Do not refactor unrelated code, rename existing variables or functions for style, or bundle unrelated fixes into the same commit or PR.
|
||||
- **Do not add, remove, or update dependencies** unless the task explicitly requires it.
|
||||
|
||||
## Pre-Commit Checklist
|
||||
|
||||
Before every commit, run **all** of the following checks and ensure they pass:
|
||||
|
||||
### 1. Formatting
|
||||
|
||||
Before committing, always run `gofmt` and `goimports` on all modified files:
|
||||
|
||||
```sh
|
||||
gofmt -w <modified files>
|
||||
goimports -w <modified files>
|
||||
```
|
||||
|
||||
### 2. Build All Commands
|
||||
|
||||
Verify that all tools compile successfully:
|
||||
|
||||
```sh
|
||||
make all
|
||||
```
|
||||
|
||||
This builds all executables under `cmd/`, including `keeper` which has special build requirements.
|
||||
|
||||
### 3. Tests
|
||||
|
||||
While iterating during development, use `-short` for faster feedback:
|
||||
|
||||
```sh
|
||||
go run ./build/ci.go test -short
|
||||
```
|
||||
|
||||
Before committing, run the full test suite **without** `-short` to ensure all tests pass, including the Ethereum execution-spec tests and all state/block test permutations:
|
||||
|
||||
```sh
|
||||
go run ./build/ci.go test
|
||||
```
|
||||
|
||||
### 4. Linting
|
||||
|
||||
```sh
|
||||
go run ./build/ci.go lint
|
||||
```
|
||||
|
||||
This runs additional style checks. Fix any issues before committing.
|
||||
|
||||
### 5. Generated Code
|
||||
|
||||
```sh
|
||||
go run ./build/ci.go check_generate
|
||||
```
|
||||
|
||||
Ensures that all generated files (e.g., `gen_*.go`) are up to date. If this fails, first install the required code generators by running `make devtools`, then run the appropriate `go generate` commands and include the updated files in your commit.
|
||||
|
||||
### 6. Dependency Hygiene
|
||||
|
||||
```sh
|
||||
go run ./build/ci.go check_baddeps
|
||||
```
|
||||
|
||||
Verifies that no forbidden dependencies have been introduced.
|
||||
|
||||
## What to include in commits
|
||||
|
||||
Do not commit binaries, whether they are produced by the main build or byproducts of investigations.
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Commit messages must be prefixed with the package(s) they modify, followed by a short lowercase description:
|
||||
|
||||
```
|
||||
<package(s)>: description
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `core/vm: fix stack overflow in PUSH instruction`
|
||||
- `eth, rpc: make trace configs optional`
|
||||
- `cmd/geth: add new flag for sync mode`
|
||||
|
||||
Use comma-separated package names when multiple areas are affected. Keep the description concise.
|
||||
|
||||
## Pull Request Title Format
|
||||
|
||||
PR titles follow the same convention as commit messages:
|
||||
|
||||
```
|
||||
<list of modified paths>: description
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `core/vm: fix stack overflow in PUSH instruction`
|
||||
- `core, eth: add arena allocator support`
|
||||
- `cmd/geth, internal/ethapi: refactor transaction args`
|
||||
- `trie/archiver: streaming subtree archival to fix OOM`
|
||||
|
||||
Use the top-level package paths, comma-separated if multiple areas are affected. Only mention the directories with functional changes, interface changes that trickle all over the codebase should not generate an exhaustive list. The description should be a short, lowercase summary of the change.
|
||||
|
|
@ -4,7 +4,7 @@ ARG VERSION=""
|
|||
ARG BUILDNUM=""
|
||||
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.26-alpine AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache gcc musl-dev linux-headers git
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ ARG VERSION=""
|
|||
ARG BUILDNUM=""
|
||||
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.26-alpine AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache gcc musl-dev linux-headers git
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ directory.
|
|||
| 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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `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`). |
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Audit reports are published in the `docs` folder: https://github.com/ethereum/go
|
|||
| Scope | Date | Report Link |
|
||||
| ------- | ------- | ----------- |
|
||||
| `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) |
|
||||
| `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) |
|
||||
| `Discv5` | 20191015 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf) |
|
||||
| `Discv5` | 20200124 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf) |
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@
|
|||
package {{.Package}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
"errors"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
|
|
@ -29,8 +27,6 @@ var (
|
|||
_ = types.BloomLookup
|
||||
_ = event.NewSubscription
|
||||
_ = abi.ConvertType
|
||||
_ = time.Tick
|
||||
_ = context.Background
|
||||
)
|
||||
|
||||
{{$structs := .Structs}}
|
||||
|
|
@ -81,11 +77,7 @@ var (
|
|||
return common.Address{}, nil, nil, errors.New("GetABI returned nil")
|
||||
}
|
||||
{{range $pattern, $name := .Libraries}}
|
||||
{{decapitalise $name}}Addr, tx, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5 * time.Second)
|
||||
if err := bind.WaitAccepted(ctx, backend, tx); err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
||||
{{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:])
|
||||
{{end}}
|
||||
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||
|
|
|
|||
|
|
@ -59,11 +59,6 @@ var (
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *{{.Type}}) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// New{{.Type}} creates a new instance of {{.Type}}.
|
||||
func New{{.Type}}() *{{.Type}} {
|
||||
parsed, err := {{.Type}}MetaData.ParseABI()
|
||||
|
|
@ -188,11 +183,8 @@ var (
|
|||
// Solidity: {{.Original.String}}
|
||||
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||
event := "{{.Original.Name}}"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type CallbackParam struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *CallbackParam) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewCallbackParam creates a new instance of CallbackParam.
|
||||
func NewCallbackParam() *CallbackParam {
|
||||
parsed, err := CallbackParamMetaData.ParseABI()
|
||||
|
|
|
|||
12
accounts/abi/abigen/testdata/v2/crowdsale.go.txt
vendored
|
|
@ -36,11 +36,6 @@ type Crowdsale struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Crowdsale) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewCrowdsale creates a new instance of Crowdsale.
|
||||
func NewCrowdsale() *Crowdsale {
|
||||
parsed, err := CrowdsaleMetaData.ParseABI()
|
||||
|
|
@ -365,11 +360,8 @@ func (CrowdsaleFundTransfer) ContractEventName() string {
|
|||
// Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution)
|
||||
func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) {
|
||||
event := "FundTransfer"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != crowdsale.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != crowdsale.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(CrowdsaleFundTransfer)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
40
accounts/abi/abigen/testdata/v2/dao.go.txt
vendored
|
|
@ -36,11 +36,6 @@ type DAO struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *DAO) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewDAO creates a new instance of DAO.
|
||||
func NewDAO() *DAO {
|
||||
parsed, err := DAOMetaData.ParseABI()
|
||||
|
|
@ -611,11 +606,8 @@ func (DAOChangeOfRules) ContractEventName() string {
|
|||
// Solidity: event ChangeOfRules(uint256 minimumQuorum, uint256 debatingPeriodInMinutes, int256 majorityMargin)
|
||||
func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) {
|
||||
event := "ChangeOfRules"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DAOChangeOfRules)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -656,11 +648,8 @@ func (DAOMembershipChanged) ContractEventName() string {
|
|||
// Solidity: event MembershipChanged(address member, bool isMember)
|
||||
func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) {
|
||||
event := "MembershipChanged"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DAOMembershipChanged)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -703,11 +692,8 @@ func (DAOProposalAdded) ContractEventName() string {
|
|||
// Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description)
|
||||
func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) {
|
||||
event := "ProposalAdded"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DAOProposalAdded)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -750,11 +736,8 @@ func (DAOProposalTallied) ContractEventName() string {
|
|||
// Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active)
|
||||
func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) {
|
||||
event := "ProposalTallied"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DAOProposalTallied)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -797,11 +780,8 @@ func (DAOVoted) ContractEventName() string {
|
|||
// Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification)
|
||||
func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) {
|
||||
event := "Voted"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DAOVoted)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type DeeplyNestedArray struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *DeeplyNestedArray) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewDeeplyNestedArray creates a new instance of DeeplyNestedArray.
|
||||
func NewDeeplyNestedArray() *DeeplyNestedArray {
|
||||
parsed, err := DeeplyNestedArrayMetaData.ParseABI()
|
||||
|
|
|
|||
5
accounts/abi/abigen/testdata/v2/empty.go.txt
vendored
|
|
@ -36,11 +36,6 @@ type Empty struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Empty) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewEmpty creates a new instance of Empty.
|
||||
func NewEmpty() *Empty {
|
||||
parsed, err := EmptyMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ type EventChecker struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *EventChecker) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewEventChecker creates a new instance of EventChecker.
|
||||
func NewEventChecker() *EventChecker {
|
||||
parsed, err := EventCheckerMetaData.ParseABI()
|
||||
|
|
@ -77,11 +72,8 @@ func (EventCheckerDynamic) ContractEventName() string {
|
|||
// Solidity: event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat)
|
||||
func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) {
|
||||
event := "dynamic"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(EventCheckerDynamic)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -120,11 +112,8 @@ func (EventCheckerEmpty) ContractEventName() string {
|
|||
// Solidity: event empty()
|
||||
func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) {
|
||||
event := "empty"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(EventCheckerEmpty)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -165,11 +154,8 @@ func (EventCheckerIndexed) ContractEventName() string {
|
|||
// Solidity: event indexed(address indexed addr, int256 indexed num)
|
||||
func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) {
|
||||
event := "indexed"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(EventCheckerIndexed)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -210,11 +196,8 @@ func (EventCheckerMixed) ContractEventName() string {
|
|||
// Solidity: event mixed(address indexed addr, int256 num)
|
||||
func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) {
|
||||
event := "mixed"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(EventCheckerMixed)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -255,11 +238,8 @@ func (EventCheckerUnnamed) ContractEventName() string {
|
|||
// Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1)
|
||||
func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) {
|
||||
event := "unnamed"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(EventCheckerUnnamed)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type Getter struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Getter) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewGetter creates a new instance of Getter.
|
||||
func NewGetter() *Getter {
|
||||
parsed, err := GetterMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type IdentifierCollision struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *IdentifierCollision) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewIdentifierCollision creates a new instance of IdentifierCollision.
|
||||
func NewIdentifierCollision() *IdentifierCollision {
|
||||
parsed, err := IdentifierCollisionMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ type InputChecker struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *InputChecker) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewInputChecker creates a new instance of InputChecker.
|
||||
func NewInputChecker() *InputChecker {
|
||||
parsed, err := InputCheckerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type Interactor struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Interactor) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewInteractor creates a new instance of Interactor.
|
||||
func NewInteractor() *Interactor {
|
||||
parsed, err := InteractorMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -42,11 +42,6 @@ type NameConflict struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *NameConflict) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewNameConflict creates a new instance of NameConflict.
|
||||
func NewNameConflict() *NameConflict {
|
||||
parsed, err := NameConflictMetaData.ParseABI()
|
||||
|
|
@ -139,11 +134,8 @@ func (NameConflictLog) ContractEventName() string {
|
|||
// Solidity: event log(int256 msg, int256 _msg)
|
||||
func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) {
|
||||
event := "log"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != nameConflict.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != nameConflict.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(NameConflictLog)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type NumericMethodName struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *NumericMethodName) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewNumericMethodName creates a new instance of NumericMethodName.
|
||||
func NewNumericMethodName() *NumericMethodName {
|
||||
parsed, err := NumericMethodNameMetaData.ParseABI()
|
||||
|
|
@ -141,11 +136,8 @@ func (NumericMethodNameE1TestEvent) ContractEventName() string {
|
|||
// Solidity: event _1TestEvent(address _param)
|
||||
func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) {
|
||||
event := "_1TestEvent"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != numericMethodName.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != numericMethodName.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(NumericMethodNameE1TestEvent)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ type OutputChecker struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *OutputChecker) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewOutputChecker creates a new instance of OutputChecker.
|
||||
func NewOutputChecker() *OutputChecker {
|
||||
parsed, err := OutputCheckerMetaData.ParseABI()
|
||||
|
|
|
|||
19
accounts/abi/abigen/testdata/v2/overload.go.txt
vendored
|
|
@ -36,11 +36,6 @@ type Overload struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Overload) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewOverload creates a new instance of Overload.
|
||||
func NewOverload() *Overload {
|
||||
parsed, err := OverloadMetaData.ParseABI()
|
||||
|
|
@ -119,11 +114,8 @@ func (OverloadBar) ContractEventName() string {
|
|||
// Solidity: event bar(uint256 i)
|
||||
func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) {
|
||||
event := "bar"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(OverloadBar)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -164,11 +156,8 @@ func (OverloadBar0) ContractEventName() string {
|
|||
// Solidity: event bar(uint256 i, uint256 j)
|
||||
func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) {
|
||||
event := "bar0"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(OverloadBar0)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type RangeKeyword struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *RangeKeyword) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewRangeKeyword creates a new instance of RangeKeyword.
|
||||
func NewRangeKeyword() *RangeKeyword {
|
||||
parsed, err := RangeKeywordMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type Slicer struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Slicer) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewSlicer creates a new instance of Slicer.
|
||||
func NewSlicer() *Slicer {
|
||||
parsed, err := SlicerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -41,11 +41,6 @@ type Structs struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Structs) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewStructs creates a new instance of Structs.
|
||||
func NewStructs() *Structs {
|
||||
parsed, err := StructsMetaData.ParseABI()
|
||||
|
|
|
|||
12
accounts/abi/abigen/testdata/v2/token.go.txt
vendored
|
|
@ -36,11 +36,6 @@ type Token struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Token) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewToken creates a new instance of Token.
|
||||
func NewToken() *Token {
|
||||
parsed, err := TokenMetaData.ParseABI()
|
||||
|
|
@ -391,11 +386,8 @@ func (TokenTransfer) ContractEventName() string {
|
|||
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
|
||||
func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) {
|
||||
event := "Transfer"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != token.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != token.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(TokenTransfer)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
19
accounts/abi/abigen/testdata/v2/tuple.go.txt
vendored
|
|
@ -61,11 +61,6 @@ type Tuple struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Tuple) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewTuple creates a new instance of Tuple.
|
||||
func NewTuple() *Tuple {
|
||||
parsed, err := TupleMetaData.ParseABI()
|
||||
|
|
@ -198,11 +193,8 @@ func (TupleTupleEvent) ContractEventName() string {
|
|||
// Solidity: event TupleEvent((uint256,uint256[],(uint256,uint256)[]) a, (uint256,uint256)[2][] b, (uint256,uint256)[][2] c, (uint256,uint256[],(uint256,uint256)[])[] d, uint256[] e)
|
||||
func (tuple *Tuple) UnpackTupleEventEvent(log *types.Log) (*TupleTupleEvent, error) {
|
||||
event := "TupleEvent"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(TupleTupleEvent)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -242,11 +234,8 @@ func (TupleTupleEvent2) ContractEventName() string {
|
|||
// Solidity: event TupleEvent2((uint8,uint8)[] arg0)
|
||||
func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) {
|
||||
event := "TupleEvent2"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(TupleTupleEvent2)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type Tupler struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Tupler) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewTupler creates a new instance of Tupler.
|
||||
func NewTupler() *Tupler {
|
||||
parsed, err := TuplerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type Underscorer struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Underscorer) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewUnderscorer creates a new instance of Underscorer.
|
||||
func NewUnderscorer() *Underscorer {
|
||||
parsed, err := UnderscorerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]any, data []byte) error {
|
|||
// Copy performs the operation go format -> provided struct.
|
||||
func (arguments Arguments) Copy(v any, values []any) error {
|
||||
// make sure the passed value is arguments pointer
|
||||
if reflect.Pointer != reflect.ValueOf(v).Kind() {
|
||||
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
if len(values) == 0 {
|
||||
|
|
@ -165,7 +165,7 @@ func (arguments Arguments) copyTuple(v any, marshalledValues []any) error {
|
|||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if value.Len() < len(marshalledValues) {
|
||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(marshalledValues), value.Len())
|
||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||
}
|
||||
for i := range nonIndexedArgs {
|
||||
if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil {
|
||||
|
|
|
|||
|
|
@ -176,13 +176,6 @@ var (
|
|||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||
// an empty contract behind.
|
||||
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy
|
||||
|
||||
// ErrNoEventSignature is returned when a log entry has no topics.
|
||||
ErrNoEventSignature = bind2.ErrNoEventSignature
|
||||
|
||||
// ErrEventSignatureMismatch is returned when a log's topic[0] does not match
|
||||
// the expected event signature.
|
||||
ErrEventSignatureMismatch = bind2.ErrEventSignatureMismatch
|
||||
)
|
||||
|
||||
// ContractCaller defines the methods needed to allow operating with a contract on a read
|
||||
|
|
@ -273,12 +266,6 @@ func (m *MetaData) GetAbi() (*abi.ABI, error) {
|
|||
|
||||
// util.go
|
||||
|
||||
// WaitAccepted waits for a tx to be accepted into the pool.
|
||||
// It stops waiting when the context is canceled.
|
||||
func WaitAccepted(ctx context.Context, b ContractBackend, tx *types.Transaction) error {
|
||||
return bind2.WaitAccepted(ctx, b, tx.Hash())
|
||||
}
|
||||
|
||||
// WaitMined waits for tx to be mined on the blockchain.
|
||||
// It stops waiting when the context is canceled.
|
||||
func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) {
|
||||
|
|
|
|||
|
|
@ -103,9 +103,6 @@ type ContractTransactor interface {
|
|||
|
||||
// PendingNonceAt retrieves the current pending nonce associated with an account.
|
||||
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
||||
|
||||
// TransactionByHash retrieves the transaction associated with the hash, if it exists in the pool.
|
||||
TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
|
||||
}
|
||||
|
||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ import (
|
|||
const basefeeWiggleMultiplier = 2
|
||||
|
||||
var (
|
||||
ErrNoEventSignature = errors.New("no event signature")
|
||||
ErrEventSignatureMismatch = errors.New("event signature mismatch")
|
||||
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
|
||||
|
|
@ -320,7 +320,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add
|
|||
}
|
||||
}
|
||||
// create the transaction
|
||||
nonce, err := c.GetNonce(opts)
|
||||
nonce, err := c.getNonce(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -365,7 +365,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr
|
|||
}
|
||||
}
|
||||
// create the transaction
|
||||
nonce, err := c.GetNonce(opts)
|
||||
nonce, err := c.getNonce(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -402,7 +402,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad
|
|||
return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
||||
}
|
||||
|
||||
func (c *BoundContract) GetNonce(opts *TransactOpts) (uint64, error) {
|
||||
func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) {
|
||||
if opts.Nonce == nil {
|
||||
return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
|
||||
} else {
|
||||
|
|
@ -536,10 +536,10 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]any)
|
|||
func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
|
||||
// Anonymous events are not supported.
|
||||
if len(log.Topics) == 0 {
|
||||
return ErrNoEventSignature
|
||||
return errNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return ErrEventSignatureMismatch
|
||||
return errEventSignatureMismatch
|
||||
}
|
||||
if len(log.Data) > 0 {
|
||||
if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
|
||||
|
|
@ -559,10 +559,10 @@ func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
|
|||
func (c *BoundContract) UnpackLogIntoMap(out map[string]any, event string, log types.Log) error {
|
||||
// Anonymous events are not supported.
|
||||
if len(log.Topics) == 0 {
|
||||
return ErrNoEventSignature
|
||||
return errNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return ErrEventSignatureMismatch
|
||||
return errEventSignatureMismatch
|
||||
}
|
||||
if len(log.Data) > 0 {
|
||||
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {
|
||||
|
|
|
|||
|
|
@ -75,10 +75,6 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac
|
|||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTransactor) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
type mockCaller struct {
|
||||
codeAtBlockNumber *big.Int
|
||||
callContractBlockNumber *big.Int
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ func TestContractLinking(t *testing.T) {
|
|||
map[rune]struct{}{},
|
||||
},
|
||||
// two contracts ('a' and 'f') share some dependencies. contract 'a' is marked as an override. expect that any of
|
||||
// its dependencies that aren't shared with 'f' are not deployed.
|
||||
// its depdencies that aren't shared with 'f' are not deployed.
|
||||
linkTestCaseInput{map[rune][]rune{
|
||||
'a': {'b', 'c', 'd', 'e'},
|
||||
'f': {'g', 'c', 'd', 'h'}},
|
||||
|
|
|
|||
|
|
@ -43,11 +43,6 @@ type DB struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *DB) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewDB creates a new instance of DB.
|
||||
func NewDB() *DB {
|
||||
parsed, err := DBMetaData.ParseABI()
|
||||
|
|
@ -281,11 +276,8 @@ func (DBInsert) ContractEventName() string {
|
|||
// Solidity: event Insert(uint256 key, uint256 value, uint256 length)
|
||||
func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) {
|
||||
event := "Insert"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DBInsert)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -326,11 +318,8 @@ func (DBKeyedInsert) ContractEventName() string {
|
|||
// Solidity: event KeyedInsert(uint256 indexed key, uint256 value)
|
||||
func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) {
|
||||
event := "KeyedInsert"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(DBKeyedInsert)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type C struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC creates a new instance of C.
|
||||
func NewC() *C {
|
||||
parsed, err := CMetaData.ParseABI()
|
||||
|
|
@ -120,11 +115,8 @@ func (CBasic1) ContractEventName() string {
|
|||
// Solidity: event basic1(uint256 indexed id, uint256 data)
|
||||
func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
|
||||
event := "basic1"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(CBasic1)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -165,11 +157,8 @@ func (CBasic2) ContractEventName() string {
|
|||
// Solidity: event basic2(bool indexed flag, uint256 data)
|
||||
func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
|
||||
event := "basic2"
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new(CBasic2)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -40,11 +40,6 @@ type C1 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C1) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC1 creates a new instance of C1.
|
||||
func NewC1() *C1 {
|
||||
parsed, err := C1MetaData.ParseABI()
|
||||
|
|
@ -123,11 +118,6 @@ type C2 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C2) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC2 creates a new instance of C2.
|
||||
func NewC2() *C2 {
|
||||
parsed, err := C2MetaData.ParseABI()
|
||||
|
|
@ -202,11 +192,6 @@ type L1 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L1) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL1 creates a new instance of L1.
|
||||
func NewL1() *L1 {
|
||||
parsed, err := L1MetaData.ParseABI()
|
||||
|
|
@ -272,11 +257,6 @@ type L2 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L2) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL2 creates a new instance of L2.
|
||||
func NewL2() *L2 {
|
||||
parsed, err := L2MetaData.ParseABI()
|
||||
|
|
@ -342,11 +322,6 @@ type L2b struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L2b) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL2b creates a new instance of L2b.
|
||||
func NewL2b() *L2b {
|
||||
parsed, err := L2bMetaData.ParseABI()
|
||||
|
|
@ -409,11 +384,6 @@ type L3 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L3) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL3 creates a new instance of L3.
|
||||
func NewL3() *L3 {
|
||||
parsed, err := L3MetaData.ParseABI()
|
||||
|
|
@ -480,11 +450,6 @@ type L4 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L4) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL4 creates a new instance of L4.
|
||||
func NewL4() *L4 {
|
||||
parsed, err := L4MetaData.ParseABI()
|
||||
|
|
@ -550,11 +515,6 @@ type L4b struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L4b) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL4b creates a new instance of L4b.
|
||||
func NewL4b() *L4b {
|
||||
parsed, err := L4bMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type C struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC creates a new instance of C.
|
||||
func NewC() *C {
|
||||
parsed, err := CMetaData.ParseABI()
|
||||
|
|
@ -178,11 +173,6 @@ type C2 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C2) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC2 creates a new instance of C2.
|
||||
func NewC2() *C2 {
|
||||
parsed, err := C2MetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ type MyContract struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *MyContract) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewMyContract creates a new instance of MyContract.
|
||||
func NewMyContract() *MyContract {
|
||||
parsed, err := MyContractMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ done:
|
|||
t.Fatalf("expected e1Count of 2 from filter call. got %d", e1Count)
|
||||
}
|
||||
if e2Count != 1 {
|
||||
t.Fatalf("expected e2Count of 1 from filter call. got %d", e2Count)
|
||||
t.Fatalf("expected e2Count of 1 from filter call. got %d", e1Count)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -379,16 +379,16 @@ func TestEventUnpackEmptyTopics(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
||||
}
|
||||
if err != bind.ErrNoEventSignature {
|
||||
t.Fatalf("expected 'no event signature' error, got: %v", err)
|
||||
if err.Error() != "event signature mismatch" {
|
||||
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
|
||||
}
|
||||
|
||||
_, err = c.UnpackBasic2Event(log)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
||||
}
|
||||
if err != bind.ErrNoEventSignature {
|
||||
t.Fatalf("expected 'no event signature' error, got: %v", err)
|
||||
if err.Error() != "event signature mismatch" {
|
||||
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,28 +75,3 @@ func WaitDeployed(ctx context.Context, b DeployBackend, hash common.Hash) (commo
|
|||
}
|
||||
return receipt.ContractAddress, err
|
||||
}
|
||||
|
||||
func WaitAccepted(ctx context.Context, d ContractBackend, txHash common.Hash) error {
|
||||
queryTicker := time.NewTicker(time.Second)
|
||||
defer queryTicker.Stop()
|
||||
logger := log.New("hash", txHash)
|
||||
for {
|
||||
_, _, err := d.TransactionByHash(ctx, txHash)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if errors.Is(err, ethereum.NotFound) { // TODO: check this is emitted
|
||||
logger.Trace("Transaction not yet accepted")
|
||||
} else {
|
||||
logger.Trace("Transaction submission failed", "err", err)
|
||||
}
|
||||
|
||||
// Wait for the next round.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-queryTicker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
|||
switch t.T {
|
||||
case UintTy:
|
||||
// make sure to not pack a negative value into a uint type.
|
||||
if reflectValue.Kind() == reflect.Pointer {
|
||||
if reflectValue.Kind() == reflect.Ptr {
|
||||
val := new(big.Int).Set(reflectValue.Interface().(*big.Int))
|
||||
if val.Sign() == -1 {
|
||||
return nil, errInvalidSign
|
||||
|
|
@ -86,7 +86,7 @@ func packNum(value reflect.Value) []byte {
|
|||
return math.U256Bytes(new(big.Int).SetUint64(value.Uint()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return math.U256Bytes(big.NewInt(value.Int()))
|
||||
case reflect.Pointer:
|
||||
case reflect.Ptr:
|
||||
return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int)))
|
||||
default:
|
||||
panic("abi: fatal error")
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ func ConvertType(in interface{}, proto interface{}) interface{} {
|
|||
// indirect recursively dereferences the value until it either gets the value
|
||||
// or finds a big.Int
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Pointer && v.Elem().Type() != reflect.TypeFor[big.Int]() {
|
||||
if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeFor[big.Int]() {
|
||||
return indirect(v.Elem())
|
||||
}
|
||||
return v
|
||||
|
|
@ -102,9 +102,9 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
|||
func set(dst, src reflect.Value) error {
|
||||
dstType, srcType := dst.Type(), src.Type()
|
||||
switch {
|
||||
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Pointer || dst.Elem().CanSet()):
|
||||
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()):
|
||||
return set(dst.Elem(), src)
|
||||
case dstType.Kind() == reflect.Pointer && dstType.Elem() != reflect.TypeFor[big.Int]():
|
||||
case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeFor[big.Int]():
|
||||
return set(dst.Elem(), src)
|
||||
case srcType.AssignableTo(dstType) && dst.CanSet():
|
||||
dst.Set(src)
|
||||
|
|
@ -138,7 +138,7 @@ func setSlice(dst, src reflect.Value) error {
|
|||
}
|
||||
|
||||
func setArray(dst, src reflect.Value) error {
|
||||
if src.Kind() == reflect.Pointer {
|
||||
if src.Kind() == reflect.Ptr {
|
||||
return set(dst, indirect(src))
|
||||
}
|
||||
array := reflect.New(dst.Type()).Elem()
|
||||
|
|
|
|||
|
|
@ -75,11 +75,8 @@ func parseElementaryType(unescapedSelector string) (string, string, error) {
|
|||
parsedType = parsedType + string(rest[0])
|
||||
rest = rest[1:]
|
||||
}
|
||||
if len(rest) == 0 {
|
||||
return "", "", fmt.Errorf("failed to parse array: expected ']', got end of string")
|
||||
}
|
||||
if rest[0] != ']' {
|
||||
return "", "", fmt.Errorf("failed to parse array: expected ']', got %c", rest[0])
|
||||
if len(rest) == 0 || rest[0] != ']' {
|
||||
return "", "", fmt.Errorf("failed to parse array: expected ']', got %c", unescapedSelector[0])
|
||||
}
|
||||
parsedType = parsedType + string(rest[0])
|
||||
rest = rest[1:]
|
||||
|
|
|
|||
|
|
@ -222,7 +222,6 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
|||
if strings.HasPrefix(internalType, "contract ") {
|
||||
typ.Size = 20
|
||||
typ.T = AddressTy
|
||||
typ.stringKind = "address"
|
||||
} else {
|
||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ 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)
|
||||
}
|
||||
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)", 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)
|
||||
}
|
||||
|
||||
// this value will become our slice or our array, depending on the type
|
||||
|
|
|
|||
|
|
@ -910,7 +910,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||
},
|
||||
},
|
||||
FieldT: T{
|
||||
big.NewInt(0).SetBits([]big.Word{}), big.NewInt(1),
|
||||
big.NewInt(0), big.NewInt(1),
|
||||
},
|
||||
A: big.NewInt(1),
|
||||
}
|
||||
|
|
@ -919,7 +919,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
if reflect.DeepEqual(ret, expected) {
|
||||
t.Error("unexpected unpack value")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
accounts/external/backend.go
vendored
|
|
@ -237,7 +237,6 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||
}
|
||||
if tx.Type() == types.BlobTxType {
|
||||
args.BlobHashes = tx.BlobHashes()
|
||||
args.BlobFeeCap = (*hexutil.Big)(tx.BlobGasFeeCap())
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
return nil, errors.New("blobs must be present for signing")
|
||||
|
|
|
|||
|
|
@ -68,27 +68,18 @@ func waitWatcherStart(ks *KeyStore) bool {
|
|||
|
||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||
var list []accounts.Account
|
||||
haveAccounts := false
|
||||
haveChange := false
|
||||
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||
if !haveAccounts {
|
||||
list = ks.Accounts()
|
||||
haveAccounts = reflect.DeepEqual(list, wantAccounts)
|
||||
}
|
||||
if !haveChange {
|
||||
list = ks.Accounts()
|
||||
if reflect.DeepEqual(list, wantAccounts) {
|
||||
// ks should have also received change notifications
|
||||
select {
|
||||
case <-ks.changes:
|
||||
haveChange = true
|
||||
default:
|
||||
return errors.New("wasn't notified of new accounts")
|
||||
}
|
||||
}
|
||||
if haveAccounts && haveChange {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if haveAccounts {
|
||||
return errors.New("wasn't notified of new accounts")
|
||||
}
|
||||
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ func toISO8601(t time.Time) string {
|
|||
if name == "UTC" {
|
||||
tz = "Z"
|
||||
} else {
|
||||
tz = fmt.Sprintf("%+03d00", offset/3600)
|
||||
tz = fmt.Sprintf("%03d00", offset/3600)
|
||||
}
|
||||
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
|
||||
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
// 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/>.
|
||||
|
||||
//go:build (darwin && !ios && cgo) || freebsd || linux || netbsd || solaris
|
||||
// +build darwin,!ios,cgo freebsd linux netbsd solaris
|
||||
//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
|
||||
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
|
||||
|
||||
package keystore
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
// 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/>.
|
||||
|
||||
//go:build (darwin && !cgo) || ios || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
|
||||
// +build darwin,!cgo ios windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
|
||||
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
|
||||
// This is the fallback implementation of directory watching.
|
||||
// It is used on unsupported platforms.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package accounts
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
|
|
@ -255,12 +254,13 @@ func merge(slice []Wallet, wallets ...Wallet) []Wallet {
|
|||
// drop is the counterpart of merge, which looks up wallets from within the sorted
|
||||
// cache and removes the ones specified.
|
||||
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||
remove := make(map[URL]struct{}, len(wallets))
|
||||
for _, w := range wallets {
|
||||
remove[w.URL()] = struct{}{}
|
||||
for _, wallet := range wallets {
|
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
|
||||
if n == len(slice) {
|
||||
// Wallet not found, may happen during startup
|
||||
continue
|
||||
}
|
||||
slice = append(slice[:n], slice[n+1:]...)
|
||||
}
|
||||
return slices.DeleteFunc(slice, func(w Wallet) bool {
|
||||
_, ok := remove[w.URL()]
|
||||
return ok
|
||||
})
|
||||
return slice
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func (hub *Hub) readPairings() error {
|
|||
}
|
||||
|
||||
func (hub *Hub) writePairings() error {
|
||||
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -129,8 +129,11 @@ func (hub *Hub) writePairings() error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = pairingFile.Write(pairingData)
|
||||
return err
|
||||
if _, err := pairingFile.Write(pairingData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/hid"
|
||||
"github.com/karalabe/hid"
|
||||
)
|
||||
|
||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
|
|
|
|||
|
|
@ -110,16 +110,6 @@ func (w *ledgerDriver) offline() bool {
|
|||
return w.version == [3]byte{0, 0, 0}
|
||||
}
|
||||
|
||||
func ledgerVersionLessThan(version [3]byte, major, minor, patch byte) bool {
|
||||
if version[0] != major {
|
||||
return version[0] < major
|
||||
}
|
||||
if version[1] != minor {
|
||||
return version[1] < minor
|
||||
}
|
||||
return version[2] < patch
|
||||
}
|
||||
|
||||
// Open implements usbwallet.driver, attempting to initialize the connection to the
|
||||
// Ledger hardware wallet. The Ledger does not require a user passphrase, so that
|
||||
// parameter is silently discarded.
|
||||
|
|
@ -176,19 +166,7 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
|
|||
return common.Address{}, nil, accounts.ErrWalletClosed
|
||||
}
|
||||
// Ensure the wallet is capable of signing the given transaction
|
||||
switch tx.Type() {
|
||||
case types.AccessListTxType, types.DynamicFeeTxType:
|
||||
if ledgerVersionLessThan(w.version, 1, 9, 0) {
|
||||
//lint:ignore ST1005 brand name displayed on the console
|
||||
return common.Address{}, nil, fmt.Errorf("Ledger version >= 1.9.0 required for EIP-2930/EIP-1559 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
|
||||
}
|
||||
case types.SetCodeTxType:
|
||||
if ledgerVersionLessThan(w.version, 1, 17, 0) {
|
||||
//lint:ignore ST1005 brand name displayed on the console
|
||||
return common.Address{}, nil, fmt.Errorf("Ledger version >= 1.17.0 required for EIP-7702 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
|
||||
}
|
||||
}
|
||||
if chainID != nil && ledgerVersionLessThan(w.version, 1, 0, 3) {
|
||||
if chainID != nil && (w.version[0] < 1 || (w.version[0] == 1 && w.version[1] == 0 && w.version[2] < 3)) {
|
||||
//lint:ignore ST1005 brand name displayed on the console
|
||||
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
||||
}
|
||||
|
|
@ -206,7 +184,7 @@ func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash
|
|||
return nil, accounts.ErrWalletClosed
|
||||
}
|
||||
// Ensure the wallet is capable of signing the given transaction
|
||||
if ledgerVersionLessThan(w.version, 1, 5, 0) {
|
||||
if w.version[0] < 1 || (w.version[0] == 1 && w.version[1] < 5) {
|
||||
//lint:ignore ST1005 brand name displayed on the console
|
||||
return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
|
||||
}
|
||||
|
|
@ -356,41 +334,26 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
|||
err error
|
||||
)
|
||||
if chainID == nil {
|
||||
if txrlp, err = rlp.EncodeToBytes([]any{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil {
|
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
} else {
|
||||
switch tx.Type() {
|
||||
case types.SetCodeTxType:
|
||||
if txrlp, err = rlp.EncodeToBytes([]any{chainID, tx.Nonce(), tx.GasTipCap(), tx.GasFeeCap(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations()}); err != nil {
|
||||
if tx.Type() == types.DynamicFeeTxType {
|
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{chainID, tx.Nonce(), tx.GasTipCap(), tx.GasFeeCap(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList()}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
// append type to transaction
|
||||
txrlp = append([]byte{tx.Type()}, txrlp...)
|
||||
case types.BlobTxType:
|
||||
if txrlp, err = rlp.EncodeToBytes([]any{chainID, tx.Nonce(), tx.GasTipCap(), tx.GasFeeCap(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList(), tx.BlobGasFeeCap(), tx.BlobHashes()}); err != nil {
|
||||
} else if tx.Type() == types.AccessListTxType {
|
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{chainID, tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList()}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
// append type to transaction
|
||||
txrlp = append([]byte{tx.Type()}, txrlp...)
|
||||
case types.DynamicFeeTxType:
|
||||
if txrlp, err = rlp.EncodeToBytes([]any{chainID, tx.Nonce(), tx.GasTipCap(), tx.GasFeeCap(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList()}); err != nil {
|
||||
} else if tx.Type() == types.LegacyTxType {
|
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
// append type to transaction
|
||||
txrlp = append([]byte{tx.Type()}, txrlp...)
|
||||
case types.AccessListTxType:
|
||||
if txrlp, err = rlp.EncodeToBytes([]any{chainID, tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.AccessList()}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
// append type to transaction
|
||||
txrlp = append([]byte{tx.Type()}, txrlp...)
|
||||
case types.LegacyTxType:
|
||||
if txrlp, err = rlp.EncodeToBytes([]any{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
default:
|
||||
return common.Address{}, nil, fmt.Errorf("unsupported transaction type: %d", tx.Type())
|
||||
}
|
||||
}
|
||||
payload := append(path, txrlp...)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/hid"
|
||||
"github.com/karalabe/hid"
|
||||
)
|
||||
|
||||
// Maximum time between wallet health checks to detect USB unplugs.
|
||||
|
|
|
|||
39
appveyor.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
clone_depth: 5
|
||||
version: "{branch}.{build}"
|
||||
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- GETH_ARCH: amd64
|
||||
GETH_MINGW: 'C:\msys64\mingw64'
|
||||
- GETH_ARCH: 386
|
||||
GETH_MINGW: 'C:\msys64\mingw32'
|
||||
|
||||
install:
|
||||
- git submodule update --init --depth 1 --recursive
|
||||
- go version
|
||||
|
||||
for:
|
||||
# Windows builds for amd64 + 386.
|
||||
- matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
environment:
|
||||
# We use gcc from MSYS2 because it is the most recent compiler version available on
|
||||
# AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is
|
||||
# contained in PATH.
|
||||
GETH_CC: '%GETH_MINGW%\bin\gcc.exe'
|
||||
PATH: '%GETH_MINGW%\bin;C:\Program Files (x86)\NSIS\;%PATH%'
|
||||
build_script:
|
||||
- 'echo %GETH_ARCH%'
|
||||
- 'echo %GETH_CC%'
|
||||
- '%GETH_CC% --version'
|
||||
- go run build/ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC%
|
||||
after_build:
|
||||
# Upload builds. Note that ci.go makes this a no-op PR 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
|
||||
test_script:
|
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short
|
||||
|
|
@ -87,10 +87,6 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
|
|||
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
|
||||
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
|
||||
} else {
|
||||
if err.Error() == "beacon syncer reorging" {
|
||||
log.Debug("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err)
|
||||
continue // ignore beacon syncer reorging errors, this error can occur if the blsync is skipping a block
|
||||
}
|
||||
log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright 2026 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 (
|
||||
"github.com/fjl/jsonw"
|
||||
)
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (list BlobAndProofListV1) MarshalJSON() ([]byte, error) {
|
||||
if list == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
var b jsonw.Buffer
|
||||
b.Array(func() {
|
||||
for _, item := range list {
|
||||
marshalBlobAndProofV1(&b, item)
|
||||
}
|
||||
})
|
||||
return b.Output(), nil
|
||||
}
|
||||
|
||||
func marshalBlobAndProofV1(b *jsonw.Buffer, item *BlobAndProofV1) {
|
||||
if item == nil {
|
||||
b.Null()
|
||||
} else {
|
||||
b.Object(func() {
|
||||
b.Key("blob")
|
||||
b.HexBytes(item.Blob)
|
||||
b.Key("proof")
|
||||
b.HexBytes(item.Proof)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (list BlobAndProofListV2) MarshalJSON() ([]byte, error) {
|
||||
if list == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
var b jsonw.Buffer
|
||||
b.Array(func() {
|
||||
for _, item := range list {
|
||||
marshalBlobAndProofV2(&b, item)
|
||||
}
|
||||
})
|
||||
return b.Output(), nil
|
||||
}
|
||||
|
||||
func marshalBlobAndProofV2(b *jsonw.Buffer, item *BlobAndProofV2) {
|
||||
if item == nil {
|
||||
b.Null()
|
||||
} else {
|
||||
b.Object(func() {
|
||||
b.Key("blob")
|
||||
b.HexBytes(item.Blob)
|
||||
b.Key("proofs")
|
||||
appendHexBytesArray(b, item.CellProofs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
// Copyright 2026 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/fjl/jsonw"
|
||||
)
|
||||
|
||||
// marshalBlobsBundle writes BlobsBundle as JSON and appends it to buf.
|
||||
func marshalBlobsBundle(b *jsonw.Buffer, bundle *BlobsBundle) {
|
||||
if bundle == nil {
|
||||
b.Null()
|
||||
return
|
||||
}
|
||||
b.Object(func() {
|
||||
b.Key("commitments")
|
||||
appendHexBytesArray(b, bundle.Commitments)
|
||||
b.Key("proofs")
|
||||
appendHexBytesArray(b, bundle.Proofs)
|
||||
b.Key("blobs")
|
||||
appendHexBytesArray(b, bundle.Blobs)
|
||||
})
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
|
||||
if e.ExecutionPayload == nil {
|
||||
return nil, errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope")
|
||||
}
|
||||
|
||||
// Pre-marshal the execution payload using its gencodec MarshalJSON.
|
||||
payload, err := e.ExecutionPayload.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pre-marshal the witness.
|
||||
var witness []byte
|
||||
if e.Witness != nil {
|
||||
witness, err = json.Marshal(e.Witness)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Write the execution payload to the buffer
|
||||
var b jsonw.Buffer
|
||||
b.Object(func() {
|
||||
b.Key("executionPayload")
|
||||
b.RawValue(payload)
|
||||
|
||||
b.Key("blockValue")
|
||||
if e.BlockValue != nil {
|
||||
b.HexBigInt(e.BlockValue)
|
||||
} else {
|
||||
b.Null()
|
||||
}
|
||||
|
||||
b.Key("blobsBundle")
|
||||
marshalBlobsBundle(&b, e.BlobsBundle)
|
||||
|
||||
b.Key("executionRequests")
|
||||
if e.Requests == nil {
|
||||
b.Null()
|
||||
} else {
|
||||
appendHexBytesArray(&b, e.Requests)
|
||||
}
|
||||
|
||||
b.Key("shouldOverrideBuilder")
|
||||
b.Bool(e.Override)
|
||||
|
||||
if e.Witness != nil {
|
||||
b.Key("witness")
|
||||
b.RawValue(witness)
|
||||
}
|
||||
})
|
||||
return b.Output(), nil
|
||||
}
|
||||
|
||||
func appendHexBytesArray[T ~[]byte](b *jsonw.Buffer, slice []T) {
|
||||
b.Array(func() {
|
||||
for _, elem := range slice {
|
||||
b.HexBytes(elem)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
// Copyright 2026 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
func makeTestPayload() *ExecutableData {
|
||||
return &ExecutableData{
|
||||
ParentHash: common.HexToHash("0x01"),
|
||||
FeeRecipient: common.HexToAddress("0x02"),
|
||||
StateRoot: common.HexToHash("0x03"),
|
||||
ReceiptsRoot: common.HexToHash("0x04"),
|
||||
LogsBloom: make([]byte, 256),
|
||||
Random: common.HexToHash("0x05"),
|
||||
Number: 100,
|
||||
GasLimit: 1000000,
|
||||
GasUsed: 500000,
|
||||
Timestamp: 1234567890,
|
||||
ExtraData: []byte("extra"),
|
||||
BaseFeePerGas: big.NewInt(7),
|
||||
BlockHash: common.HexToHash("0x08"),
|
||||
Transactions: [][]byte{{0xaa, 0xbb}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalJSONRoundtrip(t *testing.T) {
|
||||
witness := hexutil.Bytes{0xde, 0xad}
|
||||
original := ExecutionPayloadEnvelope{
|
||||
ExecutionPayload: makeTestPayload(),
|
||||
BlockValue: big.NewInt(12345),
|
||||
BlobsBundle: &BlobsBundle{
|
||||
Commitments: []hexutil.Bytes{{0x01, 0x02}},
|
||||
Proofs: []hexutil.Bytes{{0x03, 0x04}},
|
||||
Blobs: []hexutil.Bytes{{0x05, 0x06}},
|
||||
},
|
||||
Requests: [][]byte{{0xaa}, {0xbb, 0xcc}},
|
||||
Override: true,
|
||||
Witness: &witness,
|
||||
}
|
||||
|
||||
data, err := original.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalJSON error: %v", err)
|
||||
}
|
||||
|
||||
var decoded ExecutionPayloadEnvelope
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("UnmarshalJSON error: %v", err)
|
||||
}
|
||||
|
||||
if decoded.ExecutionPayload.Number != original.ExecutionPayload.Number {
|
||||
t.Error("ExecutionPayload.Number mismatch")
|
||||
}
|
||||
if decoded.BlockValue.Cmp(original.BlockValue) != 0 {
|
||||
t.Errorf("BlockValue mismatch: got %v, want %v", decoded.BlockValue, original.BlockValue)
|
||||
}
|
||||
if len(decoded.BlobsBundle.Blobs) != len(original.BlobsBundle.Blobs) {
|
||||
t.Error("BlobsBundle.Blobs length mismatch")
|
||||
}
|
||||
if len(decoded.Requests) != len(original.Requests) {
|
||||
t.Error("Requests length mismatch")
|
||||
}
|
||||
if decoded.Override != original.Override {
|
||||
t.Error("Override mismatch")
|
||||
}
|
||||
if !bytes.Equal(*decoded.Witness, *original.Witness) {
|
||||
t.Error("Witness mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalJSONNilPayload(t *testing.T) {
|
||||
env := ExecutionPayloadEnvelope{
|
||||
ExecutionPayload: nil,
|
||||
BlockValue: big.NewInt(1),
|
||||
}
|
||||
_, err := env.MarshalJSON()
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nil ExecutionPayload")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExecutionPayloadEnvelopeFieldCoverage guards against structural drift.
|
||||
// If a field is added to or removed from ExecutionPayloadEnvelope, this test
|
||||
// fails, reminding the developer to update MarshalJSON in marshal_epe.go.
|
||||
func TestExecutionPayloadEnvelopeFieldCoverage(t *testing.T) {
|
||||
expected := []string{
|
||||
"ExecutionPayload",
|
||||
"BlockValue",
|
||||
"BlobsBundle",
|
||||
"Requests",
|
||||
"Override",
|
||||
"Witness",
|
||||
}
|
||||
typ := reflect.TypeOf(ExecutionPayloadEnvelope{})
|
||||
if typ.NumField() != len(expected) {
|
||||
t.Fatalf("ExecutionPayloadEnvelope has %d fields, expected %d — update MarshalJSON in marshal_epe.go",
|
||||
typ.NumField(), len(expected))
|
||||
}
|
||||
for i, name := range expected {
|
||||
if typ.Field(i).Name != name {
|
||||
t.Errorf("field %d: got %q, want %q — update MarshalJSON in marshal_epe.go",
|
||||
i, typ.Field(i).Name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,6 @@ var (
|
|||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
|
||||
|
||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
|||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
|
||||
TargetGasLimit *hexutil.Uint64 `json:"targetGasLimit"`
|
||||
}
|
||||
var enc PayloadAttributes
|
||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||
|
|
@ -30,8 +28,6 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
|||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||
enc.Withdrawals = p.Withdrawals
|
||||
enc.BeaconRoot = p.BeaconRoot
|
||||
enc.SlotNumber = (*hexutil.Uint64)(p.SlotNumber)
|
||||
enc.TargetGasLimit = (*hexutil.Uint64)(p.TargetGasLimit)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +39,6 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
|
||||
TargetGasLimit *hexutil.Uint64 `json:"targetGasLimit"`
|
||||
}
|
||||
var dec PayloadAttributes
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -68,11 +62,5 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||
if dec.BeaconRoot != nil {
|
||||
p.BeaconRoot = dec.BeaconRoot
|
||||
}
|
||||
if dec.SlotNumber != nil {
|
||||
p.SlotNumber = (*uint64)(dec.SlotNumber)
|
||||
}
|
||||
if dec.TargetGasLimit != nil {
|
||||
p.TargetGasLimit = (*uint64)(dec.TargetGasLimit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -17,25 +17,23 @@ 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"`
|
||||
BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
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
|
||||
|
|
@ -60,33 +58,29 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
enc.Withdrawals = e.Withdrawals
|
||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||
enc.BlockAccessList = e.BlockAccessList
|
||||
enc.SlotNumber = (*hexutil.Uint64)(e.SlotNumber)
|
||||
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"`
|
||||
BlockAccessList *hexutil.Bytes `json:"blockAccessList,omitempty"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
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 {
|
||||
|
|
@ -160,11 +154,5 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
if dec.ExcessBlobGas != nil {
|
||||
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.BlockAccessList != nil {
|
||||
e.BlockAccessList = *dec.BlockAccessList
|
||||
}
|
||||
if dec.SlotNumber != nil {
|
||||
e.SlotNumber = (*uint64)(dec.SlotNumber)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -12,6 +12,31 @@ import (
|
|||
|
||||
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 *BlobsBundle `json:"blobsBundle"`
|
||||
Requests []hexutil.Bytes `json:"executionRequests"`
|
||||
Override bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness,omitempty"`
|
||||
}
|
||||
var enc ExecutionPayloadEnvelope
|
||||
enc.ExecutionPayload = e.ExecutionPayload
|
||||
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
||||
enc.BlobsBundle = e.BlobsBundle
|
||||
if e.Requests != nil {
|
||||
enc.Requests = make([]hexutil.Bytes, len(e.Requests))
|
||||
for k, v := range e.Requests {
|
||||
enc.Requests[k] = v
|
||||
}
|
||||
}
|
||||
enc.Override = e.Override
|
||||
enc.Witness = e.Witness
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
||||
type ExecutionPayloadEnvelope struct {
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
|
|
@ -25,10 +24,7 @@ import (
|
|||
"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/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
|
|
@ -54,16 +50,9 @@ var (
|
|||
// ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new
|
||||
// fields: blobGasUsed and excessBlobGas.
|
||||
PayloadV3 PayloadVersion = 0x3
|
||||
|
||||
// PayloadV4 is the identifier of ExecutionPayloadV4 introduced in amsterdam fork.
|
||||
//
|
||||
// https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#executionpayloadv4
|
||||
// ExecutionPayloadV4 has the syntax of ExecutionPayloadV3 and appends the new
|
||||
// fields slotNumber and blockAccessList.
|
||||
PayloadV4 PayloadVersion = 0x4
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out pa_codec.go
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||
|
||||
// PayloadAttributes describes the environment context in which a block should
|
||||
// be built.
|
||||
|
|
@ -73,56 +62,48 @@ type PayloadAttributes struct {
|
|||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||
SlotNumber *uint64 `json:"slotNumber"`
|
||||
TargetGasLimit *uint64 `json:"targetGasLimit"`
|
||||
}
|
||||
|
||||
// JSON type overrides for PayloadAttributes.
|
||||
type payloadAttributesMarshaling struct {
|
||||
Timestamp hexutil.Uint64
|
||||
SlotNumber *hexutil.Uint64
|
||||
TargetGasLimit *hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out ed_codec.go
|
||||
//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"`
|
||||
BlockAccessList []byte `json:"blockAccessList,omitempty"`
|
||||
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||
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
|
||||
SlotNumber *hexutil.Uint64
|
||||
BlockAccessList hexutil.Bytes
|
||||
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
|
||||
}
|
||||
|
||||
// StatelessPayloadStatusV1 is the result of a stateless payload execution.
|
||||
|
|
@ -133,7 +114,7 @@ type StatelessPayloadStatusV1 struct {
|
|||
ValidationError *string `json:"validationError"`
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -enc=false -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out epe_decode.go
|
||||
//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"`
|
||||
|
|
@ -144,12 +125,6 @@ type ExecutionPayloadEnvelope struct {
|
|||
Witness *hexutil.Bytes `json:"witness,omitempty"`
|
||||
}
|
||||
|
||||
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||
type executionPayloadEnvelopeMarshaling struct {
|
||||
BlockValue *hexutil.Big
|
||||
Requests []hexutil.Bytes
|
||||
}
|
||||
|
||||
// BlobsBundle includes the marshalled sidecar data. Note this structure is
|
||||
// shared by BlobsBundleV1 and BlobsBundleV2 for the sake of simplicity.
|
||||
//
|
||||
|
|
@ -166,24 +141,17 @@ type BlobAndProofV1 struct {
|
|||
Proof hexutil.Bytes `json:"proof"`
|
||||
}
|
||||
|
||||
// BlobAndProofListV1 is a list of BlobAndProofV1 with a hand-rolled JSON marshaler
|
||||
// that avoids the overhead of encoding/json for large blob payloads.
|
||||
type BlobAndProofListV1 []*BlobAndProofV1
|
||||
|
||||
type BlobAndProofV2 struct {
|
||||
Blob hexutil.Bytes `json:"blob"`
|
||||
CellProofs []hexutil.Bytes `json:"proofs"` // proofs MUST contain exactly CELLS_PER_EXT_BLOB cell proofs.
|
||||
}
|
||||
|
||||
type BlobCellsAndProofsV1 struct {
|
||||
BlobCells []*hexutil.Bytes `json:"blob_cells"`
|
||||
Proofs []*hexutil.Bytes `json:"proofs"`
|
||||
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||
type executionPayloadEnvelopeMarshaling struct {
|
||||
BlockValue *hexutil.Big
|
||||
Requests []hexutil.Bytes
|
||||
}
|
||||
|
||||
// BlobAndProofListV2 is a list of BlobAndProofV2 with a hand-rolled JSON marshaler
|
||||
// that avoids the overhead of encoding/json for large blob payloads.
|
||||
type BlobAndProofListV2 []*BlobAndProofV2
|
||||
|
||||
type PayloadStatusV1 struct {
|
||||
Status string `json:"status"`
|
||||
Witness *hexutil.Bytes `json:"witness,omitempty"`
|
||||
|
|
@ -245,7 +213,7 @@ func encodeTransactions(txs []*types.Transaction) [][]byte {
|
|||
return enc
|
||||
}
|
||||
|
||||
func DecodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
||||
func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
||||
var txs = make([]*types.Transaction, len(enc))
|
||||
for i, encTx := range enc {
|
||||
var tx types.Transaction
|
||||
|
|
@ -283,7 +251,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
|
|||
// for stateless execution, so it skips checking if the executable data hashes to
|
||||
// the requested hash (stateless has to *compute* the root hash, it's not given).
|
||||
func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) {
|
||||
txs, err := DecodeTransactions(data.Transactions)
|
||||
txs, err := decodeTransactions(data.Transactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -297,7 +265,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
if data.BaseFeePerGas != nil && (data.BaseFeePerGas.Sign() == -1 || data.BaseFeePerGas.BitLen() > 256) {
|
||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", data.BaseFeePerGas)
|
||||
}
|
||||
var blobHashes = make([]common.Hash, 0, len(versionedHashes))
|
||||
var blobHashes = make([]common.Hash, 0, len(txs))
|
||||
for _, tx := range txs {
|
||||
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||
}
|
||||
|
|
@ -306,7 +274,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
}
|
||||
for i := 0; i < len(blobHashes); i++ {
|
||||
if blobHashes[i] != versionedHashes[i] {
|
||||
return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHash: %v", i, versionedHashes[i], blobHashes[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
|
||||
|
|
@ -324,49 +292,31 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
requestsHash = &h
|
||||
}
|
||||
|
||||
// If Amsterdam is enabled, data.BlockAccessList is always non-nil,
|
||||
// even for empty blocks with no state transitions.
|
||||
//
|
||||
// If Amsterdam is not enabled yet, blockAccessListHash is expected
|
||||
// to be nil.
|
||||
var blockAccessListHash *common.Hash
|
||||
if data.BlockAccessList != nil {
|
||||
hash := crypto.Keccak256Hash(data.BlockAccessList)
|
||||
blockAccessListHash = &hash
|
||||
}
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: data.FeeRecipient,
|
||||
Root: data.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: data.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(data.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(data.Number),
|
||||
GasLimit: data.GasLimit,
|
||||
GasUsed: data.GasUsed,
|
||||
Time: data.Timestamp,
|
||||
BaseFee: data.BaseFeePerGas,
|
||||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: data.ExcessBlobGas,
|
||||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
SlotNumber: data.SlotNumber,
|
||||
BlockAccessListHash: blockAccessListHash,
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: data.FeeRecipient,
|
||||
Root: data.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: data.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(data.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(data.Number),
|
||||
GasLimit: data.GasLimit,
|
||||
GasUsed: data.GasUsed,
|
||||
Time: data.Timestamp,
|
||||
BaseFee: data.BaseFeePerGas,
|
||||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: data.ExcessBlobGas,
|
||||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
}
|
||||
body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}
|
||||
if data.BlockAccessList != nil {
|
||||
var accessList bal.BlockAccessList
|
||||
if err := rlp.DecodeBytes(data.BlockAccessList, &accessList); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode BAL: %w", err)
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(body).WithAccessListUnsafe(&accessList), nil
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(body), nil
|
||||
return types.NewBlockWithHeader(header).
|
||||
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
|
||||
nil
|
||||
}
|
||||
|
||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||
|
|
@ -390,13 +340,6 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
|
|||
Withdrawals: block.Withdrawals(),
|
||||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
SlotNumber: block.SlotNumber(),
|
||||
}
|
||||
if al := block.AccessList(); al != nil {
|
||||
var buf bytes.Buffer
|
||||
if err := rlp.Encode(&buf, al); err == nil {
|
||||
data.BlockAccessList = buf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Add blobs.
|
||||
|
|
@ -439,12 +382,6 @@ type ExecutionPayloadBody struct {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
}
|
||||
|
||||
// ExecutionPayloadBodyV2 extends ExecutionPayloadBody with the block access list.
|
||||
type ExecutionPayloadBodyV2 struct {
|
||||
ExecutionPayloadBody
|
||||
BlockAccessList *hexutil.Bytes `json:"blockAccessList"`
|
||||
}
|
||||
|
||||
// Client identifiers to support ClientVersionV1.
|
||||
const (
|
||||
ClientCode = "GE"
|
||||
|
|
|
|||
|
|
@ -182,12 +182,6 @@ func (s *CommitteeChain) Reset() {
|
|||
s.chainmu.Lock()
|
||||
defer s.chainmu.Unlock()
|
||||
|
||||
s.resetLocked()
|
||||
}
|
||||
|
||||
// ResetLocked resets the committee chain without locking. The caller should hold
|
||||
// the chainmu lock.
|
||||
func (s *CommitteeChain) resetLocked() {
|
||||
if err := s.rollback(0); err != nil {
|
||||
log.Error("Error writing batch into chain database", "error", err)
|
||||
}
|
||||
|
|
@ -207,22 +201,22 @@ func (s *CommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
|
|||
}
|
||||
period := bootstrap.Header.SyncPeriod()
|
||||
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil {
|
||||
s.resetLocked()
|
||||
s.Reset()
|
||||
return err
|
||||
}
|
||||
if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil {
|
||||
s.resetLocked()
|
||||
s.Reset()
|
||||
if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil {
|
||||
s.resetLocked()
|
||||
s.Reset()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil {
|
||||
s.resetLocked()
|
||||
s.Reset()
|
||||
return err
|
||||
}
|
||||
if err := s.addCommittee(period, bootstrap.Committee); err != nil {
|
||||
s.resetLocked()
|
||||
s.Reset()
|
||||
return err
|
||||
}
|
||||
s.changeCounter++
|
||||
|
|
|
|||
|
|
@ -438,11 +438,14 @@ func (s *serverWithLimits) fail(desc string) {
|
|||
// failLocked calculates the dynamic failure delay and applies it.
|
||||
func (s *serverWithLimits) failLocked(desc string) {
|
||||
log.Debug("Server error", "description", desc)
|
||||
s.failureDelay *= 2
|
||||
now := s.clock.Now()
|
||||
if now > s.failureDelayEnd {
|
||||
s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
|
||||
}
|
||||
s.failureDelay = max(min(s.failureDelay*2, float64(maxFailureDelay)), float64(minFailureDelay))
|
||||
if s.failureDelay < float64(minFailureDelay) {
|
||||
s.failureDelay = float64(minFailureDelay)
|
||||
}
|
||||
s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
|
||||
s.delay(time.Duration(s.failureDelay))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ const (
|
|||
ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary
|
||||
ssParentRequested // cp parent header requested
|
||||
ssPrintStatus // has all necessary info, print log message if init still not successful
|
||||
ssDone // log message printed, no more action required
|
||||
)
|
||||
|
||||
type serverState struct {
|
||||
|
|
@ -98,10 +99,7 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
|||
case ssDefault:
|
||||
if resp != nil {
|
||||
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) {
|
||||
err := s.chain.CheckpointInit(*checkpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.chain.CheckpointInit(*checkpoint)
|
||||
s.initialized = true
|
||||
return
|
||||
}
|
||||
|
|
@ -182,8 +180,7 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
|||
default:
|
||||
log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name())
|
||||
}
|
||||
s.serverState[server] = serverState{state: ssDefault}
|
||||
requester.Fail(server, "checkpoint init failed")
|
||||
s.serverState[server] = serverState{state: ssDone}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,106 +1,85 @@
|
|||
# This file contains sha256 checksums of optional build dependencies.
|
||||
|
||||
# version:spec-tests tests@v20.0.0
|
||||
# https://github.com/ethereum/execution-specs/releases
|
||||
# https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0
|
||||
b183702a5b447b465873865357ced9eb342315922e52e31b714e2de115dd0bb4 fixtures.tar.gz
|
||||
# version:spec-tests v5.1.0
|
||||
# https://github.com/ethereum/execution-spec-tests/releases
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.25.12
|
||||
# version:golang 1.25.1
|
||||
# https://go.dev/dl/
|
||||
f90dcee4bd023fa376374ea0a5a6ebe553537b39c426ffd8c689469b45519932 go1.25.12.src.tar.gz
|
||||
70b4b6509ed60735eff6ed9496824ad9f96201fdf5bd190184123f5908f224ef go1.25.12.aix-ppc64.tar.gz
|
||||
00a2e743b82bccec03c51c4b0f7e46d5fec52184075fd6c5183c3bb39ae9fb00 go1.25.12.darwin-amd64.tar.gz
|
||||
fa2c88bbcf64bd3b2aef355f026cfec6d3a4a01c132f999c8f8c964eb767164f go1.25.12.darwin-arm64.tar.gz
|
||||
2124cfbc1cf0b949eb819478365d4d665f84297a60f327cc65f75f61b2629b96 go1.25.12.dragonfly-amd64.tar.gz
|
||||
bdb8ab506428e9633653e67c13d582a6230406f01037023b327427c66b058ff2 go1.25.12.freebsd-386.tar.gz
|
||||
4433a424d466d47d1716df69e6a77c65b1a34d82121488014e4656d73740ebb0 go1.25.12.freebsd-amd64.tar.gz
|
||||
429fbcb46468a66d1cd24129a1b6163167676aaae8e0989a08ba95ba6aae65c4 go1.25.12.freebsd-arm.tar.gz
|
||||
c0e31a4cb827fd20fac950bcb4688fd94cdd1491dbdcff7c3754ac8f3b136eb1 go1.25.12.freebsd-arm64.tar.gz
|
||||
0ea50c6d43e809d46c2c5504e97ef11822f7ca137625171924cda971449b5373 go1.25.12.freebsd-riscv64.tar.gz
|
||||
d74c214e0c3ae8f9db23f4ec6f6b2f6cb300c3e4b9d840b82fa517af629c455d go1.25.12.illumos-amd64.tar.gz
|
||||
b71e88ae779850dc2afcd0f2e2208798652311dfda72fdef5d05721d601b8fd3 go1.25.12.linux-386.tar.gz
|
||||
234828b7a89e0e303d2556310ee549fbcf253d28de937bac3da13d6294262ac1 go1.25.12.linux-amd64.tar.gz
|
||||
8b5884aef89600aef5b0b051fb971f11f49bb996521e911f30f02a66884f7bd2 go1.25.12.linux-arm64.tar.gz
|
||||
6cd7311c02c73ba0b482a1cf8c885268edf23519261bf4b5cef3353ad934d1f1 go1.25.12.linux-armv6l.tar.gz
|
||||
0c6b2b7db8509d1df189433709ddc8fe84c12843e3713508e9ca04dc9e75ddeb go1.25.12.linux-loong64.tar.gz
|
||||
5abc63471425ab8b3408f596cd547d28d374f6dc860a4cbd6de79497123db4f3 go1.25.12.linux-mips.tar.gz
|
||||
a18f7a5ac799ed8ac264b63707e7fa475b1b81da6bbdd41c394fcfd69cc6b736 go1.25.12.linux-mips64.tar.gz
|
||||
5bde8a5d4c05b428fca3e6022dc41d4c735858be9c6945e57473da00fcddd0cc go1.25.12.linux-mips64le.tar.gz
|
||||
33c243d2b0700589e75a410ebab5b64580a477e908872abc46eef840e6ea535a go1.25.12.linux-mipsle.tar.gz
|
||||
b5288a7adb38540177146b47aa43bc75f1936c5e14026d6b6e531b534a49eb1a go1.25.12.linux-ppc64.tar.gz
|
||||
64adb4ddefef4f0a6f11af550547f39bf510350da69ab308438a21eacfde97ad go1.25.12.linux-ppc64le.tar.gz
|
||||
26919d62d21b0bee9c5c67ad76ace462edd16c2c128a983d942cb45b0bf7693c go1.25.12.linux-riscv64.tar.gz
|
||||
09875de1d6cb3237437112271b9df3a96bac9fcd10cbf9bc777c00e67f4e3e3d go1.25.12.linux-s390x.tar.gz
|
||||
2f19f4afc3d6551804228484569ec1ebf4a57a91cda75d746384aa71d5016be7 go1.25.12.netbsd-386.tar.gz
|
||||
d2237237d34058658187455d4f05f8f4f5f2118b33827308ad8313ab0f00a693 go1.25.12.netbsd-amd64.tar.gz
|
||||
4d44c8141a829541c3499db6665a47be2c6fdb97903575058809a2a8be53af89 go1.25.12.netbsd-arm.tar.gz
|
||||
fc96791f8b9cdaea544e827dc15574731afd28b7378053e801b3bd466df6dd9d go1.25.12.netbsd-arm64.tar.gz
|
||||
e9ced39c191409207a211c6013b9a156bd90dbd5a76ebd8c66dc3bf69bcb2f9e go1.25.12.openbsd-386.tar.gz
|
||||
bae7c016f0aff806c9af08ecff96b59a232144c96b1d25f4c5f6c85c8e0dbf01 go1.25.12.openbsd-amd64.tar.gz
|
||||
8517c4bca20e975acdd5a2f5e425cd2bec737bbb6d7ee18d11e3a1545014267e go1.25.12.openbsd-arm.tar.gz
|
||||
ba4ac29243f43b85a52f12ed7dd02b767e4524785fb6d04fececb7ce125aaa82 go1.25.12.openbsd-arm64.tar.gz
|
||||
dca067f41d00ba805342062ab21b88831a06a5682e9aaec1c990c175a5656c6b go1.25.12.openbsd-ppc64.tar.gz
|
||||
4f1f5376464fc91f32cd253dadfe3535ff57d1729b24e6ab014fb83964dfe10f go1.25.12.openbsd-riscv64.tar.gz
|
||||
c938792ec65ba33592deea6673265d509aaf9b5297119bc91ae973fb6beb62b9 go1.25.12.plan9-386.tar.gz
|
||||
5aec7ab115c1c6cd049a64f1617ef91d24ea07c8b0f40436d531b42d546f5e37 go1.25.12.plan9-amd64.tar.gz
|
||||
efe1dcb9750507260a28688a749c72f35c3160646f08a80606f38c3d30d74a12 go1.25.12.plan9-arm.tar.gz
|
||||
83c42cfedcc0bb03fd601d692d75b085406c9a7f5ff505bf41030f2734b62ed2 go1.25.12.solaris-amd64.tar.gz
|
||||
18abdf76719f5f84eaa35eeaf87b467a02ed075a8632ad941f10aa7a3d0de713 go1.25.12.windows-386.zip
|
||||
d5dc82da351b00e5eedd04f41356817d674cc4308131f0f638a5b14c5c3af4cb go1.25.12.windows-amd64.zip
|
||||
054f046a5fa31fdcc9491cc19065cbf43bf521d805bbe298ae8d65dd981fca84 go1.25.12.windows-arm64.zip
|
||||
d010c109cee94d80efe681eab46bdea491ac906bf46583c32e9f0dbb0bd1a594 go1.25.1.src.tar.gz
|
||||
1d622468f767a1b9fe1e1e67bd6ce6744d04e0c68712adc689748bbeccb126bb go1.25.1.darwin-amd64.tar.gz
|
||||
68deebb214f39d542e518ebb0598a406ab1b5a22bba8ec9ade9f55fb4dd94a6c go1.25.1.darwin-arm64.tar.gz
|
||||
d03cdcbc9bd8baf5cf028de390478e9e2b3e4d0afe5a6582dedc19bfe6a263b2 go1.25.1.linux-386.tar.gz
|
||||
7716a0d940a0f6ae8e1f3b3f4f36299dc53e31b16840dbd171254312c41ca12e go1.25.1.linux-amd64.tar.gz
|
||||
65a3e34fb2126f55b34e1edfc709121660e1be2dee6bdf405fc399a63a95a87d go1.25.1.linux-arm64.tar.gz
|
||||
eb949be683e82a99e9861dafd7057e31ea40b161eae6c4cd18fdc0e8c4ae6225 go1.25.1.linux-armv6l.tar.gz
|
||||
be13d5479b8c75438f2efcaa8c191fba3af684b3228abc9c99c7aa8502f34424 go1.25.1.windows-386.zip
|
||||
4a974de310e7ee1d523d2fcedb114ba5fa75408c98eb3652023e55ccf3fa7cab go1.25.1.windows-amd64.zip
|
||||
45ab4290adbd6ee9e7f18f0d57eaa9008fdbef590882778ed93eac3c8cca06c5 go1.25.1.aix-ppc64.tar.gz
|
||||
2e3c1549bed3124763774d648f291ac42611232f48320ebbd23517c909c09b81 go1.25.1.dragonfly-amd64.tar.gz
|
||||
dc0198dd4ec520e13f26798def8750544edf6448d8e9c43fd2a814e4885932af go1.25.1.freebsd-386.tar.gz
|
||||
c4f1a7e7b258406e6f3b677ecdbd97bbb23ff9c0d44be4eb238a07d360f69ac8 go1.25.1.freebsd-amd64.tar.gz
|
||||
7772fc5ff71ed39297ec0c1599fc54e399642c9b848eac989601040923b0de9c go1.25.1.freebsd-arm.tar.gz
|
||||
5bb011d5d5b6218b12189f07aa0be618ab2002662fff1ca40afba7389735c207 go1.25.1.freebsd-arm64.tar.gz
|
||||
ccac716240cb049bebfafcb7eebc3758512178a4c51fc26da9cc032035d850c8 go1.25.1.freebsd-riscv64.tar.gz
|
||||
cc53910ffb9fcfdd988a9fa25b5423bae1cfa01b19616be646700e1f5453b466 go1.25.1.illumos-amd64.tar.gz
|
||||
efe809f923bcedab44bf7be2b3af8d182b512b1bf9c07d302e0c45d26c8f56f3 go1.25.1.linux-loong64.tar.gz
|
||||
c0de33679f6ed68991dc42dc4a602e74a666e3e166c1748ee1b5d1a7ea2ffbb2 go1.25.1.linux-mips.tar.gz
|
||||
c270f7b0c0bdfbcd54fef4481227c40d41bb518f9ae38ee930870f04a0a6a589 go1.25.1.linux-mips64.tar.gz
|
||||
80be871ba9c944f34d1868cdf5047e1cf2e1289fe08cdb90e2453d2f0d6965ae go1.25.1.linux-mips64le.tar.gz
|
||||
9f09defa9bb22ebf2cde76162f40958564e57ce5c2b3649bc063bebcbc9294c1 go1.25.1.linux-mipsle.tar.gz
|
||||
2c76b7d278c1d43ad19d478ad3f0f05e7b782b64b90870701b314fa48b5f43c6 go1.25.1.linux-ppc64.tar.gz
|
||||
8b0c8d3ee5b1b5c28b6bd63dc4438792012e01d03b4bf7a61d985c87edab7d1f go1.25.1.linux-ppc64le.tar.gz
|
||||
22fe934a9d0c9c57275716c55b92d46ebd887cec3177c9140705efa9f84ba1e2 go1.25.1.linux-riscv64.tar.gz
|
||||
9cfe517ba423f59f3738ca5c3d907c103253cffbbcc2987142f79c5de8c1bf93 go1.25.1.linux-s390x.tar.gz
|
||||
6af8a08353e76205d5b743dd7a3f0126684f96f62be0a31b75daf9837e512c46 go1.25.1.netbsd-386.tar.gz
|
||||
e5d534ff362edb1bd8c8e10892b6a027c4c1482454245d1529167676498684c7 go1.25.1.netbsd-amd64.tar.gz
|
||||
88bcf39254fdcea6a199c1c27d787831b652427ce60851ae9e41a3d7eb477f45 go1.25.1.netbsd-arm.tar.gz
|
||||
d7c2eabe1d04ee47bcaea2816fdd90dbd25d90d4dfa756faa9786c788e4f3a4e go1.25.1.netbsd-arm64.tar.gz
|
||||
14a2845977eb4dde11d929858c437a043467c427db87899935e90cee04a38d72 go1.25.1.openbsd-386.tar.gz
|
||||
d27ac54b38a13a09c81e67c82ac70d387037341c85c3399291c73e13e83fdd8c go1.25.1.openbsd-amd64.tar.gz
|
||||
0f4ab5f02500afa4befd51fed1e8b45e4d07ca050f641cc3acc76eaa4027b2c3 go1.25.1.openbsd-arm.tar.gz
|
||||
d46c3bd156843656f7f3cb0dec27ea51cd926ec3f7b80744bf8156e67c1c812f go1.25.1.openbsd-arm64.tar.gz
|
||||
c550514c67f22e409be10e40eace761e2e43069f4ef086ae6e60aac736c2b679 go1.25.1.openbsd-ppc64.tar.gz
|
||||
8a09a8714a2556eb13fc1f10b7ce2553fcea4971e3330fc3be0efd24aab45734 go1.25.1.openbsd-riscv64.tar.gz
|
||||
b0e1fefaf0c7abd71f139a54eee9767944aff5f0bc9d69c968234804884e552f go1.25.1.plan9-386.tar.gz
|
||||
e94732c94f149690aa0ab11c26090577211b4a988137cb2c03ec0b54e750402e go1.25.1.plan9-amd64.tar.gz
|
||||
7eb80e9de1e817d9089a54e8c7c5c8d8ed9e5fb4d4a012fc0f18fc422a484f0c go1.25.1.plan9-arm.tar.gz
|
||||
1261dfad7c4953c0ab90381bc1242dc54e394db7485c59349428d532b2273343 go1.25.1.solaris-amd64.tar.gz
|
||||
04bc3c078e9e904c4d58d6ac2532a5bdd402bd36a9ff0b5949b3c5e6006a05ee go1.25.1.windows-arm64.zip
|
||||
|
||||
# version:golangci 2.10.1
|
||||
# version:golangci 2.4.0
|
||||
# https://github.com/golangci/golangci-lint/releases/
|
||||
# https://github.com/golangci/golangci-lint/releases/download/v2.10.1
|
||||
66fb0da81b8033b477f97eea420d4b46b230ca172b8bb87c6610109f3772b6b6 golangci-lint-2.10.1-darwin-amd64.tar.gz
|
||||
03bfadf67e52b441b7ec21305e501c717df93c959836d66c7f97312654acb297 golangci-lint-2.10.1-darwin-arm64.tar.gz
|
||||
c9a44658ccc8f7b8dbbd4ae6020ba91c1a5d3987f4d91ced0f7d2bea013e57ca golangci-lint-2.10.1-freebsd-386.tar.gz
|
||||
a513c5cb4e0f5bd5767001af9d5e97e7868cfc2d9c46739a4df93e713cfb24af golangci-lint-2.10.1-freebsd-amd64.tar.gz
|
||||
2ef38eefc4b5cee2febacb75a30579526e5656c16338a921d80e59a8e87d4425 golangci-lint-2.10.1-freebsd-arm64.tar.gz
|
||||
8fea6766318b4829e766bbe325f10191d75297dcc44ae35bf374816037878e38 golangci-lint-2.10.1-freebsd-armv6.tar.gz
|
||||
30b629870574d6254f3e8804e5a74b34f98e1263c9d55465830d739c88b862ed golangci-lint-2.10.1-freebsd-armv7.tar.gz
|
||||
c0db839f866ce80b1b6c96167aa101cfe50d9c936f42d942a3c1cbdc1801af68 golangci-lint-2.10.1-illumos-amd64.tar.gz
|
||||
280eb56636e9175f671cd7b755d7d67f628ae2ed00a164d1e443c43c112034e5 golangci-lint-2.10.1-linux-386.deb
|
||||
065a7d99da61dc7dfbfef2e2d7053dd3fa6672598f2747117aa4bb5f45e7df7f golangci-lint-2.10.1-linux-386.rpm
|
||||
a55918c03bb413b2662287653ab2ae2fef4e37428b247dad6348724adde9d770 golangci-lint-2.10.1-linux-386.tar.gz
|
||||
8aa9b3aa14f39745eeb7fc7ff50bcac683e785397d1e4bc9afd2184b12c4ce86 golangci-lint-2.10.1-linux-amd64.deb
|
||||
62a111688e9e305032334a2cbc84f4d971b64bb3bffc99d3f80081d57fb25e32 golangci-lint-2.10.1-linux-amd64.rpm
|
||||
dfa775874cf0561b404a02a8f4481fc69b28091da95aa697259820d429b09c99 golangci-lint-2.10.1-linux-amd64.tar.gz
|
||||
b3f36937e8ea1660739dc0f5c892ea59c9c21ed4e75a91a25957c561f7f79a55 golangci-lint-2.10.1-linux-arm64.deb
|
||||
36d50314d53683b1f1a2a6cedfb5a9468451b481c64ab9e97a8e843ea088074d golangci-lint-2.10.1-linux-arm64.rpm
|
||||
6652b42ae02915eb2f9cb2a2e0cac99514c8eded8388d88ae3e06e1a52c00de8 golangci-lint-2.10.1-linux-arm64.tar.gz
|
||||
a32d8d318e803496812dd3461f250e52ccc7f53c47b95ce404a9cf55778ceb6a golangci-lint-2.10.1-linux-armv6.deb
|
||||
41d065f4c8ea165a1531abea644988ee2e973e4f0b49f9725ed3b979dac45112 golangci-lint-2.10.1-linux-armv6.rpm
|
||||
59159a4df03aabbde69d15c7b7b3df143363cbb41f4bd4b200caffb8e34fb734 golangci-lint-2.10.1-linux-armv6.tar.gz
|
||||
b2e8ec0e050a1e2251dfe1561434999d202f5a3f9fa47ce94378b0fd1662ea5a golangci-lint-2.10.1-linux-armv7.deb
|
||||
28c9331429a497da27e9c77846063bd0e8275e878ffedb4eb9e9f21d24771cc0 golangci-lint-2.10.1-linux-armv7.rpm
|
||||
818f33e95b273e3769284b25563b51ef6a294e9e25acf140fda5830c075a1a59 golangci-lint-2.10.1-linux-armv7.tar.gz
|
||||
6b6b85ed4b7c27f51097dd681523000409dde835e86e6e314e87be4bb013e2ab golangci-lint-2.10.1-linux-loong64.deb
|
||||
94050a0cf06169e2ae44afb307dcaafa7d7c3b38c0c23b5652cf9cb60f0c337f golangci-lint-2.10.1-linux-loong64.rpm
|
||||
25820300fccb8c961c1cdcb1f77928040c079e04c43a3a5ceb34b1cb4a1c5c8d golangci-lint-2.10.1-linux-loong64.tar.gz
|
||||
98bf39d10139fdcaa37f94950e9bbb8888660ae468847ae0bf1cb5bf67c1f68b golangci-lint-2.10.1-linux-mips64.deb
|
||||
df3ce5f03808dcceaa8b683d1d06e95c885f09b59dc8e15deb840fbe2b3e3299 golangci-lint-2.10.1-linux-mips64.rpm
|
||||
972508dda523067e6e6a1c8e6609d63bc7c4153819c11b947d439235cf17bac2 golangci-lint-2.10.1-linux-mips64.tar.gz
|
||||
1d37f2919e183b5bf8b1777ed8c4b163d3b491d0158355a7999d647655cbbeb6 golangci-lint-2.10.1-linux-mips64le.deb
|
||||
e341d031002cd09a416329ed40f674231051a38544b8f94deb2d1708ce1f4a6f golangci-lint-2.10.1-linux-mips64le.rpm
|
||||
393560122b9cb5538df0c357d30eb27b6ee563533fbb9b138c8db4fd264002af golangci-lint-2.10.1-linux-mips64le.tar.gz
|
||||
21ca46b6a96442e8957677a3ca059c6b93674a68a01b1c71f4e5df0ea2e96d19 golangci-lint-2.10.1-linux-ppc64le.deb
|
||||
57fe0cbca0a9bbdf1547c5e8aa7d278e6896b438d72a541bae6bc62c38b43d1e golangci-lint-2.10.1-linux-ppc64le.rpm
|
||||
e2883db9fa51584e5e203c64456f29993550a7faadc84e3faccdb48f0669992e golangci-lint-2.10.1-linux-ppc64le.tar.gz
|
||||
aa6da0e98ab0ba3bb7582e112174c349907d5edfeff90a551dca3c6eecf92fc0 golangci-lint-2.10.1-linux-riscv64.deb
|
||||
3c68d76cd884a7aad206223a980b9c20bb9ea74b560fa27ed02baf2389189234 golangci-lint-2.10.1-linux-riscv64.rpm
|
||||
3bca11bfac4197205639cbd4676a5415054e629ac6c12ea10fcbe33ef852d9c3 golangci-lint-2.10.1-linux-riscv64.tar.gz
|
||||
0c6aed2ce49db2586adbac72c80d871f06feb1caf4c0763a5ca98fec809a8f0b golangci-lint-2.10.1-linux-s390x.deb
|
||||
16c285adfe1061d69dd8e503be69f87c7202857c6f4add74ac02e3571158fbec golangci-lint-2.10.1-linux-s390x.rpm
|
||||
21011ad368eb04f024201b832095c6b5f96d0888de194cca5bfe4d9307d6364b golangci-lint-2.10.1-linux-s390x.tar.gz
|
||||
7b5191e77a70485918712e31ed55159956323e4911bab1b67569c9d86e1b75eb golangci-lint-2.10.1-netbsd-386.tar.gz
|
||||
07801fd38d293ebad10826f8285525a39ea91ce5ddad77d05bfa90bda9c884a9 golangci-lint-2.10.1-netbsd-amd64.tar.gz
|
||||
7e7219d71c1bf33b98c328c93dc0560706dd896a1c43c44696e5222fc9d7446e golangci-lint-2.10.1-netbsd-arm64.tar.gz
|
||||
92fbc90b9eec0e572269b0f5492a2895c426b086a68372fde49b7e4d4020863e golangci-lint-2.10.1-netbsd-armv6.tar.gz
|
||||
f67b3ae1f47caeefa507a4ebb0c8336958a19011fe48766443212030f75d004b golangci-lint-2.10.1-netbsd-armv7.tar.gz
|
||||
a40bc091c10cea84eaee1a90b84b65f5e8652113b0a600bb099e4e4d9d7caddb golangci-lint-2.10.1-windows-386.zip
|
||||
c60c87695e79db8e320f0e5be885059859de52bb5ee5f11be5577828570bc2a3 golangci-lint-2.10.1-windows-amd64.zip
|
||||
636ab790c8dcea8034aa34aba6031ca3893d68f7eda000460ab534341fadbab1 golangci-lint-2.10.1-windows-arm64.zip
|
||||
# https://github.com/golangci/golangci-lint/releases/download/v2.4.0/
|
||||
7904ce63f79db44934939cf7a063086ea0ea98e9b19eba0a9d52ccdd0d21951c golangci-lint-2.4.0-darwin-amd64.tar.gz
|
||||
cd4dd53fa09b6646baff5fd22b8c64d91db02c21c7496df27992d75d34feec59 golangci-lint-2.4.0-darwin-arm64.tar.gz
|
||||
d58f426ebe14cc257e81562b4bf37a488ffb4ffbbb3ec73041eb3b38bb25c0e1 golangci-lint-2.4.0-freebsd-386.tar.gz
|
||||
6ec4a6177fc6c0dd541fbcb3a7612845266d020d35cc6fa92959220cdf64ca39 golangci-lint-2.4.0-freebsd-amd64.tar.gz
|
||||
4d473e3e71c01feaa915a0604fb35758b41284fb976cdeac3f842118d9ee7e17 golangci-lint-2.4.0-freebsd-armv6.tar.gz
|
||||
58727746c6530801a3f9a702a5945556a5eb7e88809222536dd9f9d54cafaeff golangci-lint-2.4.0-freebsd-armv7.tar.gz
|
||||
fbf28c662760e24c32f82f8d16dffdb4a82de7726a52ba1fad94f890c22997ea golangci-lint-2.4.0-illumos-amd64.tar.gz
|
||||
a15a000a8981ef665e971e0f67e2acda9066a9e37a59344393b7351d8fb49c81 golangci-lint-2.4.0-linux-386.tar.gz
|
||||
fae792524c04424c0ac369f5b8076f04b45cf29fc945a370e55d369a8dc11840 golangci-lint-2.4.0-linux-amd64.tar.gz
|
||||
70ac11f55b80ec78fd3a879249cc9255121b8dfd7f7ed4fc46ed137f4abf17e7 golangci-lint-2.4.0-linux-arm64.tar.gz
|
||||
4acdc40e5cebe99e4e7ced358a05b2e71789f409b41cb4f39bbb86ccfa14b1dc golangci-lint-2.4.0-linux-armv6.tar.gz
|
||||
2a68749568fa22b4a97cb88dbea655595563c795076536aa6c087f7968784bf3 golangci-lint-2.4.0-linux-armv7.tar.gz
|
||||
9e3369afb023711036dcb0b4f45c9fe2792af962fa1df050c9f6ac101a6c5d73 golangci-lint-2.4.0-linux-loong64.tar.gz
|
||||
bb9143d6329be2c4dbfffef9564078e7da7d88e7dde6c829b6263d98e072229e golangci-lint-2.4.0-linux-mips64.tar.gz
|
||||
5ad1765b40d56cd04d4afd805b3ba6f4bfd9b36181da93c31e9b17e483d8608d golangci-lint-2.4.0-linux-mips64le.tar.gz
|
||||
918936fb9c0d5ba96bef03cf4348b03938634cfcced49be1e9bb29cb5094fa73 golangci-lint-2.4.0-linux-ppc64le.tar.gz
|
||||
f7474c638e1fb67ebbdc654b55ca0125377ea0bc88e8fee8d964a4f24eacf828 golangci-lint-2.4.0-linux-riscv64.tar.gz
|
||||
b617a9543997c8bfceaffa88a75d4e595030c6add69fba800c1e4d8f5fe253dd golangci-lint-2.4.0-linux-s390x.tar.gz
|
||||
7db027b03a9ba328f795215b04f594036837bc7dd0dd7cd16776b02a6167981c golangci-lint-2.4.0-netbsd-386.tar.gz
|
||||
52d8f9393f4313df0a62b752c37775e3af0b818e43e8dd28954351542d7c60bc golangci-lint-2.4.0-netbsd-amd64.tar.gz
|
||||
5c0086027fb5a4af3829e530c8115db4b35d11afe1914322eef528eb8cd38c69 golangci-lint-2.4.0-netbsd-arm64.tar.gz
|
||||
6b779d6ed1aed87cefe195cc11759902b97a76551b593312c6833f2635a3488f golangci-lint-2.4.0-netbsd-armv6.tar.gz
|
||||
f00d1f4b7ec3468a0f9fffd0d9ea036248b029b7621cbc9a59c449ef94356d09 golangci-lint-2.4.0-netbsd-armv7.tar.gz
|
||||
3ce671b0b42b58e35066493aab75a7e2826c9e079988f1ba5d814a4029faaf87 golangci-lint-2.4.0-windows-386.zip
|
||||
003112f7a56746feaabf20b744054bf9acdf900c9e77176383623c4b1d76aaa9 golangci-lint-2.4.0-windows-amd64.zip
|
||||
dc0c2092af5d47fc2cd31a1dfe7b4c7e765fab22de98bd21ef2ffcc53ad9f54f golangci-lint-2.4.0-windows-arm64.zip
|
||||
0263d23e20a260cb1592d35e12a388f99efe2c51b3611fdc66fbd9db1fce664d golangci-lint-2.4.0-windows-armv6.zip
|
||||
9403c03bf648e6313036e0273149d44bad1b9ad53889b6d00e4ccb842ba3c058 golangci-lint-2.4.0-windows-armv7.zip
|
||||
|
||||
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
|
||||
#
|
||||
|
|
|
|||
317
build/ci.go
|
|
@ -73,9 +73,21 @@ var (
|
|||
"./cmd/keeper",
|
||||
}
|
||||
|
||||
// Files that end up in the geth-alltools*.zip archive (and the NSIS installer
|
||||
// dev-tools section). Order matches the historical layout produced by ci.go.
|
||||
allToolsBinaries = []string{"abigen", "evm", "geth", "rlpdump"}
|
||||
// Files that end up in the geth*.zip archive.
|
||||
gethArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("geth"),
|
||||
}
|
||||
|
||||
// Files that end up in the geth-alltools*.zip archive.
|
||||
allToolsArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("abigen"),
|
||||
executablePath("evm"),
|
||||
executablePath("geth"),
|
||||
executablePath("rlpdump"),
|
||||
executablePath("clef"),
|
||||
}
|
||||
|
||||
// Keeper build targets with their configurations
|
||||
keeperTargets = []struct {
|
||||
|
|
@ -95,21 +107,17 @@ var (
|
|||
Tags: "ziren",
|
||||
Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"},
|
||||
},
|
||||
{
|
||||
Name: "womir",
|
||||
GOOS: "wasip1",
|
||||
GOARCH: "wasm",
|
||||
Tags: "womir",
|
||||
},
|
||||
{
|
||||
Name: "wasm-js",
|
||||
GOOS: "js",
|
||||
GOARCH: "wasm",
|
||||
Tags: "example",
|
||||
},
|
||||
{
|
||||
Name: "wasm-wasi",
|
||||
GOOS: "wasip1",
|
||||
GOARCH: "wasm",
|
||||
Tags: "example",
|
||||
},
|
||||
{
|
||||
Name: "example",
|
||||
|
|
@ -135,6 +143,10 @@ var (
|
|||
BinaryName: "rlpdump",
|
||||
Description: "Developer utility tool that prints RLP structures.",
|
||||
},
|
||||
{
|
||||
BinaryName: "clef",
|
||||
Description: "Ethereum account management tool.",
|
||||
},
|
||||
}
|
||||
|
||||
// A debian package is created for all executables listed here.
|
||||
|
|
@ -151,11 +163,13 @@ var (
|
|||
|
||||
// Distros for which packages are created
|
||||
debDistros = []string{
|
||||
"xenial", // 16.04, EOL: 04/2026
|
||||
"bionic", // 18.04, EOL: 04/2028
|
||||
"focal", // 20.04, EOL: 04/2030
|
||||
"jammy", // 22.04, EOL: 04/2032
|
||||
"noble", // 24.04, EOL: 04/2034
|
||||
"xenial", // 16.04, EOL: 04/2026
|
||||
"bionic", // 18.04, EOL: 04/2028
|
||||
"focal", // 20.04, EOL: 04/2030
|
||||
"jammy", // 22.04, EOL: 04/2032
|
||||
"noble", // 24.04, EOL: 04/2034
|
||||
"oracular", // 24.10, EOL: 07/2025
|
||||
"plucky", // 25.04, EOL: 01/2026
|
||||
}
|
||||
|
||||
// This is where the tests should be unpacked.
|
||||
|
|
@ -164,35 +178,13 @@ var (
|
|||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
|
||||
// executablePath returns the path to a built binary in GOBIN, applying the
|
||||
// platform-specific extension for the given target OS.
|
||||
func executablePath(name, targetOS string) string {
|
||||
if targetOS == "windows" {
|
||||
func executablePath(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
return filepath.Join(GOBIN, name)
|
||||
}
|
||||
|
||||
// gethArchiveFiles returns the file list for the geth-{platform}-{ver}.zip
|
||||
// archive, with binary paths resolved for the target OS.
|
||||
func gethArchiveFiles(targetOS string) []string {
|
||||
return []string{
|
||||
"COPYING",
|
||||
executablePath("geth", targetOS),
|
||||
}
|
||||
}
|
||||
|
||||
// allToolsArchiveFiles returns the file list for the
|
||||
// geth-alltools-{platform}-{ver}.zip archive, with binary paths resolved for
|
||||
// the target OS.
|
||||
func allToolsArchiveFiles(targetOS string) []string {
|
||||
files := []string{"COPYING"}
|
||||
for _, name := range allToolsBinaries {
|
||||
files = append(files, executablePath(name, targetOS))
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
|
|
@ -239,7 +231,6 @@ func main() {
|
|||
func doInstall(cmdline []string) {
|
||||
var (
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
targetOS = flag.String("os", runtime.GOOS, "Target OS to cross build for")
|
||||
arch = flag.String("arch", "", "Architecture to cross build for")
|
||||
cc = flag.String("cc", "", "C compiler to cross build with")
|
||||
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
||||
|
|
@ -248,7 +239,7 @@ func doInstall(cmdline []string) {
|
|||
env := build.Env()
|
||||
|
||||
// Configure the toolchain.
|
||||
tc := build.GoToolchain{GOOS: *targetOS, GOARCH: *arch, CC: *cc}
|
||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||
if *dlgo {
|
||||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||
tc.Root = build.DownloadGo(csdb)
|
||||
|
|
@ -262,7 +253,7 @@ func doInstall(cmdline []string) {
|
|||
}
|
||||
|
||||
// Configure the build.
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags, *targetOS)...)
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
||||
|
||||
// Show packages during build.
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
|
|
@ -277,7 +268,7 @@ func doInstall(cmdline []string) {
|
|||
// Do the build!
|
||||
for _, pkg := range packages {
|
||||
args := slices.Clone(gobuild.Args)
|
||||
args = append(args, "-o", executablePath(path.Base(pkg), *targetOS))
|
||||
args = append(args, "-o", executablePath(path.Base(pkg)))
|
||||
args = append(args, pkg)
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
||||
}
|
||||
|
|
@ -304,13 +295,7 @@ func doInstallKeeper(cmdline []string) {
|
|||
tc.GOARCH = target.GOARCH
|
||||
tc.GOOS = target.GOOS
|
||||
tc.CC = target.CC
|
||||
// An empty GOOS means "build for the host OS"; thread that through to
|
||||
// buildFlags so platform-specific linker flags are picked correctly.
|
||||
targetOS := target.GOOS
|
||||
if targetOS == "" {
|
||||
targetOS = runtime.GOOS
|
||||
}
|
||||
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags}, targetOS)...)
|
||||
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...)
|
||||
gobuild.Dir = "./cmd/keeper"
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
|
||||
|
|
@ -320,15 +305,14 @@ func doInstallKeeper(cmdline []string) {
|
|||
outputName := fmt.Sprintf("keeper-%s", target.Name)
|
||||
|
||||
args := slices.Clone(gobuild.Args)
|
||||
args = append(args, "-o", executablePath(outputName, targetOS))
|
||||
args = append(args, "-o", executablePath(outputName))
|
||||
args = append(args, ".")
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
||||
}
|
||||
}
|
||||
|
||||
// buildFlags returns the go tool flags for building. targetOS is the OS we
|
||||
// are producing binaries for.
|
||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string, targetOS string) (flags []string) {
|
||||
// buildFlags returns the go tool flags for building.
|
||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
|
||||
var ld []string
|
||||
// See https://github.com/golang/go/issues/33772#issuecomment-528176001
|
||||
// We need to set --buildid to the linker here, and also pass --build-id to the
|
||||
|
|
@ -340,10 +324,10 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string, t
|
|||
}
|
||||
// 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.
|
||||
if targetOS == "darwin" {
|
||||
if runtime.GOOS == "darwin" {
|
||||
ld = append(ld, "-s")
|
||||
}
|
||||
if targetOS == "linux" {
|
||||
if runtime.GOOS == "linux" {
|
||||
// Enforce the stacksize to 8M, which is the case on most platforms apart from
|
||||
// alpine Linux.
|
||||
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
|
||||
|
|
@ -452,7 +436,7 @@ func doTest(cmdline []string) {
|
|||
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||
func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
ext := ".tar.gz"
|
||||
base := "fixtures"
|
||||
base := "fixtures_develop"
|
||||
archivePath := filepath.Join(cachedir, base+ext)
|
||||
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -696,13 +680,12 @@ func downloadProtoc(cachedir string) string {
|
|||
// Release Packaging
|
||||
func doArchive(cmdline []string) {
|
||||
var (
|
||||
targetOS = flag.String("os", runtime.GOOS, "Target OS the binaries were built for")
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
ext string
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
ext string
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
switch *atype {
|
||||
|
|
@ -716,15 +699,15 @@ func doArchive(cmdline []string) {
|
|||
|
||||
var (
|
||||
env = build.Env()
|
||||
basegeth = archiveBasename(*targetOS, *arch, version.Archive(env.Commit))
|
||||
basegeth = archiveBasename(*arch, version.Archive(env.Commit))
|
||||
geth = "geth-" + basegeth + ext
|
||||
alltools = "geth-alltools-" + basegeth + ext
|
||||
)
|
||||
maybeSkipArchive(env)
|
||||
if err := build.WriteArchive(geth, gethArchiveFiles(*targetOS)); err != nil {
|
||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles(*targetOS)); err != nil {
|
||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, archive := range []string{geth, alltools} {
|
||||
|
|
@ -750,11 +733,7 @@ func doKeeperArchive(cmdline []string) {
|
|||
maybeSkipArchive(env)
|
||||
files := []string{"COPYING"}
|
||||
for _, target := range keeperTargets {
|
||||
targetOS := target.GOOS
|
||||
if targetOS == "" {
|
||||
targetOS = runtime.GOOS
|
||||
}
|
||||
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name), targetOS))
|
||||
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name)))
|
||||
}
|
||||
if err := build.WriteArchive(keeper, files); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -764,8 +743,8 @@ func doKeeperArchive(cmdline []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func archiveBasename(targetOS, arch, archiveVersion string) string {
|
||||
platform := targetOS + "-" + arch
|
||||
func archiveBasename(arch string, archiveVersion string) string {
|
||||
platform := runtime.GOOS + "-" + arch
|
||||
if arch == "arm" {
|
||||
platform += os.Getenv("GOARM")
|
||||
}
|
||||
|
|
@ -937,104 +916,54 @@ func doDebianSource(cmdline []string) {
|
|||
cidepfetch.Env = append(cidepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath"))
|
||||
cidepfetch.Run() // Command fails, don't care, we only need the deps to start it
|
||||
|
||||
// Create Debian packages and upload them. Each distro is independent, so a
|
||||
// failure in one must not stop the others.
|
||||
var failed []string
|
||||
// Create Debian packages and upload them.
|
||||
for _, pkg := range debPackages {
|
||||
for _, distro := range debDistros {
|
||||
target := fmt.Sprintf("%s/%s", pkg.Name, distro)
|
||||
err := retry(debianBuildAttempts, func(attempt int) error {
|
||||
if attempt > 1 {
|
||||
log.Printf("Retrying %s (attempt %d/%d)", target, attempt, debianBuildAttempts)
|
||||
// Prepare the debian package with the go-ethereum sources.
|
||||
meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
|
||||
pkgdir := stageDebianSource(*workdir, meta)
|
||||
|
||||
// Add bootstrapper Go source code
|
||||
for i, gobootbundle := range gobootbundles {
|
||||
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, fmt.Sprintf(".goboot-%d", i+1))); err != nil {
|
||||
log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err)
|
||||
}
|
||||
return buildDebianPackage(*workdir, distro, *signer, *upload, *sshUser, env, now, pkg, gobootbundles, gobundle)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("FAILED %s: %v", target, err)
|
||||
failed = append(failed, target)
|
||||
continue
|
||||
}
|
||||
log.Printf("Done %s", target)
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
log.Fatalf("%d of %d debian packages failed: %s", len(failed), len(debPackages)*len(debDistros), strings.Join(failed, ", "))
|
||||
}
|
||||
}
|
||||
// Add builder Go source code
|
||||
if err := build.ExtractArchive(gobundle, pkgdir); err != nil {
|
||||
log.Fatalf("Failed to extract builder Go sources: %v", err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// Add all dependency modules in compressed form
|
||||
os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755)
|
||||
if err := cp.CopyAll(filepath.Join(pkgdir, ".mod", "cache", "download"), filepath.Join(*workdir, "modgopath", "pkg", "mod", "cache", "download")); err != nil {
|
||||
log.Fatalf("Failed to copy Go module dependencies: %v", err)
|
||||
}
|
||||
// Run the packaging and upload to the PPA
|
||||
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz", "-nc")
|
||||
debuild.Dir = pkgdir
|
||||
build.MustRun(debuild)
|
||||
|
||||
// debianBuildAttempts is how many times a single distro's package is built before
|
||||
// giving up on it.
|
||||
const debianBuildAttempts = 3
|
||||
|
||||
// retry runs fn until it succeeds or the attempts are exhausted, pausing between
|
||||
// tries. The attempt number is passed in so callers can report progress. The
|
||||
// error from the last attempt is returned.
|
||||
func retry(attempts int, fn func(attempt int) error) error {
|
||||
var err error
|
||||
for i := 1; i <= attempts; i++ {
|
||||
if err = fn(i); err == nil {
|
||||
return nil
|
||||
}
|
||||
if i < attempts {
|
||||
log.Printf("Attempt %d/%d failed: %v", i, attempts, err)
|
||||
time.Sleep(5 * time.Second)
|
||||
var (
|
||||
basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString())
|
||||
source = filepath.Join(*workdir, basename+".tar.xz")
|
||||
dsc = filepath.Join(*workdir, basename+".dsc")
|
||||
changes = filepath.Join(*workdir, basename+"_source.changes")
|
||||
buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo")
|
||||
)
|
||||
if *signer != "" {
|
||||
build.MustRunCommand("debsign", changes)
|
||||
}
|
||||
if *upload != "" {
|
||||
ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo})
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("all %d attempts failed, last error: %w", attempts, err)
|
||||
}
|
||||
|
||||
// buildDebianPackage stages, builds, signs and uploads the source package for a
|
||||
// single distro.
|
||||
func buildDebianPackage(workdir, distro, signer, upload, sshUser string, env build.Environment, now time.Time, pkg debPackage, gobootbundles []string, gobundle string) error {
|
||||
// Prepare the debian package with the go-ethereum sources.
|
||||
meta := newDebMetadata(distro, signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
|
||||
pkgdir := stageDebianSource(workdir, meta)
|
||||
|
||||
// Add bootstrapper Go source code
|
||||
for i, gobootbundle := range gobootbundles {
|
||||
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
|
||||
return fmt.Errorf("extracting bootstrapper Go sources: %w", err)
|
||||
}
|
||||
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, fmt.Sprintf(".goboot-%d", i+1))); err != nil {
|
||||
return fmt.Errorf("renaming bootstrapper Go source folder: %w", err)
|
||||
}
|
||||
}
|
||||
// Add builder Go source code
|
||||
if err := build.ExtractArchive(gobundle, pkgdir); err != nil {
|
||||
return fmt.Errorf("extracting builder Go sources: %w", err)
|
||||
}
|
||||
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil {
|
||||
return fmt.Errorf("renaming builder Go source folder: %w", err)
|
||||
}
|
||||
// Add all dependency modules in compressed form
|
||||
os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755)
|
||||
if err := cp.CopyAll(filepath.Join(pkgdir, ".mod", "cache", "download"), filepath.Join(workdir, "modgopath", "pkg", "mod", "cache", "download")); err != nil {
|
||||
return fmt.Errorf("copying Go module dependencies: %w", err)
|
||||
}
|
||||
// Run the packaging and upload to the PPA
|
||||
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz", "-nc")
|
||||
debuild.Dir = pkgdir
|
||||
if err := build.Run(debuild); err != nil {
|
||||
return fmt.Errorf("debuild: %w", err)
|
||||
}
|
||||
var (
|
||||
basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString())
|
||||
source = filepath.Join(workdir, basename+".tar.xz")
|
||||
dsc = filepath.Join(workdir, basename+".dsc")
|
||||
changes = filepath.Join(workdir, basename+"_source.changes")
|
||||
buildinfo = filepath.Join(workdir, basename+"_source.buildinfo")
|
||||
)
|
||||
if signer != "" {
|
||||
if err := build.Run(exec.Command("debsign", changes)); err != nil {
|
||||
return fmt.Errorf("debsign: %w", err)
|
||||
}
|
||||
}
|
||||
if upload != "" {
|
||||
if err := ppaUpload(workdir, upload, sshUser, []string{source, dsc, changes, buildinfo}); err != nil {
|
||||
return fmt.Errorf("ppa upload: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadGoBootstrapSources downloads the Go source tarball(s) that will be used
|
||||
|
|
@ -1073,14 +1002,10 @@ func downloadGoSources(cachedir string) string {
|
|||
return dst
|
||||
}
|
||||
|
||||
// ppaUploadAttempts is how many times a single upload is tried. It is separate
|
||||
// from debianBuildAttempts so that a flaky upload does not force a rebuild.
|
||||
const ppaUploadAttempts = 3
|
||||
|
||||
func ppaUpload(workdir, ppa, sshUser string, files []string) error {
|
||||
func ppaUpload(workdir, ppa, sshUser string, files []string) {
|
||||
p := strings.Split(ppa, "/")
|
||||
if len(p) != 2 {
|
||||
return fmt.Errorf("-upload PPA name %q must contain a single /", ppa)
|
||||
log.Fatal("-upload PPA name must contain single /")
|
||||
}
|
||||
if sshUser == "" {
|
||||
sshUser = p[0]
|
||||
|
|
@ -1094,13 +1019,17 @@ func ppaUpload(workdir, ppa, sshUser string, files []string) error {
|
|||
os.WriteFile(idfile, sshkey, 0600)
|
||||
}
|
||||
}
|
||||
// Upload. This doesn't always work, so retry here rather than leaning on the
|
||||
// caller's retry, which would redo the whole (slow) package build just to get
|
||||
// back to this point.
|
||||
// Upload. This doesn't always work, so try up to three times.
|
||||
dest := sshUser + "@ppa.launchpad.net"
|
||||
return retry(ppaUploadAttempts, func(int) error {
|
||||
return build.UploadSFTP(idfile, dest, incomingDir, files)
|
||||
})
|
||||
for i := 0; i < 3; i++ {
|
||||
err := build.UploadSFTP(idfile, dest, incomingDir, files)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
log.Println("PPA upload failed:", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
log.Fatal("PPA upload failed all attempts.")
|
||||
}
|
||||
|
||||
func getenvBase64(variable string) []byte {
|
||||
|
|
@ -1240,12 +1169,6 @@ func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
|||
func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
|
||||
pkg := meta.Name() + "-" + meta.VersionString()
|
||||
pkgdir = filepath.Join(tmpdir, pkg)
|
||||
|
||||
// Start from a clean directory so that staging can be repeated, which a retry
|
||||
// of the enclosing build depends on.
|
||||
if err := os.RemoveAll(pkgdir); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(pkgdir, 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
@ -1275,7 +1198,7 @@ func doWindowsInstaller(cmdline []string) {
|
|||
var (
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
|
||||
signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
||||
)
|
||||
|
|
@ -1284,13 +1207,13 @@ func doWindowsInstaller(cmdline []string) {
|
|||
env := build.Env()
|
||||
maybeSkipArchive(env)
|
||||
|
||||
// Aggregate binaries that are included in the installer.
|
||||
// Aggregate binaries that are included in the installer
|
||||
var (
|
||||
devTools []string
|
||||
allTools []string
|
||||
gethTool string
|
||||
)
|
||||
for _, file := range allToolsArchiveFiles("windows") {
|
||||
for _, file := range allToolsArchiveFiles {
|
||||
if file == "COPYING" { // license, copied later
|
||||
continue
|
||||
}
|
||||
|
|
@ -1327,24 +1250,16 @@ func doWindowsInstaller(cmdline []string) {
|
|||
if env.Commit != "" {
|
||||
ver[2] += "-" + env.Commit[:8]
|
||||
}
|
||||
installer, err := filepath.Abs("geth-" + archiveBasename("windows", *arch, version.Archive(env.Commit)) + ".exe")
|
||||
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Commit)) + ".exe")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to convert installer file path: %v", err)
|
||||
}
|
||||
// makensis on Windows is "makensis.exe" with /D-style defines; on Linux
|
||||
// (and other Unixes) the binary is "makensis" and accepts -D.
|
||||
makensisCmd := "makensis"
|
||||
defineFlag := "-D"
|
||||
if runtime.GOOS == "windows" {
|
||||
makensisCmd = "makensis.exe"
|
||||
defineFlag = "/D"
|
||||
}
|
||||
build.MustRunCommand(makensisCmd,
|
||||
defineFlag+"OUTPUTFILE="+installer,
|
||||
defineFlag+"MAJORVERSION="+ver[0],
|
||||
defineFlag+"MINORVERSION="+ver[1],
|
||||
defineFlag+"BUILDVERSION="+ver[2],
|
||||
defineFlag+"ARCH="+*arch,
|
||||
build.MustRunCommand("makensis.exe",
|
||||
"/DOUTPUTFILE="+installer,
|
||||
"/DMAJORVERSION="+ver[0],
|
||||
"/DMINORVERSION="+ver[1],
|
||||
"/DBUILDVERSION="+ver[2],
|
||||
"/DARCH="+*arch,
|
||||
filepath.Join(*workdir, "geth.nsi"),
|
||||
)
|
||||
// Sign and publish installer.
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
|
|||
code string
|
||||
err error
|
||||
)
|
||||
if c.Bool(v2Flag.Name) {
|
||||
if c.IsSet(v2Flag.Name) {
|
||||
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
|
||||
} else {
|
||||
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
||||
|
|
|
|||
922
cmd/clef/README.md
Normal file
|
|
@ -0,0 +1,922 @@
|
|||
# Clef
|
||||
|
||||
Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and ask for permission to sign the content. If the user grants the signing request, Clef will send the signature back to the DApp.
|
||||
|
||||
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management.
|
||||
|
||||
Clef can run as a daemon on the same machine, off a usb-stick like [USB armory](https://inversepath.com/usbarmory), or even a separate VM in a [QubesOS](https://www.qubes-os.org/) type setup.
|
||||
|
||||
Check out the
|
||||
|
||||
* [CLI tutorial](tutorial.md) for some concrete examples on how Clef works.
|
||||
* [Setup docs](docs/setup.md) for information on how to configure Clef on QubesOS or USB Armory.
|
||||
* [Data types](datatypes.md) for details on the communication messages between Clef and an external UI.
|
||||
|
||||
## Command line flags
|
||||
|
||||
Clef accepts the following command line options:
|
||||
|
||||
```
|
||||
COMMANDS:
|
||||
init Initialize the signer, generate secret storage
|
||||
attest Attest that a js-file is to be used
|
||||
setpw Store a credential for a keystore file
|
||||
delpw Remove a credential for a keystore file
|
||||
gendoc Generate documentation about json-rpc format
|
||||
help Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--loglevel value log level to emit to the screen (default: 4)
|
||||
--keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore")
|
||||
--configdir value Directory for Clef configuration (default: "$HOME/.clef")
|
||||
--chainid value Chain id to use for signing (1=mainnet, 17000=Holesky) (default: 1)
|
||||
--lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength
|
||||
--nousb Disables monitoring for and managing USB hardware wallets
|
||||
--pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm")
|
||||
--http.addr value HTTP-RPC server listening interface (default: "localhost")
|
||||
--http.vhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost")
|
||||
--ipcdisable Disable the IPC-RPC server
|
||||
--ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it)
|
||||
--http Enable the HTTP-RPC server
|
||||
--http.port value HTTP-RPC server listening port (default: 8550)
|
||||
--signersecret value A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash
|
||||
--4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json")
|
||||
--auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log")
|
||||
--rules value Path to the rule file to auto-authorize requests with
|
||||
--stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when Clef is started by an external process.
|
||||
--stdio-ui-test Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.
|
||||
--advanced If enabled, issues warnings instead of rejections for suspicious requests. Default off
|
||||
--suppress-bootwarn If set, does not show the warning during boot
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ clef -keystore /my/keystore -chainid 4
|
||||
```
|
||||
|
||||
## Security model
|
||||
|
||||
The security model of Clef is as follows:
|
||||
|
||||
* One critical component (the Clef binary / daemon) is responsible for handling cryptographic operations: signing, private keys, encryption/decryption of keystore files.
|
||||
* Clef has a well-defined 'external' API.
|
||||
* The 'external' API is considered UNTRUSTED.
|
||||
* Clef also communicates with whatever process that invoked the binary, via stdin/stdout.
|
||||
* This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated.
|
||||
|
||||
The general flow for signing a transaction using e.g. Geth is as follows:
|
||||

|
||||
|
||||
In this case, `geth` would be started with `--signer http://localhost:8550` and would relay requests to `eth.sendTransaction`.
|
||||
|
||||
## TODOs
|
||||
|
||||
Some snags and todos
|
||||
|
||||
* [ ] Clef should take a startup param "--no-change", for UIs that do not contain the capability to perform changes to things, only approve/deny. Such a UI should be able to start the signer in a more secure mode by telling it that it only wants approve/deny capabilities.
|
||||
* [x] It would be nice if Clef could collect new 4byte-id:s/method selectors, and have a secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for inclusion upstream.
|
||||
* [ ] It should be possible to configure Clef to check if an account is indeed known to it, before passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate accounts if it immediately returned "unknown account" (side channel attack).
|
||||
* [x] It should be possible to configure Clef to auto-allow listing (certain) accounts, instead of asking every time.
|
||||
* [x] Done Upon startup, Clef should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), invoking methods with the following info:
|
||||
* [x] Version info about the signer
|
||||
* [x] Address of API (HTTP/IPC)
|
||||
* [ ] List of known accounts
|
||||
* [ ] Have a default timeout on signing operations, so that if the user has not answered within e.g. 60 seconds, the request is rejected.
|
||||
* [ ] `account_signRawTransaction`
|
||||
* [ ] `account_bulkSignTransactions([] transactions)` should
|
||||
* only exist if enabled via config/flag
|
||||
* only allow non-data-sending transactions
|
||||
* all txs must use the same `from`-account
|
||||
* let the user confirm, showing
|
||||
* the total amount
|
||||
* the number of unique recipients
|
||||
|
||||
* Geth todos
|
||||
- The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is put together is a bit of a hack into the HTTP server. This could probably be greatly improved.
|
||||
- Relay: Geth should be started in `geth --signer localhost:8550`.
|
||||
- Currently, the Geth APIs use `common.Address` in the arguments to transaction submission (e.g `to` field). This type is 20 `bytes`, and is incapable of carrying checksum information. The signer uses `common.MixedcaseAddress`, which retains the original input.
|
||||
- The Geth API should switch to use the same type, and relay `to`-account verbatim to the external API.
|
||||
* [x] Storage
|
||||
* [x] An encrypted key-value storage should be implemented.
|
||||
* See [rules.md](rules.md) for more info about this.
|
||||
* Another potential thing to introduce is pairing.
|
||||
* To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API).
|
||||
* Thus Geth/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests.
|
||||
* This feature would make the addition of rules less dangerous.
|
||||
|
||||
* Wallets / accounts. Add API methods for wallets.
|
||||
|
||||
## Communication
|
||||
|
||||
### External API
|
||||
|
||||
Clef listens to HTTP requests on `http.addr`:`http.port` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification).
|
||||
|
||||
Some of these calls can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a user decides to ignore the confirmation request.
|
||||
|
||||
The External API is **untrusted**: it does not accept credentials, nor does it expect that requests have any authority.
|
||||
|
||||
### Internal UI API
|
||||
|
||||
Clef has one native console-based UI, for operation without any standalone tools. However, there is also an API to communicate with an external UI. To enable that UI, the signer needs to be executed with the `--stdio-ui` option, which allocates `stdin` / `stdout` for the UI API.
|
||||
|
||||
An example (insecure) proof-of-concept has been implemented in `pythonsigner.py`.
|
||||
|
||||
The model is as follows:
|
||||
|
||||
* The user starts the UI app (`pythonsigner.py`).
|
||||
* The UI app starts `clef` with `--stdio-ui`, and listens to the
|
||||
process output for confirmation-requests.
|
||||
* `clef` opens the external HTTP API.
|
||||
* When the `signer` receives requests, it sends a JSON-RPC request via `stdout`.
|
||||
* The UI app prompts the user accordingly, and responds to `clef`.
|
||||
* `clef` signs (or not), and responds to the original request.
|
||||
|
||||
## External API
|
||||
|
||||
See the [external API changelog](extapi_changelog.md) for information about changes to this API.
|
||||
|
||||
### Encoding
|
||||
- number: positive integers that are hex encoded
|
||||
- data: hex encoded data
|
||||
- string: ASCII string
|
||||
|
||||
All hex encoded values must be prefixed with `0x`.
|
||||
|
||||
### account_new
|
||||
|
||||
#### Create new password protected account
|
||||
|
||||
The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/) and store it in the keystore directory.
|
||||
The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts.
|
||||
|
||||
#### Arguments
|
||||
|
||||
None
|
||||
|
||||
#### Result
|
||||
- address [string]: account address that is derived from the generated key
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 0,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_new",
|
||||
"params": []
|
||||
}
|
||||
```
|
||||
Response
|
||||
```json
|
||||
{
|
||||
"id": 0,
|
||||
"jsonrpc": "2.0",
|
||||
"result": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133"
|
||||
}
|
||||
```
|
||||
|
||||
### account_list
|
||||
|
||||
#### List available accounts
|
||||
List all accounts that this signer currently manages
|
||||
|
||||
#### Arguments
|
||||
|
||||
None
|
||||
|
||||
#### Result
|
||||
- array with account records:
|
||||
- account.address [string]: account address that is derived from the generated key
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_list"
|
||||
}
|
||||
```
|
||||
Response
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": [
|
||||
"0xafb2f771f58513609765698f65d3f2f0224a956f",
|
||||
"0xbea9183f8f4f03d427f6bcea17388bdff1cab133"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### account_signTransaction
|
||||
|
||||
#### Sign transactions
|
||||
Signs a transaction and responds with the signed transaction in RLP-encoded and JSON forms.
|
||||
|
||||
#### Arguments
|
||||
1. transaction object:
|
||||
- `from` [address]: account to send the transaction from
|
||||
- `to` [address]: receiver account. If omitted or `0x`, will cause contract creation.
|
||||
- `gas` [number]: maximum amount of gas to burn
|
||||
- `gasPrice` [number]: gas price
|
||||
- `value` [number:optional]: amount of Wei to send with the transaction
|
||||
- `data` [data:optional]: input data
|
||||
- `nonce` [number]: account nonce
|
||||
2. method signature [string:optional]
|
||||
- The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected.
|
||||
|
||||
|
||||
#### Result
|
||||
- raw [data]: signed transaction in RLP encoded form
|
||||
- tx [json]: signed transaction in JSON form
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_signTransaction",
|
||||
"params": [
|
||||
{
|
||||
"from": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
|
||||
"gas": "0x55555",
|
||||
"gasPrice": "0x1234",
|
||||
"input": "0xabcd",
|
||||
"nonce": "0x0",
|
||||
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"value": "0x1234"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Response
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||
"tx": {
|
||||
"nonce": "0x0",
|
||||
"gasPrice": "0x1234",
|
||||
"gas": "0x55555",
|
||||
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"value": "0x1234",
|
||||
"input": "0xabcd",
|
||||
"v": "0x26",
|
||||
"r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e",
|
||||
"s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||
"hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Sample call with ABI-data
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 67,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_signTransaction",
|
||||
"params": [
|
||||
{
|
||||
"from": "0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||
"gas": "0x333",
|
||||
"gasPrice": "0x1",
|
||||
"nonce": "0x0",
|
||||
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"value": "0x0",
|
||||
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"
|
||||
},
|
||||
"safeSend(address)"
|
||||
]
|
||||
}
|
||||
```
|
||||
Response
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 67,
|
||||
"result": {
|
||||
"raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||
"tx": {
|
||||
"nonce": "0x0",
|
||||
"gasPrice": "0x1",
|
||||
"gas": "0x333",
|
||||
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"value": "0x0",
|
||||
"input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||
"v": "0x26",
|
||||
"r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e",
|
||||
"s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||
"hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bash example:
|
||||
```bash
|
||||
> curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||
|
||||
{"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}}
|
||||
```
|
||||
|
||||
### account_signData
|
||||
|
||||
#### Sign data
|
||||
Signs a chunk of data and returns the calculated signature.
|
||||
|
||||
#### Arguments
|
||||
- content type [string]: type of signed data
|
||||
- `text/validator`: hex data with a custom validator defined in a contract
|
||||
- `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers
|
||||
- `text/plain`: simple hex data validated by `account_ecRecover`
|
||||
- account [address]: account to sign with
|
||||
- data [object]: data to sign
|
||||
|
||||
#### Result
|
||||
- calculated signature [data]
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_signData",
|
||||
"params": [
|
||||
"data/plain",
|
||||
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
|
||||
"0xaabbccdd"
|
||||
]
|
||||
}
|
||||
```
|
||||
Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"jsonrpc": "2.0",
|
||||
"result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
|
||||
}
|
||||
```
|
||||
|
||||
### account_signTypedData
|
||||
|
||||
#### Sign data
|
||||
Signs a chunk of structured data conformant to [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and returns the calculated signature.
|
||||
|
||||
#### Arguments
|
||||
- account [address]: account to sign with
|
||||
- data [object]: data to sign
|
||||
|
||||
#### Result
|
||||
- calculated signature [data]
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 68,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_signTypedData",
|
||||
"params": [
|
||||
"0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
|
||||
{
|
||||
"types": {
|
||||
"EIP712Domain": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "chainId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "verifyingContract",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"Person": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "wallet",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"Mail": [
|
||||
{
|
||||
"name": "from",
|
||||
"type": "Person"
|
||||
},
|
||||
{
|
||||
"name": "to",
|
||||
"type": "Person"
|
||||
},
|
||||
{
|
||||
"name": "contents",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"primaryType": "Mail",
|
||||
"domain": {
|
||||
"name": "Ether Mail",
|
||||
"version": "1",
|
||||
"chainId": 1,
|
||||
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
|
||||
},
|
||||
"message": {
|
||||
"from": {
|
||||
"name": "Cow",
|
||||
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
|
||||
},
|
||||
"to": {
|
||||
"name": "Bob",
|
||||
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
|
||||
},
|
||||
"contents": "Hello, Bob!"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
|
||||
}
|
||||
```
|
||||
|
||||
### account_ecRecover
|
||||
|
||||
#### Recover the signing address
|
||||
|
||||
Derive the address from the account that was used to sign data with content type `text/plain` and the signature.
|
||||
|
||||
#### Arguments
|
||||
- data [data]: data that was signed
|
||||
- signature [data]: the signature to verify
|
||||
|
||||
#### Result
|
||||
- derived account [address]
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 4,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_ecRecover",
|
||||
"params": [
|
||||
"0xaabbccdd",
|
||||
"0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
|
||||
]
|
||||
}
|
||||
```
|
||||
Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 4,
|
||||
"jsonrpc": "2.0",
|
||||
"result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db"
|
||||
}
|
||||
```
|
||||
|
||||
### account_version
|
||||
|
||||
#### Get external API version
|
||||
|
||||
Get the version of the external API used by Clef.
|
||||
|
||||
#### Arguments
|
||||
|
||||
None
|
||||
|
||||
#### Result
|
||||
|
||||
* external API version [string]
|
||||
|
||||
#### Sample call
|
||||
```json
|
||||
{
|
||||
"id": 0,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_version",
|
||||
"params": []
|
||||
}
|
||||
```
|
||||
|
||||
Response
|
||||
```json
|
||||
{
|
||||
"id": 0,
|
||||
"jsonrpc": "2.0",
|
||||
"result": "6.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
## UI API
|
||||
|
||||
These methods needs to be implemented by a UI listener.
|
||||
|
||||
By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with
|
||||
denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented.
|
||||
See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'.
|
||||
|
||||
All methods in this API use object-based parameters, so that there can be no mixup of parameters: each piece of data is accessed by key.
|
||||
|
||||
See the [ui API changelog](intapi_changelog.md) for information about changes to this API.
|
||||
|
||||
OBS! A slight deviation from `json` standard is in place: every request and response should be confined to a single line.
|
||||
Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make
|
||||
things simpler for both parties.
|
||||
|
||||
### ApproveTx / `ui_approveTx`
|
||||
|
||||
Invoked when there's a transaction for approval.
|
||||
|
||||
|
||||
#### Sample call
|
||||
|
||||
Here's a method invocation:
|
||||
```bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||
```
|
||||
Results in the following invocation on the UI:
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "ui_approveTx",
|
||||
"params": [
|
||||
{
|
||||
"transaction": {
|
||||
"from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||
"to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"gas": "0x333",
|
||||
"gasPrice": "0x1",
|
||||
"value": "0x0",
|
||||
"nonce": "0x0",
|
||||
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||
"input": null
|
||||
},
|
||||
"call_info": [
|
||||
{
|
||||
"type": "WARNING",
|
||||
"message": "Invalid checksum on to-address"
|
||||
},
|
||||
{
|
||||
"type": "Info",
|
||||
"message": "safeSend(address: 0x0000000000000000000000000000000000000012)"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "127.0.0.1:48486",
|
||||
"local": "localhost:8550",
|
||||
"scheme": "HTTP/1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The same method invocation, but with invalid data:
|
||||
```bash
|
||||
|
||||
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000002000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||
```
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "ui_approveTx",
|
||||
"params": [
|
||||
{
|
||||
"transaction": {
|
||||
"from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||
"to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"gas": "0x333",
|
||||
"gasPrice": "0x1",
|
||||
"value": "0x0",
|
||||
"nonce": "0x0",
|
||||
"data": "0x4401a6e40000000000000002000000000000000000000000000000000000000000000012",
|
||||
"input": null
|
||||
},
|
||||
"call_info": [
|
||||
{
|
||||
"type": "WARNING",
|
||||
"message": "Invalid checksum on to-address"
|
||||
},
|
||||
{
|
||||
"type": "WARNING",
|
||||
"message": "Transaction data did not match ABI-interface: WARNING: Supplied data is stuffed with extra data. \nWant 0000000000000002000000000000000000000000000000000000000000000012\nHave 0000000000000000000000000000000000000000000000000000000000000012\nfor method safeSend(address)"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "127.0.0.1:48492",
|
||||
"local": "localhost:8550",
|
||||
"scheme": "HTTP/1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
One which has missing `to`, but with no `data`:
|
||||
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "ui_approveTx",
|
||||
"params": [
|
||||
{
|
||||
"transaction": {
|
||||
"from": "",
|
||||
"to": null,
|
||||
"gas": "0x0",
|
||||
"gasPrice": "0x0",
|
||||
"value": "0x0",
|
||||
"nonce": "0x0",
|
||||
"data": null,
|
||||
"input": null
|
||||
},
|
||||
"call_info": [
|
||||
{
|
||||
"type": "CRITICAL",
|
||||
"message": "Tx will create contract with empty code!"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "signer binary",
|
||||
"local": "main",
|
||||
"scheme": "in-proc"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### ApproveListing / `ui_approveListing`
|
||||
|
||||
Invoked when a request for account listing has been made.
|
||||
|
||||
#### Sample call
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"method": "ui_approveListing",
|
||||
"params": [
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42",
|
||||
"address": "0x123409812340981234098123409812deadbeef42"
|
||||
},
|
||||
{
|
||||
"url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42",
|
||||
"address": "0xcafebabedeadbeef34098123409812deadbeef42"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "signer binary",
|
||||
"local": "main",
|
||||
"scheme": "in-proc"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
### ApproveSignData / `ui_approveSignData`
|
||||
|
||||
#### Sample call
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "ui_approveSignData",
|
||||
"params": [
|
||||
{
|
||||
"address": "0x123409812340981234098123409812deadbeef42",
|
||||
"raw_data": "0x01020304",
|
||||
"messages": [
|
||||
{
|
||||
"name": "message",
|
||||
"value": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004",
|
||||
"type": "text/plain"
|
||||
}
|
||||
],
|
||||
"hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310",
|
||||
"meta": {
|
||||
"remote": "signer binary",
|
||||
"local": "main",
|
||||
"scheme": "in-proc"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### ApproveNewAccount / `ui_approveNewAccount`
|
||||
|
||||
Invoked when a request for creating a new account has been made.
|
||||
|
||||
#### Sample call
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "ui_approveNewAccount",
|
||||
"params": [
|
||||
{
|
||||
"meta": {
|
||||
"remote": "signer binary",
|
||||
"local": "main",
|
||||
"scheme": "in-proc"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### ShowInfo / `ui_showInfo`
|
||||
|
||||
The UI should show the info (a single message) to the user. Does not expect response.
|
||||
|
||||
#### Sample call
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 9,
|
||||
"method": "ui_showInfo",
|
||||
"params": [
|
||||
"Tests completed"
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### ShowError / `ui_showError`
|
||||
|
||||
The UI should show the error (a single message) to the user. Does not expect response.
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "ui_showError",
|
||||
"params": [
|
||||
"Something bad happened!"
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### OnApprovedTx / `ui_onApprovedTx`
|
||||
|
||||
`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions.
|
||||
|
||||
When implementing rate-limited rules, this callback should be used.
|
||||
|
||||
TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`.
|
||||
|
||||
Example call:
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "ui_onApprovedTx",
|
||||
"params": [
|
||||
{
|
||||
"raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||
"tx": {
|
||||
"nonce": "0x0",
|
||||
"gasPrice": "0x1",
|
||||
"gas": "0x333",
|
||||
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"value": "0x0",
|
||||
"input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||
"v": "0x26",
|
||||
"r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e",
|
||||
"s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||
"hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### OnSignerStartup / `ui_onSignerStartup`
|
||||
|
||||
This method provides the UI with information about what API version the signer uses (both internal and external) as well as build-info and external API,
|
||||
in k/v-form.
|
||||
|
||||
Example call:
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "ui_onSignerStartup",
|
||||
"params": [
|
||||
{
|
||||
"info": {
|
||||
"extapi_http": "http://localhost:8550",
|
||||
"extapi_ipc": null,
|
||||
"extapi_version": "2.0.0",
|
||||
"intapi_version": "1.2.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### OnInputRequired / `ui_onInputRequired`
|
||||
|
||||
Invoked when Clef requires user input (e.g. a password).
|
||||
|
||||
Example call:
|
||||
```json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "ui_onInputRequired",
|
||||
"params": [
|
||||
{
|
||||
"title": "Account password",
|
||||
"prompt": "Please enter the password for account 0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||
"isPassword": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Rules for UI apis
|
||||
|
||||
A UI should conform to the following rules.
|
||||
|
||||
* A UI MUST NOT load any external resources that were not embedded/part of the UI package.
|
||||
* For example, not load icons, stylesheets from the internet
|
||||
* Not load files from the filesystem, unless they reside in the same local directory (e.g. config files)
|
||||
* A Graphical UI MUST show the blocky-identicon for ethereum addresses.
|
||||
* A UI MUST warn display appropriate warning if the destination-account is formatted with invalid checksum.
|
||||
* A UI MUST NOT open any ports or services
|
||||
* The signer opens the public port
|
||||
* A UI SHOULD verify the permissions on the signer binary, and refuse to execute or warn if permissions allow non-user write.
|
||||
* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed
|
||||
* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts
|
||||
* The signer provides accounts
|
||||
* A UI SHOULD, to the best extent possible, use static linking / bundling, so that required libraries are bundled
|
||||
along with the UI.
|
||||
|
||||
|
||||
### UI Implementations
|
||||
|
||||
There are a couple of implementation for a UI. We'll try to keep this list up to date.
|
||||
|
||||
| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters|
|
||||
| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- |
|
||||
| QtSigner| https://github.com/holiman/qtsigner/ | Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
||||
| GtkSigner| https://github.com/holiman/gtksigner | Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
||||
| Frame | https://github.com/floating/frame/commits/go-signer | Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
||||
| Clef UI| https://github.com/ethereum/clef-ui | Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)|
|
||||
121
cmd/clef/consolecmd_test.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestImportRaw tests clef --importraw
|
||||
func TestImportRaw(t *testing.T) {
|
||||
t.Parallel()
|
||||
keyPath := filepath.Join(t.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
||||
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
||||
|
||||
t.Run("happy-path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Run clef importraw
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||
clef.input("myverylongpassword").input("myverylongpassword")
|
||||
if out := string(clef.Output()); !strings.Contains(out,
|
||||
"Key imported:\n Address 0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6") {
|
||||
t.Logf("Output\n%v", out)
|
||||
t.Error("Failure")
|
||||
}
|
||||
})
|
||||
// tests clef --importraw with mismatched passwords.
|
||||
t.Run("pw-mismatch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Run clef importraw
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||
clef.input("myverylongpassword1").input("myverylongpassword2").WaitExit()
|
||||
if have, want := clef.StderrText(), "Passwords do not match\n"; have != want {
|
||||
t.Errorf("have %q, want %q", have, want)
|
||||
}
|
||||
})
|
||||
// tests clef --importraw with a too short password.
|
||||
t.Run("short-pw", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Run clef importraw
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||
clef.input("shorty").input("shorty").WaitExit()
|
||||
if have, want := clef.StderrText(),
|
||||
"password requirements not met: password too short (<10 characters)\n"; have != want {
|
||||
t.Errorf("have %q, want %q", have, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestListAccounts tests clef --list-accounts
|
||||
func TestListAccounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
keyPath := filepath.Join(t.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
||||
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
||||
|
||||
t.Run("no-accounts", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-accounts")
|
||||
if out := string(clef.Output()); !strings.Contains(out, "The keystore is empty.") {
|
||||
t.Logf("Output\n%v", out)
|
||||
t.Error("Failure")
|
||||
}
|
||||
})
|
||||
t.Run("one-account", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// First, we need to import
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
|
||||
// Secondly, do a listing, using the same datadir
|
||||
clef = runWithKeystore(t, clef.Datadir, "--suppress-bootwarn", "--lightkdf", "list-accounts")
|
||||
if out := string(clef.Output()); !strings.Contains(out, "0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6 (keystore:") {
|
||||
t.Logf("Output\n%v", out)
|
||||
t.Error("Failure")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestListWallets tests clef --list-wallets
|
||||
func TestListWallets(t *testing.T) {
|
||||
t.Parallel()
|
||||
keyPath := filepath.Join(t.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name()))
|
||||
os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777)
|
||||
|
||||
t.Run("no-accounts", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-wallets")
|
||||
if out := string(clef.Output()); !strings.Contains(out, "There are no wallets.") {
|
||||
t.Logf("Output\n%v", out)
|
||||
t.Error("Failure")
|
||||
}
|
||||
})
|
||||
t.Run("one-account", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// First, we need to import
|
||||
clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath)
|
||||
clef.input("myverylongpassword").input("myverylongpassword").WaitExit()
|
||||
// Secondly, do a listing, using the same datadir
|
||||
clef = runWithKeystore(t, clef.Datadir, "--suppress-bootwarn", "--lightkdf", "list-wallets")
|
||||
if out := string(clef.Output()); !strings.Contains(out, "Account 0: 0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6") {
|
||||
t.Logf("Output\n%v", out)
|
||||
t.Error("Failure")
|
||||
}
|
||||
})
|
||||
}
|
||||
224
cmd/clef/datatypes.md
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
## UI Client interface
|
||||
|
||||
These data types are defined in the channel between clef and the UI
|
||||
### SignDataRequest
|
||||
|
||||
SignDataRequest contains information about a pending request to sign some data. The data to be signed can be of various types, defined by content-type. Clef has done most of the work in canonicalizing and making sense of the data, and it's up to the UI to present the user with the contents of the `message`
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"content_type": "text/plain",
|
||||
"address": "0xDEADbEeF000000000000000000000000DeaDbeEf",
|
||||
"raw_data": "GUV0aGVyZXVtIFNpZ25lZCBNZXNzYWdlOgoxMWhlbGxvIHdvcmxk",
|
||||
"messages": [
|
||||
{
|
||||
"name": "message",
|
||||
"value": "\u0019Ethereum Signed Message:\n11hello world",
|
||||
"type": "text/plain"
|
||||
}
|
||||
],
|
||||
"hash": "0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68",
|
||||
"meta": {
|
||||
"remote": "localhost:9999",
|
||||
"local": "localhost:8545",
|
||||
"scheme": "http",
|
||||
"User-Agent": "Firefox 3.2",
|
||||
"Origin": "www.malicious.ru"
|
||||
}
|
||||
}
|
||||
```
|
||||
### SignDataResponse - approve
|
||||
|
||||
Response to SignDataRequest
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"approved": true
|
||||
}
|
||||
```
|
||||
### SignDataResponse - deny
|
||||
|
||||
Response to SignDataRequest
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"approved": false
|
||||
}
|
||||
```
|
||||
### SignTxRequest
|
||||
|
||||
SignTxRequest contains information about a pending request to sign a transaction. Aside from the transaction itself, there is also a `call_info`-struct. That struct contains messages of various types, that the user should be informed of.
|
||||
|
||||
As in any request, it's important to consider that the `meta` info also contains untrusted data.
|
||||
|
||||
The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, they must be identical, otherwise an error is generated. However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"transaction": {
|
||||
"from": "0xDEADbEeF000000000000000000000000DeaDbeEf",
|
||||
"to": null,
|
||||
"gas": "0x3e8",
|
||||
"gasPrice": "0x5",
|
||||
"value": "0x6",
|
||||
"nonce": "0x1",
|
||||
"data": "0x01020304"
|
||||
},
|
||||
"call_info": [
|
||||
{
|
||||
"type": "Warning",
|
||||
"message": "Something looks odd, show this message as a warning"
|
||||
},
|
||||
{
|
||||
"type": "Info",
|
||||
"message": "User should see this as well"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "localhost:9999",
|
||||
"local": "localhost:8545",
|
||||
"scheme": "http",
|
||||
"User-Agent": "Firefox 3.2",
|
||||
"Origin": "www.malicious.ru"
|
||||
}
|
||||
}
|
||||
```
|
||||
### SignTxResponse - approve
|
||||
|
||||
Response to request to sign a transaction. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"transaction": {
|
||||
"from": "0xDEADbEeF000000000000000000000000DeaDbeEf",
|
||||
"to": null,
|
||||
"gas": "0x3e8",
|
||||
"gasPrice": "0x5",
|
||||
"value": "0x6",
|
||||
"nonce": "0x4",
|
||||
"data": "0x04030201"
|
||||
},
|
||||
"approved": true
|
||||
}
|
||||
```
|
||||
### SignTxResponse - deny
|
||||
|
||||
Response to SignTxRequest. When denying a request, there's no need to provide the transaction in return
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"transaction": {
|
||||
"from": "0x",
|
||||
"to": null,
|
||||
"gas": "0x0",
|
||||
"gasPrice": "0x0",
|
||||
"value": "0x0",
|
||||
"nonce": "0x0",
|
||||
"data": null
|
||||
},
|
||||
"approved": false
|
||||
}
|
||||
```
|
||||
### OnApproved - SignTransactionResult
|
||||
|
||||
SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`
|
||||
|
||||
This occurs _after_ successful completion of the entire signing procedure, but right before the signed transaction is passed to the external caller. This method (and data) can be used by the UI to signal to the user that the transaction was signed, but it is primarily useful for ruleset implementations.
|
||||
|
||||
A ruleset that implements a rate limitation needs to know what transactions are sent out to the external interface. By hooking into this methods, the ruleset can maintain track of that count.
|
||||
|
||||
**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content.
|
||||
|
||||
The `OnApproved` method cannot be responded to, it's purely informative
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"raw": "0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed",
|
||||
"tx": {
|
||||
"nonce": "0x64",
|
||||
"gasPrice": "0x1",
|
||||
"gas": "0x1",
|
||||
"to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192",
|
||||
"value": "0x1",
|
||||
"input": "0x",
|
||||
"v": "0x26",
|
||||
"r": "0x716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293",
|
||||
"s": "0x4e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed",
|
||||
"hash": "0x662f6d772692dd692f1b5e8baa77a9ff95bbd909362df3fc3d301aafebde5441"
|
||||
}
|
||||
}
|
||||
```
|
||||
### UserInputRequest
|
||||
|
||||
Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"prompt": "The question to ask the user",
|
||||
"title": "The title here",
|
||||
"isPassword": true
|
||||
}
|
||||
```
|
||||
### UserInputResponse
|
||||
|
||||
Response to UserInputRequest
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"text": "The textual response from user"
|
||||
}
|
||||
```
|
||||
### ListRequest
|
||||
|
||||
Sent when a request has been made to list addresses. The UI is provided with the full `account`s, including local directory names. Note: this information is not passed back to the external caller, who only sees the `address`es.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"address": "0xdeadbeef000000000000000000000000deadbeef",
|
||||
"url": "keystore:///path/to/keyfile/a"
|
||||
},
|
||||
{
|
||||
"address": "0x1111111122222222222233333333334444444444",
|
||||
"url": "keystore:///path/to/keyfile/b"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "localhost:9999",
|
||||
"local": "localhost:8545",
|
||||
"scheme": "http",
|
||||
"User-Agent": "Firefox 3.2",
|
||||
"Origin": "www.malicious.ru"
|
||||
}
|
||||
}
|
||||
```
|
||||
### ListResponse
|
||||
|
||||
Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"address": "0x0000000000000000000000000000000000000000",
|
||||
"url": ".. ignored .."
|
||||
},
|
||||
{
|
||||
"address": "0xffffffffffffffffffffffffffffffffffffffff",
|
||||
"url": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
BIN
cmd/clef/docs/clef_architecture_pt1.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
cmd/clef/docs/clef_architecture_pt2.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
cmd/clef/docs/clef_architecture_pt3.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
cmd/clef/docs/clef_architecture_pt4.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
cmd/clef/docs/qubes/clef_qubes_http.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
cmd/clef/docs/qubes/clef_qubes_qrexec.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
cmd/clef/docs/qubes/qrexec-example.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
23
cmd/clef/docs/qubes/qubes-client.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
This implements a dispatcher which listens to localhost:8550, and proxies
|
||||
requests via qrexec to the service qubes.EthSign on a target domain
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import socketserver,subprocess
|
||||
|
||||
PORT=8550
|
||||
TARGET_DOMAIN= 'debian-work'
|
||||
|
||||
class Dispatcher(http.server.BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
post_data = self.rfile.read(int(self.headers['Content-Length']))
|
||||
p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
output = p.communicate(post_data)[0]
|
||||
self.wfile.write(output)
|
||||
|
||||
|
||||
with socketserver.TCPServer(("",PORT), Dispatcher) as httpd:
|
||||
print("Serving at port", PORT)
|
||||
httpd.serve_forever()
|
||||
|
||||
16
cmd/clef/docs/qubes/qubes.Clefsign
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
SIGNER_BIN="/home/user/tools/clef/clef"
|
||||
SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN"
|
||||
|
||||
# Start clef if not already started
|
||||
if [ ! -S /home/user/.clef/clef.ipc ]; then
|
||||
$SIGNER_CMD &
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# Should be started by now
|
||||
if [ -S /home/user/.clef/clef.ipc ]; then
|
||||
# Post incoming request to HTTP channel
|
||||
curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null
|
||||
fi
|
||||
BIN
cmd/clef/docs/qubes/qubes_newaccount-1.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
cmd/clef/docs/qubes/qubes_newaccount-2.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
198
cmd/clef/docs/setup.md
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# Setting up Clef
|
||||
|
||||
This document describes how Clef can be used in a more secure manner than executing it from your everyday laptop,
|
||||
in order to ensure that the keys remain safe in the event that your computer should get compromised.
|
||||
|
||||
## Qubes OS
|
||||
|
||||
|
||||
### Background
|
||||
|
||||
The Qubes operating system is based around virtual machines (qubes), where a set of virtual machines are configured, typically for
|
||||
different purposes such as:
|
||||
|
||||
- personal
|
||||
- Your personal email, browsing etc
|
||||
- work
|
||||
- Work email etc
|
||||
- vault
|
||||
- a VM without network access, where gpg-keys and/or keepass credentials are stored.
|
||||
|
||||
A couple of dedicated virtual machines handle externalities:
|
||||
|
||||
- sys-net provides networking to all other (network-enabled) machines
|
||||
- sys-firewall handles firewall rules
|
||||
- sys-usb handles USB devices, and can map usb-devices to certain qubes.
|
||||
|
||||
The goal of this document is to describe how we can set up clef to provide secure transaction
|
||||
signing from a `vault` vm, to another networked qube which runs Dapps.
|
||||
|
||||
### Setup
|
||||
|
||||
There are two ways that this can be achieved: integrated via Qubes or integrated via networking.
|
||||
|
||||
|
||||
#### 1. Qubes Integrated
|
||||
|
||||
Qubes provides a facility for inter-qubes communication via `qrexec`. A qube can request to make a cross-qube RPC request
|
||||
to another qube. The OS then asks the user if the call is permitted.
|
||||
|
||||

|
||||
|
||||
A policy-file can be created to allow such interaction. On the `target` domain, a service is invoked which can read the
|
||||
`stdin` from the `client` qube.
|
||||
|
||||
This is how [Split GPG](https://www.qubes-os.org/doc/split-gpg/) is implemented. We can set up Clef the same way:
|
||||
|
||||
##### Server
|
||||
|
||||

|
||||
|
||||
On the `target` qubes, we need to define the RPC service.
|
||||
|
||||
[qubes.Clefsign](qubes/qubes.Clefsign):
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
SIGNER_BIN="/home/user/tools/clef/clef"
|
||||
SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN"
|
||||
|
||||
# Start clef if not already started
|
||||
if [ ! -S /home/user/.clef/clef.ipc ]; then
|
||||
$SIGNER_CMD &
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# Should be started by now
|
||||
if [ -S /home/user/.clef/clef.ipc ]; then
|
||||
# Post incoming request to HTTP channel
|
||||
curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null
|
||||
fi
|
||||
|
||||
```
|
||||
This RPC service is not complete (see notes about HTTP headers below), but works as a proof-of-concept.
|
||||
It will forward the data received on `stdin` (forwarded by the OS) to Clef's HTTP channel.
|
||||
|
||||
It would have been possible to send data directly to the `/home/user/.clef/.clef.ipc`
|
||||
socket via e.g `nc -U /home/user/.clef/clef.ipc`, but the reason for sending the request
|
||||
data over `HTTP` instead of `IPC` is that we want the ability to forward `HTTP` headers.
|
||||
|
||||
To enable the service:
|
||||
|
||||
``` bash
|
||||
sudo cp qubes.Clefsign /etc/qubes-rpc/
|
||||
sudo chmod +x /etc/qubes-rpc/ qubes.Clefsign
|
||||
```
|
||||
|
||||
This setup uses [gtksigner](https://github.com/holiman/gtksigner), which is a very minimal GTK-based UI that works well
|
||||
with minimal requirements.
|
||||
|
||||
##### Client
|
||||
|
||||
|
||||
On the `client` qube, we need to create a listener which will receive the request from the Dapp, and proxy it.
|
||||
|
||||
|
||||
[qubes-client.py](qubes/qubes-client.py):
|
||||
|
||||
```python
|
||||
|
||||
"""
|
||||
This implements a dispatcher which listens to localhost:8550, and proxies
|
||||
requests via qrexec to the service qubes.EthSign on a target domain
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import socketserver,subprocess
|
||||
|
||||
PORT=8550
|
||||
TARGET_DOMAIN= 'debian-work'
|
||||
|
||||
class Dispatcher(http.server.BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
post_data = self.rfile.read(int(self.headers['Content-Length']))
|
||||
p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
output = p.communicate(post_data)[0]
|
||||
self.wfile.write(output)
|
||||
|
||||
|
||||
with socketserver.TCPServer(("",PORT), Dispatcher) as httpd:
|
||||
print("Serving at port", PORT)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
```
|
||||
|
||||
#### Testing
|
||||
|
||||
To test the flow, if we have set up `debian-work` as the `target`, we can do
|
||||
|
||||
```bash
|
||||
$ cat newaccnt.json
|
||||
{ "id": 0, "jsonrpc": "2.0","method": "account_new","params": []}
|
||||
|
||||
$ cat newaccnt.json| qrexec-client-vm debian-work qubes.Clefsign
|
||||
```
|
||||
|
||||
A dialog should pop up first to allow the IPC call:
|
||||
|
||||

|
||||
|
||||
Followed by a GTK-dialog to approve the operation:
|
||||
|
||||

|
||||
|
||||
To test the full flow, we use the client wrapper. Start it on the `client` qube:
|
||||
```
|
||||
[user@work qubes]$ python3 qubes-client.py
|
||||
```
|
||||
|
||||
Make the request over http (`client` qube):
|
||||
```
|
||||
[user@work clef]$ cat newaccnt.json | curl -X POST -d @- http://localhost:8550
|
||||
```
|
||||
And it should show the same popups again.
|
||||
|
||||
##### Pros and cons
|
||||
|
||||
The benefits of this setup are:
|
||||
|
||||
- This is the qubes-os intended model for inter-qube communication,
|
||||
- and thus benefits from qubes-os dialogs and policies for user approval
|
||||
|
||||
However, it comes with a couple of drawbacks:
|
||||
|
||||
- The `qubes-gpg-client` must forward the http request via RPC to the `target` qube. When doing so, the proxy
|
||||
will either drop important headers, or replace them.
|
||||
- The `Host` header is most likely `localhost`
|
||||
- The `Origin` header must be forwarded
|
||||
- Information about the remote ip must be added as a `X-Forwarded-For`. However, Clef cannot always trust an `XFF` header,
|
||||
since malicious clients may lie about `XFF` in order to fool the http server into believing it comes from another address.
|
||||
- Even with a policy in place to allow RPC calls between `caller` and `target`, there will be several popups:
|
||||
- One qubes-specific where the user specifies the `target` vm
|
||||
- One clef-specific to approve the transaction
|
||||
|
||||
|
||||
#### 2. Network integrated
|
||||
|
||||
The second way to set up Clef on a qubes system is to allow networking, and have Clef listen to a port which is accessible
|
||||
from other qubes.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
## USBArmory
|
||||
|
||||
The [USB armory](https://inversepath.com/usbarmory) is an open source hardware design with an 800 MHz ARM processor. It is a pocket-size
|
||||
computer. When inserted into a laptop, it identifies itself as a USB network interface, basically adding another network
|
||||
to your computer. Over this new network interface, you can SSH into the device.
|
||||
|
||||
Running Clef off a USB armory means that you can use the armory as a very versatile offline computer, which only
|
||||
ever connects to a local network between your computer and the device itself.
|
||||
|
||||
Needless to say, while this model should be fairly secure against remote attacks, an attacker with physical access
|
||||
to the USB Armory would trivially be able to extract the contents of the device filesystem.
|
||||
|
||||
104
cmd/clef/extapi_changelog.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
## Changelog for external API
|
||||
|
||||
The API uses [semantic versioning](https://semver.org/).
|
||||
|
||||
TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
* MAJOR version when you make incompatible API changes,
|
||||
* MINOR version when you add functionality in a backwards-compatible manner, and
|
||||
* PATCH version when you make backwards-compatible bug fixes.
|
||||
|
||||
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
|
||||
|
||||
### 6.1.0
|
||||
|
||||
The API-method `account_signGnosisSafeTx` was added. This method takes two parameters,
|
||||
`[address, safeTx]`. The latter, `safeTx`, can be copy-pasted from the gnosis relay. For example:
|
||||
|
||||
```
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "account_signGnosisSafeTx",
|
||||
"params": ["0xfd1c4226bfD1c436672092F4eCbfC270145b7256",
|
||||
{
|
||||
"safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3",
|
||||
"to": "0xB372a646f7F05Cc1785018dBDA7EBc734a2A20E2",
|
||||
"value": "20000000000000000",
|
||||
"data": null,
|
||||
"operation": 0,
|
||||
"gasToken": "0x0000000000000000000000000000000000000000",
|
||||
"safeTxGas": 27845,
|
||||
"baseGas": 0,
|
||||
"gasPrice": "0",
|
||||
"refundReceiver": "0x0000000000000000000000000000000000000000",
|
||||
"nonce": 2,
|
||||
"executionDate": null,
|
||||
"submissionDate": "2020-09-15T21:54:49.617634Z",
|
||||
"modified": "2020-09-15T21:54:49.617634Z",
|
||||
"blockNumber": null,
|
||||
"transactionHash": null,
|
||||
"safeTxHash": "0x2edfbd5bc113ff18c0631595db32eb17182872d88d9bf8ee4d8c2dd5db6d95e2",
|
||||
"executor": null,
|
||||
"isExecuted": false,
|
||||
"isSuccessful": null,
|
||||
"ethGasPrice": null,
|
||||
"gasUsed": null,
|
||||
"fee": null,
|
||||
"origin": null,
|
||||
"dataDecoded": null,
|
||||
"confirmationsRequired": null,
|
||||
"confirmations": [
|
||||
{
|
||||
"owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34",
|
||||
"submissionDate": "2020-09-15T21:54:49.663299Z",
|
||||
"transactionHash": null,
|
||||
"confirmationType": "CONFIRMATION",
|
||||
"signature": "0x95a7250bb645f831c86defc847350e7faff815b2fb586282568e96cc859e39315876db20a2eed5f7a0412906ec5ab57652a6f645ad4833f345bda059b9da2b821c",
|
||||
"signatureType": "EOA"
|
||||
}
|
||||
],
|
||||
"signatures": null
|
||||
}
|
||||
],
|
||||
"id": 67
|
||||
}
|
||||
```
|
||||
|
||||
Not all fields are required, though. This method is really just a UX helper, which massages the
|
||||
input to conform to the `EIP-712` [specification](https://docs.safe.global/core-api/transaction-service-reference/gnosis)
|
||||
for the Gnosis Safe, and making the output be directly importable to by a relay service.
|
||||
|
||||
|
||||
### 6.0.0
|
||||
|
||||
* `New` was changed to deliver only an address, not the full `Account` data
|
||||
* `Export` was moved from External API to the UI Server API
|
||||
|
||||
#### 5.0.0
|
||||
|
||||
* The external `account_EcRecover`-method was reimplemented.
|
||||
* The external method `account_sign(address, data)` was replaced with `account_signData(contentType, address, data)`.
|
||||
The addition of `contentType` makes it possible to use the method for different types of objects, such as:
|
||||
* signing data with an intended validator (not yet implemented)
|
||||
* signing clique headers,
|
||||
* signing plain personal messages,
|
||||
* The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data.
|
||||
|
||||
#### 4.0.0
|
||||
|
||||
* The external `account_Ecrecover`-method was removed.
|
||||
* The external `account_Import`-method was removed.
|
||||
|
||||
#### 3.0.0
|
||||
|
||||
* The external `account_List`-method was changed to not expose `url`, which contained info about the local filesystem. It now returns only a list of addresses.
|
||||
|
||||
#### 2.0.0
|
||||
|
||||
* Commit `73abaf04b1372fa4c43201fb1b8019fe6b0a6f8d`, move `from` into `transaction` object in `signTransaction`. This
|
||||
makes the `accounts_signTransaction` identical to the old `eth_signTransaction`.
|
||||
|
||||
|
||||
#### 1.0.0
|
||||
|
||||
Initial release.
|
||||
191
cmd/clef/intapi_changelog.md
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
## Changelog for internal API (ui-api)
|
||||
|
||||
The API uses [semantic versioning](https://semver.org/).
|
||||
|
||||
TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
* MAJOR version when you make incompatible API changes,
|
||||
* MINOR version when you add functionality in a backwards-compatible manner, and
|
||||
* PATCH version when you make backwards-compatible bug fixes.
|
||||
|
||||
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
|
||||
|
||||
### 7.0.1
|
||||
|
||||
Added `clef_New` to the internal API callable from a UI.
|
||||
|
||||
> `New` creates a new password protected Account. The private key is protected with
|
||||
> the given password. Users are responsible to backup the private key that is stored
|
||||
> in the keystore location that was specified when this API was created.
|
||||
> This method is the same as New on the external API, the difference being that
|
||||
> this implementation does not ask for confirmation, since it's initiated by
|
||||
> the user
|
||||
|
||||
### 7.0.0
|
||||
|
||||
- The `message` field was renamed to `messages` in all data signing request methods to better reflect that it's a list, not a value.
|
||||
- The `storage.Put` and `storage.Get` methods in the rule execution engine were lower-cased to `storage.put` and `storage.get` to be consistent with JavaScript call conventions.
|
||||
|
||||
### 6.0.0
|
||||
|
||||
Removed `password` from responses to operations which require them. This is for two reasons,
|
||||
|
||||
- Consistency between how rulesets operate and how manual processing works. A rule can `Approve` but require the actual password to be stored in the clef storage.
|
||||
With this change, the same stored password can be used even if rulesets are not enabled, but storage is.
|
||||
- It also removes the usability-shortcut that a UI might otherwise want to implement; remembering passwords. Since we now will not require the
|
||||
password on every `Approve`, there's no need for the UI to cache it locally.
|
||||
- In a future update, we'll likely add `clef_storePassword` to the internal API, so the user can store it via his UI (currently only CLI works).
|
||||
|
||||
Affected datatypes:
|
||||
- `SignTxResponse`
|
||||
- `SignDataResponse`
|
||||
- `NewAccountResponse`
|
||||
|
||||
If `clef` requires a password, the `OnInputRequired` will be used to collect it.
|
||||
|
||||
|
||||
### 5.0.0
|
||||
|
||||
Changed the namespace format to adhere to the legacy ethereum format: `name_methodName`. Changes:
|
||||
|
||||
* `ApproveTx` -> `ui_approveTx`
|
||||
* `ApproveSignData` -> `ui_approveSignData`
|
||||
* `ApproveExport` -> `removed`
|
||||
* `ApproveImport` -> `removed`
|
||||
* `ApproveListing` -> `ui_approveListing`
|
||||
* `ApproveNewAccount` -> `ui_approveNewAccount`
|
||||
* `ShowError` -> `ui_showError`
|
||||
* `ShowInfo` -> `ui_showInfo`
|
||||
* `OnApprovedTx` -> `ui_onApprovedTx`
|
||||
* `OnSignerStartup` -> `ui_onSignerStartup`
|
||||
* `OnInputRequired` -> `ui_onInputRequired`
|
||||
|
||||
|
||||
### 4.0.0
|
||||
|
||||
* Bidirectional communication implemented, so the UI can query `clef` via the stdin/stdout RPC channel. Methods implemented are:
|
||||
- `clef_listWallets`
|
||||
- `clef_listAccounts`
|
||||
- `clef_listWallets`
|
||||
- `clef_deriveAccount`
|
||||
- `clef_importRawKey`
|
||||
- `clef_openWallet`
|
||||
- `clef_chainId`
|
||||
- `clef_setChainId`
|
||||
- `clef_export`
|
||||
- `clef_import`
|
||||
|
||||
* The type `Account` was modified (the json-field `type` was removed), to consist of
|
||||
|
||||
```go
|
||||
type Account struct {
|
||||
Address common.Address `json:"address"` // Ethereum account address derived from the key
|
||||
URL URL `json:"url"` // Optional resource locator within a backend
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3.2.0
|
||||
|
||||
* Make `ShowError`, `OnApprovedTx`, `OnSignerStartup` be json-rpc [notifications](https://www.jsonrpc.org/specification#notification):
|
||||
|
||||
> A Notification is a Request object without an "id" member. A Request object that is a Notification signifies the Client's lack of interest in the corresponding Response object, and as such no Response object needs to be returned to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
||||
>
|
||||
> Notifications are not confirmable by definition, since they do not have a Response object to be returned. As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error"
|
||||
### 3.1.0
|
||||
|
||||
* Add `ContentType` `string` to `SignDataRequest` to accommodate the latest [EIP-191](https://eips.ethereum.org/EIPS/eip-191) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) implementations.
|
||||
|
||||
### 3.0.0
|
||||
|
||||
* Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup
|
||||
|
||||
### 2.1.0
|
||||
|
||||
* Add `OnInputRequired(info UserInputRequest)` to internal API. This method is used when Clef needs user input, e.g. passwords.
|
||||
|
||||
The following structures are used:
|
||||
|
||||
```go
|
||||
UserInputRequest struct {
|
||||
Prompt string `json:"prompt"`
|
||||
Title string `json:"title"`
|
||||
IsPassword bool `json:"isPassword"`
|
||||
}
|
||||
UserInputResponse struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
```
|
||||
|
||||
### 2.0.0
|
||||
|
||||
* Modify how `call_info` on a transaction is conveyed. New format:
|
||||
|
||||
```
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "ApproveTx",
|
||||
"params": [
|
||||
{
|
||||
"transaction": {
|
||||
"from": "0x82A2A876D39022B3019932D30Cd9c97ad5616813",
|
||||
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||
"gas": "0x333",
|
||||
"gasPrice": "0x123",
|
||||
"value": "0x10",
|
||||
"nonce": "0x0",
|
||||
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||
"input": null
|
||||
},
|
||||
"call_info": [
|
||||
{
|
||||
"type": "WARNING",
|
||||
"message": "Invalid checksum on to-address"
|
||||
},
|
||||
{
|
||||
"type": "WARNING",
|
||||
"message": "Tx contains data, but provided ABI signature could not be matched: Did not match: test (0 matches)"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"remote": "127.0.0.1:54286",
|
||||
"local": "localhost:8550",
|
||||
"scheme": "HTTP/1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2.0
|
||||
|
||||
* Add `OnStartup` method, to provide the UI with information about what API version
|
||||
the signer uses (both internal and external) as well as build-info and external api.
|
||||
|
||||
Example call:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "OnSignerStartup",
|
||||
"params": [
|
||||
{
|
||||
"info": {
|
||||
"extapi_http": "http://localhost:8550",
|
||||
"extapi_ipc": null,
|
||||
"extapi_version": "2.0.0",
|
||||
"intapi_version": "1.2.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.1.0
|
||||
|
||||
* Add `OnApproved` method
|
||||
|
||||
#### 1.0.0
|
||||
|
||||
Initial release.
|
||||
1224
cmd/clef/main.go
Normal file
315
cmd/clef/pythonsigner.py
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
import sys
|
||||
import subprocess
|
||||
|
||||
from tinyrpc.transports import ServerTransport
|
||||
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
|
||||
from tinyrpc.dispatch import public, RPCDispatcher
|
||||
from tinyrpc.server import RPCServer
|
||||
|
||||
"""
|
||||
This is a POC example of how to write a custom UI for Clef.
|
||||
The UI starts the clef process with the '--stdio-ui' option
|
||||
and communicates with clef using standard input / output.
|
||||
|
||||
The standard input/output is a relatively secure way to communicate,
|
||||
as it does not require opening any ports or IPC files. Needless to say,
|
||||
it does not protect against memory inspection mechanisms
|
||||
where an attacker can access process memory.
|
||||
|
||||
To make this work install all the requirements:
|
||||
|
||||
pip install -r requirements.txt
|
||||
"""
|
||||
|
||||
try:
|
||||
import urllib.parse as urlparse
|
||||
except ImportError:
|
||||
import urllib as urlparse
|
||||
|
||||
|
||||
class StdIOTransport(ServerTransport):
|
||||
"""Uses std input/output for RPC"""
|
||||
|
||||
def receive_message(self):
|
||||
return None, urlparse.unquote(sys.stdin.readline())
|
||||
|
||||
def send_reply(self, context, reply):
|
||||
print(reply)
|
||||
|
||||
|
||||
class PipeTransport(ServerTransport):
|
||||
"""Uses std a pipe for RPC"""
|
||||
|
||||
def __init__(self, input, output):
|
||||
self.input = input
|
||||
self.output = output
|
||||
|
||||
def receive_message(self):
|
||||
data = self.input.readline()
|
||||
print(">> {}".format(data))
|
||||
return None, urlparse.unquote(data)
|
||||
|
||||
def send_reply(self, context, reply):
|
||||
reply = str(reply, "utf-8")
|
||||
print("<< {}".format(reply))
|
||||
self.output.write("{}\n".format(reply))
|
||||
|
||||
|
||||
def sanitize(txt, limit=100):
|
||||
return txt[:limit].encode("unicode_escape").decode("utf-8")
|
||||
|
||||
|
||||
def metaString(meta):
|
||||
"""
|
||||
"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"\tRequest context:\n"
|
||||
"\t\t{remote} -> {scheme} -> {local}\n"
|
||||
"\tAdditional HTTP header data, provided by the external caller:\n"
|
||||
"\t\tUser-Agent: {user_agent}\n"
|
||||
"\t\tOrigin: {origin}\n"
|
||||
)
|
||||
return message.format(
|
||||
remote=meta.get("remote", "<missing>"),
|
||||
scheme=meta.get("scheme", "<missing>"),
|
||||
local=meta.get("local", "<missing>"),
|
||||
user_agent=sanitize(meta.get("User-Agent"), 200),
|
||||
origin=sanitize(meta.get("Origin"), 100),
|
||||
)
|
||||
|
||||
|
||||
class StdIOHandler:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@public
|
||||
def approveTx(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","id":20,"method":"ui_approveTx","params":[{"transaction":{"from":"0xDEADbEeF000000000000000000000000DeaDbeEf","to":"0xDEADbEeF000000000000000000000000DeaDbeEf","gas":"0x3e8","gasPrice":"0x5","maxFeePerGas":null,"maxPriorityFeePerGas":null,"value":"0x6","nonce":"0x1","data":"0x"},"call_info":null,"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
||||
|
||||
:param transaction: transaction info
|
||||
:param call_info: info about the call, e.g. if ABI info could not be
|
||||
:param meta: metadata about the request, e.g. where the call comes from
|
||||
:return:
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"Sign transaction request:\n"
|
||||
"\t{meta_string}\n"
|
||||
"\n"
|
||||
"\tFrom: {from_}\n"
|
||||
"\tTo: {to}\n"
|
||||
"\n"
|
||||
"\tAuto-rejecting request"
|
||||
)
|
||||
meta = req.get("meta", {})
|
||||
transaction = req.get("transaction")
|
||||
sys.stdout.write(
|
||||
message.format(
|
||||
meta_string=metaString(meta),
|
||||
from_=transaction.get("from", "<missing>"),
|
||||
to=transaction.get("to", "<missing>"),
|
||||
)
|
||||
)
|
||||
return {
|
||||
"approved": False,
|
||||
}
|
||||
|
||||
@public
|
||||
def approveSignData(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","id":8,"method":"ui_approveSignData","params":[{"content_type":"application/x-clique-header","address":"0x0011223344556677889900112233445566778899","raw_data":"+QIRoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIFOYIFOYIFOoIFOoIFOppFeHRyYSBkYXRhIEV4dHJhIGRhdGEgRXh0cqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAAAAAA==","messages":[{"name":"Clique header","value":"clique header 1337 [0x44381ab449d77774874aca34634cb53bc21bd22aef2d3d4cf40e51176cb585ec]","type":"clique"}],"call_info":null,"hash":"0xa47ab61438a12a06c81420e308c2b7aae44e9cd837a5df70dd021421c0f58643","meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"Sign data request:\n"
|
||||
"\t{meta_string}\n"
|
||||
"\n"
|
||||
"\tContent-type: {content_type}\n"
|
||||
"\tAddress: {address}\n"
|
||||
"\tHash: {hash_}\n"
|
||||
"\n"
|
||||
"\tAuto-rejecting request\n"
|
||||
)
|
||||
meta = req.get("meta", {})
|
||||
sys.stdout.write(
|
||||
message.format(
|
||||
meta_string=metaString(meta),
|
||||
content_type=req.get("content_type"),
|
||||
address=req.get("address"),
|
||||
hash_=req.get("hash"),
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"approved": False,
|
||||
"password": None,
|
||||
}
|
||||
|
||||
@public
|
||||
def approveNewAccount(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","id":25,"method":"ui_approveNewAccount","params":[{"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"Create new account request:\n"
|
||||
"\t{meta_string}\n"
|
||||
"\n"
|
||||
"\tAuto-rejecting request\n"
|
||||
)
|
||||
meta = req.get("meta", {})
|
||||
sys.stdout.write(message.format(meta_string=metaString(meta)))
|
||||
return {
|
||||
"approved": False,
|
||||
}
|
||||
|
||||
@public
|
||||
def showError(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","method":"ui_showError","params":[{"text":"If you see this message, enter 'yes' to the next question"}]}
|
||||
|
||||
:param message: to display
|
||||
:return:nothing
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"## Error\n{text}\n"
|
||||
"Press enter to continue\n"
|
||||
)
|
||||
text = req.get("text")
|
||||
sys.stdout.write(message.format(text=text))
|
||||
input()
|
||||
return
|
||||
|
||||
@public
|
||||
def showInfo(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","method":"ui_showInfo","params":[{"text":"If you see this message, enter 'yes' to next question"}]}
|
||||
|
||||
:param message: to display
|
||||
:return:nothing
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"## Info\n{text}\n"
|
||||
"Press enter to continue\n"
|
||||
)
|
||||
text = req.get("text")
|
||||
sys.stdout.write(message.format(text=text))
|
||||
input()
|
||||
return
|
||||
|
||||
@public
|
||||
def onSignerStartup(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0", "method":"ui_onSignerStartup", "params":[{"info":{"extapi_http":"n/a","extapi_ipc":"/home/user/.clef/clef.ipc","extapi_version":"6.1.0","intapi_version":"7.0.1"}}]}
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"\n"
|
||||
"\t\tExt api url: {extapi_http}\n"
|
||||
"\t\tInt api ipc: {extapi_ipc}\n"
|
||||
"\t\tExt api ver: {extapi_version}\n"
|
||||
"\t\tInt api ver: {intapi_version}\n"
|
||||
)
|
||||
info = req.get("info")
|
||||
sys.stdout.write(
|
||||
message.format(
|
||||
extapi_http=info.get("extapi_http"),
|
||||
extapi_ipc=info.get("extapi_ipc"),
|
||||
extapi_version=info.get("extapi_version"),
|
||||
intapi_version=info.get("intapi_version"),
|
||||
)
|
||||
)
|
||||
|
||||
@public
|
||||
def approveListing(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","id":23,"method":"ui_approveListing","params":[{"accounts":[{"address":...
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"\n"
|
||||
"## Account listing request\n"
|
||||
"\t{meta_string}\n"
|
||||
"\tDo you want to allow listing the following accounts?\n"
|
||||
"\t-{addrs}\n"
|
||||
"\n"
|
||||
"->Auto-answering No\n"
|
||||
)
|
||||
meta = req.get("meta", {})
|
||||
accounts = req.get("accounts", [])
|
||||
addrs = [x.get("address") for x in accounts]
|
||||
sys.stdout.write(
|
||||
message.format(
|
||||
addrs="\n\t-".join(addrs),
|
||||
meta_string=metaString(meta)
|
||||
)
|
||||
)
|
||||
return {}
|
||||
|
||||
@public
|
||||
def onInputRequired(self, req):
|
||||
"""
|
||||
Example request:
|
||||
|
||||
{"jsonrpc":"2.0","id":1,"method":"ui_onInputRequired","params":[{"title":"Master Password","prompt":"Please enter the password to decrypt the master seed","isPassword":true}]}
|
||||
|
||||
:param message: to display
|
||||
:return:nothing
|
||||
""" # noqa: E501
|
||||
message = (
|
||||
"\n"
|
||||
"## {title}\n"
|
||||
"\t{prompt}\n"
|
||||
"\n"
|
||||
"> "
|
||||
)
|
||||
sys.stdout.write(
|
||||
message.format(
|
||||
title=req.get("title"),
|
||||
prompt=req.get("prompt")
|
||||
)
|
||||
)
|
||||
isPassword = req.get("isPassword")
|
||||
if not isPassword:
|
||||
return {"text": input()}
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def main(args):
|
||||
cmd = ["clef", "--stdio-ui"]
|
||||
if len(args) > 0 and args[0] == "test":
|
||||
cmd.extend(["--stdio-ui-test"])
|
||||
print("cmd: {}".format(" ".join(cmd)))
|
||||
|
||||
dispatcher = RPCDispatcher()
|
||||
dispatcher.register_instance(StdIOHandler(), "ui_")
|
||||
|
||||
# line buffered
|
||||
p = subprocess.Popen(
|
||||
cmd,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
||||
rpc_server = RPCServer(
|
||||
PipeTransport(p.stdout, p.stdin), JSONRPCProtocol(), dispatcher
|
||||
)
|
||||
rpc_server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
1
cmd/clef/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
tinyrpc==1.1.4
|
||||
234
cmd/clef/rules.md
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# Rules
|
||||
|
||||
The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto)
|
||||
|
||||
It enables use cases like the following:
|
||||
|
||||
* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period
|
||||
* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`
|
||||
|
||||
The two main features that are required for this to work well are:
|
||||
|
||||
1. Rule Implementation: how to create, manage, and interpret rules in a flexible but secure manner
|
||||
2. Credential management and credentials; how to provide auto-unlock without exposing keys unnecessarily.
|
||||
|
||||
The section below deals with both of them
|
||||
|
||||
## Rule Implementation
|
||||
|
||||
A ruleset file is implemented as a `js` file. Under the hood, the ruleset engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods
|
||||
defined in the UI protocol. Example:
|
||||
|
||||
```js
|
||||
function asBig(str) {
|
||||
if (str.slice(0, 2) == "0x") {
|
||||
return new BigNumber(str.slice(2), 16)
|
||||
}
|
||||
return new BigNumber(str)
|
||||
}
|
||||
|
||||
// Approve transactions to a certain contract if the value is below a certain limit
|
||||
function ApproveTx(req) {
|
||||
var limit = new BigNumber("0xb1a2bc2ec50000")
|
||||
var value = asBig(req.transaction.value);
|
||||
|
||||
if (req.transaction.to.toLowerCase() == "0xae967917c465db8578ca9024c205720b1a3651a9" && value.lt(limit)) {
|
||||
return "Approve"
|
||||
}
|
||||
// If we return "Reject", it will be rejected.
|
||||
// By not returning anything, it will be passed to the next UI, for manual processing
|
||||
}
|
||||
|
||||
// Approve listings if request made from IPC
|
||||
function ApproveListing(req){
|
||||
if (req.metadata.scheme == "ipc"){ return "Approve"}
|
||||
}
|
||||
```
|
||||
|
||||
Whenever the external API is called (and the ruleset is enabled), the `signer` calls the UI, which is an instance of a ruleset-engine. The ruleset-engine
|
||||
invokes the corresponding method. In doing so, there are three possible outcomes:
|
||||
|
||||
1. JS returns "Approve"
|
||||
* Auto-approve request
|
||||
2. JS returns "Reject"
|
||||
* Auto-reject request
|
||||
3. Error occurs, or something else is returned
|
||||
* Pass on to `next` ui: the regular UI channel.
|
||||
|
||||
A more advanced example can be found below, "Example 1: ruleset for a rate-limited window", using `storage` to `Put` and `Get` `string`s by key.
|
||||
|
||||
* At the time of writing, storage only exists as an ephemeral unencrypted implementation, to be used during testing.
|
||||
|
||||
### Things to note
|
||||
|
||||
The Otto vm has a few [caveats](https://github.com/robertkrimen/otto):
|
||||
|
||||
* "use strict" will parse, but does nothing.
|
||||
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
|
||||
* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported.
|
||||
|
||||
Additionally, a few more have been added
|
||||
|
||||
* The rule execution cannot load external javascript files.
|
||||
* The only preloaded library is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the GitHub repository.
|
||||
* Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data.
|
||||
* Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes.
|
||||
* The JS engine has access to `storage` and `console`.
|
||||
|
||||
#### Security considerations
|
||||
|
||||
##### Security of ruleset
|
||||
|
||||
Some security precautions can be made, such as:
|
||||
|
||||
* Never load `ruleset.js` unless the file is `readonly` (`r-??-??-?`). If the user wishes to modify the ruleset, he must make it writeable and then set back to readonly.
|
||||
* This is to prevent attacks where files are dropped on the users disk.
|
||||
* Since we're going to have to have some form of secure storage (not defined in this section), we could also store the `sha3` of the `ruleset.js` file in there.
|
||||
* If the user wishes to modify the ruleset, he'd then have to perform e.g. `signer --attest /path/to/ruleset --credential <creds>`
|
||||
|
||||
##### Security of implementation
|
||||
|
||||
The drawback of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement since it's already
|
||||
implemented for `geth`. There are no known security vulnerabilities in it, nor have we had any security problems with it so far.
|
||||
|
||||
The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered
|
||||
an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit
|
||||
to be gained from attacking the actual `signer` process from the `js` side would be if it could somehow extract cryptographic keys from memory.
|
||||
|
||||
##### Security in usability
|
||||
|
||||
Javascript is flexible, but also easy to get wrong, especially when users assume that `js` can handle large integers natively. Typical errors
|
||||
include trying to multiply `gasCost` with `gas` without using `bigint`:s.
|
||||
|
||||
It's unclear whether any other DSL could be more secure; since there's always the possibility of erroneously implementing a rule.
|
||||
|
||||
|
||||
## Credential management
|
||||
|
||||
The ability to auto-approve transactions means that the signer needs to have the necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass).
|
||||
|
||||
### Example implementation
|
||||
|
||||
Upon startup of the signer, the signer is given a switch: `--seed <path/to/masterseed>`
|
||||
The `seed` contains a blob of bytes, which is the master seed for the `signer`.
|
||||
|
||||
The `signer` uses the `seed` to:
|
||||
|
||||
* Generate the `path` where the settings are stored.
|
||||
* `./settings/1df094eb-c2b1-4689-90dd-790046d38025/vault.dat`
|
||||
* `./settings/1df094eb-c2b1-4689-90dd-790046d38025/rules.js`
|
||||
* Generate the encryption password for `vault.dat`.
|
||||
|
||||
The `vault.dat` would be an encrypted container storing the following information:
|
||||
|
||||
* `ksp` entries
|
||||
* `sha256` hash of `rules.js`
|
||||
* Information about pair:ed callers (not yet specified)
|
||||
|
||||
### Security considerations
|
||||
|
||||
This would leave it up to the user to ensure that the `path/to/masterseed` is handled securely. It's difficult to get around this, although one could
|
||||
imagine leveraging OS-level keychains where supported. The setup is however, in general, similar to how ssh-keys are stored in `.ssh/`.
|
||||
|
||||
|
||||
# Implementation status
|
||||
|
||||
This is now implemented (with ephemeral non-encrypted storage for now, so not yet enabled).
|
||||
|
||||
## Example 1: ruleset for a rate-limited window
|
||||
|
||||
|
||||
```js
|
||||
function big(str) {
|
||||
if (str.slice(0, 2) == "0x") {
|
||||
return new BigNumber(str.slice(2), 16)
|
||||
}
|
||||
return new BigNumber(str)
|
||||
}
|
||||
|
||||
// Time window: 1 week
|
||||
var window = 1000* 3600*24*7;
|
||||
|
||||
// Limit: 1 ether
|
||||
var limit = new BigNumber("1e18");
|
||||
|
||||
function isLimitOk(transaction) {
|
||||
var value = big(transaction.value)
|
||||
// Start of our window function
|
||||
var windowstart = new Date().getTime() - window;
|
||||
|
||||
var txs = [];
|
||||
var stored = storage.get('txs');
|
||||
|
||||
if (stored != "") {
|
||||
txs = JSON.parse(stored)
|
||||
}
|
||||
// First, remove all that has passed out of the time window
|
||||
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
|
||||
console.log(txs, newtxs.length);
|
||||
|
||||
// Secondly, aggregate the current sum
|
||||
sum = new BigNumber(0)
|
||||
|
||||
sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
|
||||
console.log("ApproveTx > Sum so far", sum);
|
||||
console.log("ApproveTx > Requested", value.toNumber());
|
||||
|
||||
// Would we exceed the weekly limit ?
|
||||
return sum.plus(value).lt(limit)
|
||||
|
||||
}
|
||||
function ApproveTx(r) {
|
||||
if (isLimitOk(r.transaction)) {
|
||||
return "Approve"
|
||||
}
|
||||
return "Nope"
|
||||
}
|
||||
|
||||
/**
|
||||
* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
|
||||
* 'response_str' contains the return value that will be sent to the external caller.
|
||||
* The return value from this method is ignore - the reason for having this callback is to allow the
|
||||
* ruleset to keep track of approved transactions.
|
||||
*
|
||||
* When implementing rate-limited rules, this callback should be used.
|
||||
* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
|
||||
* then accepts the transaction, this method will be called.
|
||||
*
|
||||
* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
|
||||
*/
|
||||
function OnApprovedTx(resp) {
|
||||
var value = big(resp.tx.value)
|
||||
var txs = []
|
||||
// Load stored transactions
|
||||
var stored = storage.get('txs');
|
||||
if (stored != "") {
|
||||
txs = JSON.parse(stored)
|
||||
}
|
||||
// Add this to the storage
|
||||
txs.push({tstamp: new Date().getTime(), value: value});
|
||||
storage.put("txs", JSON.stringify(txs));
|
||||
}
|
||||
```
|
||||
|
||||
## Example 2: allow destination
|
||||
|
||||
```js
|
||||
function ApproveTx(r) {
|
||||
if (r.transaction.from.toLowerCase() == "0x0000000000000000000000000000000000001337") {
|
||||
return "Approve"
|
||||
}
|
||||
if (r.transaction.from.toLowerCase() == "0x000000000000000000000000000000000000dead") {
|
||||
return "Reject"
|
||||
}
|
||||
// Otherwise goes to manual processing
|
||||
}
|
||||
```
|
||||
|
||||
## Example 3: Allow listing
|
||||
|
||||
```js
|
||||
function ApproveListing() {
|
||||
return "Approve"
|
||||
}
|
||||
```
|
||||
103
cmd/clef/run_test.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||
"github.com/ethereum/go-ethereum/internal/reexec"
|
||||
)
|
||||
|
||||
const registeredName = "clef-test"
|
||||
|
||||
type testproc struct {
|
||||
*cmdtest.TestCmd
|
||||
|
||||
// template variables for expect
|
||||
Datadir string
|
||||
Etherbase string
|
||||
}
|
||||
|
||||
func init() {
|
||||
reexec.Register(registeredName, func() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// check if we have been reexec'd
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// runClef spawns clef with the given command line args and adds keystore arg.
|
||||
// This method creates a temporary keystore folder which will be removed after
|
||||
// the test exits.
|
||||
func runClef(t *testing.T, args ...string) *testproc {
|
||||
ddir := t.TempDir()
|
||||
return runWithKeystore(t, ddir, args...)
|
||||
}
|
||||
|
||||
// runWithKeystore spawns clef with the given command line args and adds keystore arg.
|
||||
// This method does _not_ create the keystore folder, but it _does_ add the arg
|
||||
// to the args.
|
||||
func runWithKeystore(t *testing.T, keystore string, args ...string) *testproc {
|
||||
args = append([]string{"--keystore", keystore}, args...)
|
||||
tt := &testproc{Datadir: keystore}
|
||||
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
||||
// Boot "clef". This actually runs the test binary but the TestMain
|
||||
// function will prevent any tests from running.
|
||||
tt.Run(registeredName, args...)
|
||||
return tt
|
||||
}
|
||||
|
||||
func (proc *testproc) input(text string) *testproc {
|
||||
proc.TestCmd.InputLine(text)
|
||||
return proc
|
||||
}
|
||||
|
||||
/*
|
||||
// waitForEndpoint waits for the rpc endpoint to appear, or
|
||||
// aborts after 3 seconds.
|
||||
func (proc *testproc) waitForEndpoint(t *testing.T) *testproc {
|
||||
t.Helper()
|
||||
timeout := 3 * time.Second
|
||||
ipc := filepath.Join(proc.Datadir, "clef.ipc")
|
||||
|
||||
start := time.Now()
|
||||
for time.Since(start) < timeout {
|
||||
if _, err := os.Stat(ipc); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Logf("endpoint %v opened", ipc)
|
||||
return proc
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
t.Logf("stderr: \n%v", proc.StderrText())
|
||||
t.Logf("stdout: \n%v", proc.Output())
|
||||
t.Fatal("endpoint", ipc, "did not open within", timeout)
|
||||
return proc
|
||||
}
|
||||
*/
|
||||