Compare commits
134 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0287d54847 | ||
|
24562d9b0c | ||
|
dc681fc1f6 | ||
|
86bcbb0d79 | ||
|
066c75531d | ||
|
8327d1fdfc | ||
|
d54f2f2e5e | ||
|
c5d28f0b27 | ||
|
de971cc845 | ||
|
f86324edb7 | ||
|
eeaf191633 | ||
|
3010f9fc75 | ||
|
d90bbce954 | ||
|
5cdb476dd1 | ||
|
ff23e265cd | ||
|
12d8570322 | ||
|
5883afb3ef | ||
|
05280a7ae3 | ||
|
d97e0063d5 | ||
|
856307d8bb | ||
|
16d7eae1c8 | ||
|
d8da0b3d81 | ||
|
92b12ee6c6 | ||
|
fc20680b95 | ||
|
979fc96899 | ||
|
63a9d4b2ae | ||
|
ce5f94920d | ||
|
341f451083 | ||
|
d13b8e5570 | ||
|
5655dce3b8 | ||
|
7b5107b73f | ||
|
bdde616f23 | ||
|
3ee91b9f2e | ||
|
0f4e7c9b0d | ||
|
1b5a867eec | ||
|
87c0ba9213 | ||
|
b68929caee | ||
|
9f7b79af00 | ||
|
4e54b1a45e | ||
|
a70a79b285 | ||
|
15fdaf2005 | ||
|
8cbdc8638f | ||
|
0bdd295cc0 | ||
|
7ebc6c43ff | ||
|
560d44479c | ||
|
32b078d418 | ||
|
2ff464b29d | ||
|
f3bafecef7 | ||
|
54add42550 | ||
|
04926db204 | ||
|
3e0641923d | ||
|
74925e547f | ||
|
7afdf792ab | ||
|
c28fd9c079 | ||
|
4baa574410 | ||
|
9f45d6efae | ||
|
cbbc54c495 | ||
|
7cee2509c0 | ||
|
48b484c5ac | ||
|
06125bff89 | ||
|
9fea1a5cf5 | ||
|
e401f5ff10 | ||
|
6a53ce29a4 | ||
|
8f24097836 | ||
|
4b9c0ea76d | ||
|
3bb8a4ed3f | ||
|
983cb25a07 | ||
|
68754f3931 | ||
|
5d4512b113 | ||
|
d21303f9dd | ||
|
4fde0cabc1 | ||
|
4a04127ce3 | ||
|
2de37f28e0 | ||
|
5a88a7cf5b | ||
|
1d25039ff5 | ||
|
8ead45c20b | ||
|
82a9e11058 | ||
|
b35e4fce99 | ||
|
e24e05dd01 | ||
|
90dedea40f | ||
|
c0c01612e9 | ||
|
b2b14e6ce3 | ||
|
290d6bd903 | ||
|
9c2ac6fbd5 | ||
|
a00dc5095b | ||
|
ff90894636 | ||
|
9e04c5ec83 | ||
|
abf2d7d74f | ||
|
1976bb3df0 | ||
|
350a0490ab | ||
|
37564ceda6 | ||
|
28c5a8a54b | ||
|
298a19bbc6 | ||
|
c47052a580 | ||
|
93da0cf8a1 | ||
|
79ce5537ab | ||
|
8e7bee9b56 | ||
|
f538259187 | ||
|
b1be979443 | ||
|
e997f92caf | ||
|
56434bfa89 | ||
|
6793ffa12b | ||
|
5413df1dfa | ||
|
c374447401 | ||
|
105922180f | ||
|
3a57eecc69 | ||
|
997b55236e | ||
|
4c268e65a0 | ||
|
0b53e485d8 | ||
|
9e22e912e3 | ||
|
123864fc05 | ||
|
7163a6664e | ||
|
4366c45e4e | ||
|
3a52c4dcf2 | ||
|
722b742780 | ||
|
748f22c192 | ||
|
43e2e58cbd | ||
|
35ddf36229 | ||
|
0fef66c739 | ||
|
508891e64b | ||
|
9e88224eb8 | ||
|
295693759e | ||
|
240d1851db | ||
|
6c9f040ebe | ||
|
5b081ab214 | ||
|
6ef4495a8f | ||
|
79addac698 | ||
|
7d5267e3a2 | ||
|
4edbc1f2bb | ||
|
6cf6e1d753 | ||
|
2e08dad9e6 | ||
|
af258efdb9 | ||
|
6eef141aef | ||
|
b8dd0890b3 |
11
.github/CODEOWNERS
vendored
11
.github/CODEOWNERS
vendored
@@ -3,21 +3,20 @@
|
||||
|
||||
accounts/usbwallet @karalabe
|
||||
accounts/scwallet @gballet
|
||||
accounts/abi @gballet
|
||||
accounts/abi @gballet @MariusVanDerWijden
|
||||
cmd/clef @holiman
|
||||
cmd/puppeth @karalabe
|
||||
consensus @karalabe
|
||||
core/ @karalabe @holiman @rjl493456442
|
||||
dashboard/ @kurkomisi
|
||||
eth/ @karalabe @holiman @rjl493456442
|
||||
graphql/ @gballet
|
||||
les/ @zsfelfoldi @rjl493456442
|
||||
light/ @zsfelfoldi @rjl493456442
|
||||
mobile/ @karalabe @ligi
|
||||
node/ @fjl @renaynay
|
||||
p2p/ @fjl @zsfelfoldi
|
||||
rpc/ @fjl @holiman
|
||||
p2p/simulations @zelig @janos @justelad
|
||||
p2p/protocols @zelig @janos @justelad
|
||||
p2p/testing @zelig @janos @justelad
|
||||
p2p/simulations @fjl
|
||||
p2p/protocols @fjl
|
||||
p2p/testing @fjl
|
||||
signer/ @holiman
|
||||
whisper/ @gballet @gluk256
|
||||
|
32
.travis.yml
32
.travis.yml
@@ -16,7 +16,7 @@ jobs:
|
||||
- stage: lint
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- lint
|
||||
git:
|
||||
@@ -34,12 +34,22 @@ jobs:
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||
|
||||
- stage: build
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.14.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
script:
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||
|
||||
# These are the latest Go versions.
|
||||
- stage: build
|
||||
os: linux
|
||||
arch: amd64
|
||||
dist: xenial
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
script:
|
||||
@@ -51,7 +61,7 @@ jobs:
|
||||
os: linux
|
||||
arch: arm64
|
||||
dist: xenial
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
script:
|
||||
@@ -61,7 +71,7 @@ jobs:
|
||||
- stage: build
|
||||
os: osx
|
||||
osx_image: xcode11.3
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
script:
|
||||
@@ -82,7 +92,7 @@ jobs:
|
||||
if: type = push
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- ubuntu-ppa
|
||||
- GO111MODULE=on
|
||||
@@ -99,7 +109,7 @@ jobs:
|
||||
- python-paramiko
|
||||
script:
|
||||
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||
- go run build/ci.go debsrc -goversion 1.14.2 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
- go run build/ci.go debsrc -goversion 1.15 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
|
||||
# This builder does the Linux Azure uploads
|
||||
- stage: build
|
||||
@@ -107,7 +117,7 @@ jobs:
|
||||
os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- azure-linux
|
||||
- GO111MODULE=on
|
||||
@@ -144,7 +154,7 @@ jobs:
|
||||
dist: xenial
|
||||
services:
|
||||
- docker
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- azure-linux-mips
|
||||
- GO111MODULE=on
|
||||
@@ -192,7 +202,7 @@ jobs:
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
before_install:
|
||||
- curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -xz
|
||||
- curl https://dl.google.com/go/go1.15.linux-amd64.tar.gz | tar -xz
|
||||
- export PATH=`pwd`/go/bin:$PATH
|
||||
- export GOROOT=`pwd`/go
|
||||
- export GOPATH=$HOME/go
|
||||
@@ -210,7 +220,7 @@ jobs:
|
||||
- stage: build
|
||||
if: type = push
|
||||
os: osx
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- azure-osx
|
||||
- azure-ios
|
||||
@@ -242,7 +252,7 @@ jobs:
|
||||
if: type = cron
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.14.x
|
||||
go: 1.15.x
|
||||
env:
|
||||
- azure-purge
|
||||
- GO111MODULE=on
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.14-alpine as builder
|
||||
FROM golang:1.15-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.14-alpine as builder
|
||||
FROM golang:1.15-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
||||
|
||||
|
@@ -4,7 +4,7 @@ Official Golang implementation of the Ethereum protocol.
|
||||
|
||||
[](https://godoc.org/github.com/ethereum/go-ethereum)
|
||||
)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc)
|
||||
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
||||
[](https://travis-ci.org/ethereum/go-ethereum)
|
||||
[](https://discord.gg/nthXNEv)
|
||||
@@ -108,7 +108,7 @@ accounts available between them.*
|
||||
|
||||
### Full node on the Rinkeby test network
|
||||
|
||||
Go Ethereum also supports connecting to the older proof-of-authority based test network
|
||||
Go Ethereum also supports connecting to the older proof-of-authority based test network
|
||||
called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community.
|
||||
|
||||
```shell
|
||||
@@ -117,10 +117,10 @@ $ geth --rinkeby console
|
||||
|
||||
### Full node on the Ropsten test network
|
||||
|
||||
In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The
|
||||
In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The
|
||||
Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such,
|
||||
it has certain extra overhead and is more susceptible to reorganization attacks due to the
|
||||
network's low difficulty/security.
|
||||
network's low difficulty/security.
|
||||
|
||||
```shell
|
||||
$ geth --ropsten console
|
||||
|
@@ -45,7 +45,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
|
||||
// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend.
|
||||
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
||||
|
||||
var (
|
||||
@@ -55,7 +55,7 @@ var (
|
||||
)
|
||||
|
||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
||||
// the background. Its main purpose is to allow easily testing contract bindings.
|
||||
// the background. Its main purpose is to allow for easy testing of contract bindings.
|
||||
// Simulated backend implements the following interfaces:
|
||||
// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor,
|
||||
// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender
|
||||
@@ -123,10 +123,10 @@ func (b *SimulatedBackend) Rollback() {
|
||||
|
||||
func (b *SimulatedBackend) rollback() {
|
||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
||||
statedb, _ := b.blockchain.State()
|
||||
stateDB, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil)
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
||||
}
|
||||
|
||||
// stateByBlockNumber retrieves a state by a given blocknumber.
|
||||
@@ -146,12 +146,12 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address,
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return statedb.GetCode(contract), nil
|
||||
return stateDB.GetCode(contract), nil
|
||||
}
|
||||
|
||||
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
||||
@@ -159,12 +159,12 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return statedb.GetBalance(contract), nil
|
||||
return stateDB.GetBalance(contract), nil
|
||||
}
|
||||
|
||||
// NonceAt returns the nonce of a certain account in the blockchain.
|
||||
@@ -172,12 +172,12 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address,
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return statedb.GetNonce(contract), nil
|
||||
return stateDB.GetNonce(contract), nil
|
||||
}
|
||||
|
||||
// StorageAt returns the value of key in the storage of an account in the blockchain.
|
||||
@@ -185,17 +185,17 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val := statedb.GetState(contract, key)
|
||||
val := stateDB.GetState(contract, key)
|
||||
return val[:], nil
|
||||
}
|
||||
|
||||
// TransactionReceipt returns the receipt of a transaction.
|
||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
func (b *SimulatedBackend) TransactionReceipt(_ context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -207,7 +207,7 @@ func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common
|
||||
// blockchain. The isPending return value indicates whether the transaction has been
|
||||
// mined yet. Note that the transaction may not be part of the canonical chain even if
|
||||
// it's not pending.
|
||||
func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||
func (b *SimulatedBackend) TransactionByHash(_ context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -222,8 +222,8 @@ func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.
|
||||
return nil, false, ethereum.NotFound
|
||||
}
|
||||
|
||||
// BlockByHash retrieves a block based on the block hash
|
||||
func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
// BlockByHash retrieves a block based on the block hash.
|
||||
func (b *SimulatedBackend) BlockByHash(_ context.Context, hash common.Hash) (*types.Block, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -250,7 +250,7 @@ func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (
|
||||
|
||||
// blockByNumberNoLock retrieves a block from the database by number, caching it
|
||||
// (associated with its hash) if found without Lock.
|
||||
func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||
func (b *SimulatedBackend) blockByNumberNoLock(_ context.Context, number *big.Int) (*types.Block, error) {
|
||||
if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 {
|
||||
return b.blockchain.CurrentBlock(), nil
|
||||
}
|
||||
@@ -264,7 +264,7 @@ func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big.
|
||||
}
|
||||
|
||||
// HeaderByHash returns a block header from the current canonical chain.
|
||||
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
func (b *SimulatedBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -282,7 +282,7 @@ func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (
|
||||
|
||||
// HeaderByNumber returns a block header from the current canonical chain. If number is
|
||||
// nil, the latest known header is returned.
|
||||
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
|
||||
func (b *SimulatedBackend) HeaderByNumber(_ context.Context, block *big.Int) (*types.Header, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -293,8 +293,8 @@ func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (
|
||||
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||
}
|
||||
|
||||
// TransactionCount returns the number of transactions in a given block
|
||||
func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
|
||||
// TransactionCount returns the number of transactions in a given block.
|
||||
func (b *SimulatedBackend) TransactionCount(_ context.Context, blockHash common.Hash) (uint, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -310,8 +310,8 @@ func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash commo
|
||||
return uint(block.Transactions().Len()), nil
|
||||
}
|
||||
|
||||
// TransactionInBlock returns the transaction for a specific block at a specific index
|
||||
func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
|
||||
// TransactionInBlock returns the transaction for a specific block at a specific index.
|
||||
func (b *SimulatedBackend) TransactionInBlock(_ context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -338,7 +338,7 @@ func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash com
|
||||
}
|
||||
|
||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
||||
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
||||
func (b *SimulatedBackend) PendingCodeAt(_ context.Context, contract common.Address) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -357,14 +357,14 @@ func newRevertError(result *core.ExecutionResult) *revertError {
|
||||
}
|
||||
}
|
||||
|
||||
// revertError is an API error that encompassas an EVM revertal with JSON error
|
||||
// revertError is an API error that encompasses an EVM revert with JSON error
|
||||
// code and a binary data blob.
|
||||
type revertError struct {
|
||||
error
|
||||
reason string // revert reason hex encoded
|
||||
}
|
||||
|
||||
// ErrorCode returns the JSON error code for a revertal.
|
||||
// ErrorCode returns the JSON error code for a revert.
|
||||
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
||||
func (e *revertError) ErrorCode() int {
|
||||
return 3
|
||||
@@ -383,11 +383,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
}
|
||||
state, err := b.blockchain.State()
|
||||
stateDB, err := b.blockchain.State()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
|
||||
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -417,7 +417,7 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu
|
||||
|
||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
||||
// the nonce currently pending for the account.
|
||||
func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
||||
func (b *SimulatedBackend) PendingNonceAt(_ context.Context, account common.Address) (uint64, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -426,7 +426,7 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad
|
||||
|
||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||
func (b *SimulatedBackend) SuggestGasPrice(_ context.Context) (*big.Int, error) {
|
||||
return big.NewInt(1), nil
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
||||
hi = b.pendingBlock.GasLimit()
|
||||
}
|
||||
// Recap the highest gas allowance with account's balance.
|
||||
if call.GasPrice != nil && call.GasPrice.Uint64() != 0 {
|
||||
if call.GasPrice != nil && call.GasPrice.BitLen() != 0 {
|
||||
balance := b.pendingState.GetBalance(call.From) // from can't be nil
|
||||
available := new(big.Int).Set(balance)
|
||||
if call.Value != nil {
|
||||
@@ -458,7 +458,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
||||
available.Sub(available, call.Value)
|
||||
}
|
||||
allowance := new(big.Int).Div(available, call.GasPrice)
|
||||
if hi > allowance.Uint64() {
|
||||
if allowance.IsUint64() && hi > allowance.Uint64() {
|
||||
transfer := call.Value
|
||||
if transfer == nil {
|
||||
transfer = new(big.Int)
|
||||
@@ -525,7 +525,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
||||
|
||||
// callContract implements common code between normal and pending contract calls.
|
||||
// state is modified during execution, make sure to copy it if necessary.
|
||||
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) {
|
||||
func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) {
|
||||
// Ensure message is initialized properly.
|
||||
if call.GasPrice == nil {
|
||||
call.GasPrice = big.NewInt(1)
|
||||
@@ -537,23 +537,23 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
|
||||
call.Value = new(big.Int)
|
||||
}
|
||||
// Set infinite balance to the fake caller account.
|
||||
from := statedb.GetOrNewStateObject(call.From)
|
||||
from := stateDB.GetOrNewStateObject(call.From)
|
||||
from.SetBalance(math.MaxBig256)
|
||||
// Execute the call.
|
||||
msg := callmsg{call}
|
||||
msg := callMsg{call}
|
||||
|
||||
evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil)
|
||||
// Create a new environment which holds all relevant information
|
||||
// about the transaction and calling mechanisms.
|
||||
vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{})
|
||||
gaspool := new(core.GasPool).AddGas(math.MaxUint64)
|
||||
vmEnv := vm.NewEVM(evmContext, stateDB, b.config, vm.Config{})
|
||||
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
|
||||
|
||||
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
|
||||
return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb()
|
||||
}
|
||||
|
||||
// SendTransaction updates the pending block to include the given transaction.
|
||||
// It panics if the transaction is invalid.
|
||||
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||
func (b *SimulatedBackend) SendTransaction(_ context.Context, tx *types.Transaction) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -572,10 +572,10 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||
}
|
||||
block.AddTxWithChain(b.blockchain, tx)
|
||||
})
|
||||
statedb, _ := b.blockchain.State()
|
||||
stateDB, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil)
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -589,7 +589,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter
|
||||
// Block filter requested, construct a single-shot filter
|
||||
filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics)
|
||||
} else {
|
||||
// Initialize unset filter boundaried to run from genesis to chain head
|
||||
// Initialize unset filter boundaries to run from genesis to chain head
|
||||
from := int64(0)
|
||||
if query.FromBlock != nil {
|
||||
from = query.FromBlock.Int64()
|
||||
@@ -607,15 +607,15 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter
|
||||
return nil, err
|
||||
}
|
||||
res := make([]types.Log, len(logs))
|
||||
for i, log := range logs {
|
||||
res[i] = *log
|
||||
for i, nLog := range logs {
|
||||
res[i] = *nLog
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
||||
// subscription immediately, which can be used to stream the found events.
|
||||
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
func (b *SimulatedBackend) SubscribeFilterLogs(_ context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
// Subscribe to contract events
|
||||
sink := make(chan []*types.Log)
|
||||
|
||||
@@ -629,9 +629,9 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere
|
||||
for {
|
||||
select {
|
||||
case logs := <-sink:
|
||||
for _, log := range logs {
|
||||
for _, nlog := range logs {
|
||||
select {
|
||||
case ch <- *log:
|
||||
case ch <- *nlog:
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
@@ -647,8 +647,8 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere
|
||||
}), nil
|
||||
}
|
||||
|
||||
// SubscribeNewHead returns an event subscription for a new header
|
||||
func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
|
||||
// SubscribeNewHead returns an event subscription for a new header.
|
||||
func (b *SimulatedBackend) SubscribeNewHead(_ context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
|
||||
// subscribe to a new head
|
||||
sink := make(chan *types.Header)
|
||||
sub := b.events.SubscribeNewHeads(sink)
|
||||
@@ -675,20 +675,22 @@ func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *type
|
||||
}
|
||||
|
||||
// AdjustTime adds a time shift to the simulated clock.
|
||||
// It can only be called on empty blocks.
|
||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if len(b.pendingBlock.Transactions()) != 0 {
|
||||
return errors.New("Could not adjust time on non-empty block")
|
||||
}
|
||||
|
||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
for _, tx := range b.pendingBlock.Transactions() {
|
||||
block.AddTx(tx)
|
||||
}
|
||||
block.OffsetTime(int64(adjustment.Seconds()))
|
||||
})
|
||||
statedb, _ := b.blockchain.State()
|
||||
stateDB, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil)
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -698,19 +700,19 @@ func (b *SimulatedBackend) Blockchain() *core.BlockChain {
|
||||
return b.blockchain
|
||||
}
|
||||
|
||||
// callmsg implements core.Message to allow passing it as a transaction simulator.
|
||||
type callmsg struct {
|
||||
// callMsg implements core.Message to allow passing it as a transaction simulator.
|
||||
type callMsg struct {
|
||||
ethereum.CallMsg
|
||||
}
|
||||
|
||||
func (m callmsg) From() common.Address { return m.CallMsg.From }
|
||||
func (m callmsg) Nonce() uint64 { return 0 }
|
||||
func (m callmsg) CheckNonce() bool { return false }
|
||||
func (m callmsg) To() *common.Address { return m.CallMsg.To }
|
||||
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
|
||||
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
|
||||
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
|
||||
func (m callmsg) Data() []byte { return m.CallMsg.Data }
|
||||
func (m callMsg) From() common.Address { return m.CallMsg.From }
|
||||
func (m callMsg) Nonce() uint64 { return 0 }
|
||||
func (m callMsg) CheckNonce() bool { return false }
|
||||
func (m callMsg) To() *common.Address { return m.CallMsg.To }
|
||||
func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
|
||||
func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
|
||||
func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
|
||||
func (m callMsg) Data() []byte { return m.CallMsg.Data }
|
||||
|
||||
// filterBackend implements filters.Backend to support filtering for logs without
|
||||
// taking bloom-bits acceleration structures into account.
|
||||
@@ -722,18 +724,18 @@ type filterBackend struct {
|
||||
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
||||
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
||||
|
||||
func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) {
|
||||
func (fb *filterBackend) HeaderByNumber(_ context.Context, block rpc.BlockNumber) (*types.Header, error) {
|
||||
if block == rpc.LatestBlockNumber {
|
||||
return fb.bc.CurrentHeader(), nil
|
||||
}
|
||||
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
func (fb *filterBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) {
|
||||
return fb.bc.GetHeaderByHash(hash), nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||
func (fb *filterBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Receipts, error) {
|
||||
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
||||
if number == nil {
|
||||
return nil, nil
|
||||
@@ -741,7 +743,7 @@ func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (typ
|
||||
return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
|
||||
func (fb *filterBackend) GetLogs(_ context.Context, hash common.Hash) ([][]*types.Log, error) {
|
||||
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
||||
if number == nil {
|
||||
return nil, nil
|
||||
@@ -757,7 +759,7 @@ func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*ty
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||
func (fb *filterBackend) SubscribeNewTxsEvent(_ chan<- core.NewTxsEvent) event.Subscription {
|
||||
return nullSubscription()
|
||||
}
|
||||
|
||||
@@ -773,13 +775,13 @@ func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscr
|
||||
return fb.bc.SubscribeLogsEvent(ch)
|
||||
}
|
||||
|
||||
func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||
func (fb *filterBackend) SubscribePendingLogsEvent(_ chan<- []*types.Log) event.Subscription {
|
||||
return nullSubscription()
|
||||
}
|
||||
|
||||
func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
|
||||
|
||||
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
|
||||
func (fb *filterBackend) ServiceFilter(_ context.Context, _ *bloombits.MatcherSession) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
|
@@ -129,8 +129,8 @@ func TestNewSimulatedBackend(t *testing.T) {
|
||||
t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config)
|
||||
}
|
||||
|
||||
statedb, _ := sim.blockchain.State()
|
||||
bal := statedb.GetBalance(testAddr)
|
||||
stateDB, _ := sim.blockchain.State()
|
||||
bal := stateDB.GetBalance(testAddr)
|
||||
if bal.Cmp(expectedBal) != 0 {
|
||||
t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal)
|
||||
}
|
||||
@@ -143,8 +143,7 @@ func TestSimulatedBackend_AdjustTime(t *testing.T) {
|
||||
defer sim.Close()
|
||||
|
||||
prevTime := sim.pendingBlock.Time()
|
||||
err := sim.AdjustTime(time.Second)
|
||||
if err != nil {
|
||||
if err := sim.AdjustTime(time.Second); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
newTime := sim.pendingBlock.Time()
|
||||
@@ -154,6 +153,44 @@ func TestSimulatedBackend_AdjustTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSimulatedBackend_AdjustTimeFail(t *testing.T) {
|
||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
sim := simTestBackend(testAddr)
|
||||
// Create tx and send
|
||||
tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, big.NewInt(1), nil)
|
||||
signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||
if err != nil {
|
||||
t.Errorf("could not sign tx: %v", err)
|
||||
}
|
||||
sim.SendTransaction(context.Background(), signedTx)
|
||||
// AdjustTime should fail on non-empty block
|
||||
if err := sim.AdjustTime(time.Second); err == nil {
|
||||
t.Error("Expected adjust time to error on non-empty block")
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
prevTime := sim.pendingBlock.Time()
|
||||
if err := sim.AdjustTime(time.Minute); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
newTime := sim.pendingBlock.Time()
|
||||
if newTime-prevTime != uint64(time.Minute.Seconds()) {
|
||||
t.Errorf("adjusted time not equal to a minute. prev: %v, new: %v", prevTime, newTime)
|
||||
}
|
||||
// Put a transaction after adjusting time
|
||||
tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, big.NewInt(1), nil)
|
||||
signedTx2, err := types.SignTx(tx2, types.HomesteadSigner{}, testKey)
|
||||
if err != nil {
|
||||
t.Errorf("could not sign tx: %v", err)
|
||||
}
|
||||
sim.SendTransaction(context.Background(), signedTx2)
|
||||
sim.Commit()
|
||||
newTime = sim.pendingBlock.Time()
|
||||
if newTime-prevTime >= uint64(time.Minute.Seconds()) {
|
||||
t.Errorf("time adjusted, but shouldn't be: prev: %v, new: %v", prevTime, newTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimulatedBackend_BalanceAt(t *testing.T) {
|
||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
expectedBal := big.NewInt(10000000000)
|
||||
@@ -484,7 +521,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
||||
sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000)
|
||||
defer sim.Close()
|
||||
|
||||
receipant := common.HexToAddress("deadbeef")
|
||||
recipient := common.HexToAddress("deadbeef")
|
||||
var cases = []struct {
|
||||
name string
|
||||
message ethereum.CallMsg
|
||||
@@ -493,7 +530,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
||||
}{
|
||||
{"EstimateWithoutPrice", ethereum.CallMsg{
|
||||
From: addr,
|
||||
To: &receipant,
|
||||
To: &recipient,
|
||||
Gas: 0,
|
||||
GasPrice: big.NewInt(0),
|
||||
Value: big.NewInt(1000),
|
||||
@@ -502,7 +539,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
||||
|
||||
{"EstimateWithPrice", ethereum.CallMsg{
|
||||
From: addr,
|
||||
To: &receipant,
|
||||
To: &recipient,
|
||||
Gas: 0,
|
||||
GasPrice: big.NewInt(1000),
|
||||
Value: big.NewInt(1000),
|
||||
@@ -511,7 +548,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
||||
|
||||
{"EstimateWithVeryHighPrice", ethereum.CallMsg{
|
||||
From: addr,
|
||||
To: &receipant,
|
||||
To: &recipient,
|
||||
Gas: 0,
|
||||
GasPrice: big.NewInt(1e14), // gascost = 2.1ether
|
||||
Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether
|
||||
@@ -520,7 +557,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
||||
|
||||
{"EstimateWithSuperhighPrice", ethereum.CallMsg{
|
||||
From: addr,
|
||||
To: &receipant,
|
||||
To: &recipient,
|
||||
Gas: 0,
|
||||
GasPrice: big.NewInt(2e14), // gascost = 4.2ether
|
||||
Value: big.NewInt(1000),
|
||||
@@ -1049,12 +1086,12 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) {
|
||||
t.Errorf("result from %v was not nil: %v", key, res)
|
||||
}
|
||||
if val != nil {
|
||||
rerr, ok := err.(*revertError)
|
||||
rErr, ok := err.(*revertError)
|
||||
if !ok {
|
||||
t.Errorf("expect revert error")
|
||||
}
|
||||
if rerr.Error() != "execution reverted: "+val.(string) {
|
||||
t.Errorf("error was malformed: got %v want %v", rerr.Error(), val)
|
||||
if rErr.Error() != "execution reverted: "+val.(string) {
|
||||
t.Errorf("error was malformed: got %v want %v", rErr.Error(), val)
|
||||
}
|
||||
} else {
|
||||
// revert(0x0,0x0)
|
||||
|
@@ -386,7 +386,7 @@ func isDynamicType(t Type) bool {
|
||||
func getTypeSize(t Type) int {
|
||||
if t.T == ArrayTy && !isDynamicType(*t.Elem) {
|
||||
// Recursively calculate type size if it is a nested array
|
||||
if t.Elem.T == ArrayTy {
|
||||
if t.Elem.T == ArrayTy || t.Elem.T == TupleTy {
|
||||
return t.Size * getTypeSize(*t.Elem)
|
||||
}
|
||||
return t.Size * 32
|
||||
|
@@ -330,3 +330,39 @@ func TestInternalType(t *testing.T) {
|
||||
t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(kind)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTypeSize(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
typ string
|
||||
components []ArgumentMarshaling
|
||||
typSize int
|
||||
}{
|
||||
// simple array
|
||||
{"uint256[2]", nil, 32 * 2},
|
||||
{"address[3]", nil, 32 * 3},
|
||||
{"bytes32[4]", nil, 32 * 4},
|
||||
// array array
|
||||
{"uint256[2][3][4]", nil, 32 * (2 * 3 * 4)},
|
||||
// array tuple
|
||||
{"tuple[2]", []ArgumentMarshaling{{Name: "x", Type: "bytes32"}, {Name: "y", Type: "bytes32"}}, (32 * 2) * 2},
|
||||
// simple tuple
|
||||
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "uint256"}, {Name: "y", Type: "uint256"}}, 32 * 2},
|
||||
// tuple array
|
||||
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}}, 32 * 2},
|
||||
// tuple tuple
|
||||
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32"}}}}, 32},
|
||||
{"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}, {Name: "y", Type: "uint256"}}}}, 32 * (2 + 1)},
|
||||
}
|
||||
|
||||
for i, data := range testCases {
|
||||
typ, err := NewType(data.typ, "", data.components)
|
||||
if err != nil {
|
||||
t.Errorf("type %q: failed to parse type string: %v", data.typ, err)
|
||||
}
|
||||
|
||||
result := getTypeSize(typ)
|
||||
if result != data.typSize {
|
||||
t.Errorf("case %d type %q: get type size error: actual: %d expected: %d", i, data.typ, result, data.typSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -368,18 +368,22 @@ func (w *wallet) selfDerive() {
|
||||
w.log.Warn("USB wallet nonce retrieval failed", "err", err)
|
||||
break
|
||||
}
|
||||
// If the next account is empty, stop self-derivation, but add for the last base path
|
||||
// We've just self-derived a new account, start tracking it locally
|
||||
// unless the account was empty.
|
||||
path := make(accounts.DerivationPath, len(nextPaths[i]))
|
||||
copy(path[:], nextPaths[i][:])
|
||||
if balance.Sign() == 0 && nonce == 0 {
|
||||
empty = true
|
||||
// If it indeed was empty, make a log output for it anyway. In the case
|
||||
// of legacy-ledger, the first account on the legacy-path will
|
||||
// be shown to the user, even if we don't actively track it
|
||||
if i < len(nextAddrs)-1 {
|
||||
w.log.Info("Skipping trakcking first account on legacy path, use personal.deriveAccount(<url>,<path>, false) to track",
|
||||
"path", path, "address", nextAddrs[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
// We've just self-derived a new account, start tracking it locally
|
||||
path := make(accounts.DerivationPath, len(nextPaths[i]))
|
||||
copy(path[:], nextPaths[i][:])
|
||||
paths = append(paths, path)
|
||||
|
||||
account := accounts.Account{
|
||||
Address: nextAddrs[i],
|
||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
||||
|
@@ -24,8 +24,8 @@ environment:
|
||||
install:
|
||||
- git submodule update --init
|
||||
- rmdir C:\go /s /q
|
||||
- appveyor DownloadFile https://dl.google.com/go/go1.14.2.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.14.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- appveyor DownloadFile https://dl.google.com/go/go1.15.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.15.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- go version
|
||||
- gcc --version
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# This file contains sha256 checksums of optional build dependencies.
|
||||
|
||||
98de84e69726a66da7b4e58eac41b99cbe274d7e8906eeb8a5b7eb0aadee7f7c go1.14.2.src.tar.gz
|
||||
69438f7ed4f532154ffaf878f3dfd83747e7a00b70b3556eddabf7aaee28ac3a go1.15.src.tar.gz
|
||||
|
||||
d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz
|
||||
bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip
|
||||
|
19
build/ci.go
19
build/ci.go
@@ -79,7 +79,6 @@ var (
|
||||
executablePath("geth"),
|
||||
executablePath("puppeth"),
|
||||
executablePath("rlpdump"),
|
||||
executablePath("wnode"),
|
||||
executablePath("clef"),
|
||||
}
|
||||
|
||||
@@ -109,10 +108,6 @@ var (
|
||||
BinaryName: "rlpdump",
|
||||
Description: "Developer utility tool that prints RLP structures.",
|
||||
},
|
||||
{
|
||||
BinaryName: "wnode",
|
||||
Description: "Ethereum Whisper diagnostic tool",
|
||||
},
|
||||
{
|
||||
BinaryName: "clef",
|
||||
Description: "Ethereum account management tool.",
|
||||
@@ -139,13 +134,14 @@ var (
|
||||
// Note: zesty is unsupported because it was officially deprecated on Launchpad.
|
||||
// Note: artful is unsupported because it was officially deprecated on Launchpad.
|
||||
// Note: cosmic is unsupported because it was officially deprecated on Launchpad.
|
||||
// Note: disco is unsupported because it was officially deprecated on Launchpad.
|
||||
debDistroGoBoots = map[string]string{
|
||||
"trusty": "golang-1.11",
|
||||
"xenial": "golang-go",
|
||||
"bionic": "golang-go",
|
||||
"disco": "golang-go",
|
||||
"eoan": "golang-go",
|
||||
"focal": "golang-go",
|
||||
"groovy": "golang-go",
|
||||
}
|
||||
|
||||
debGoBootPaths = map[string]string{
|
||||
@@ -215,9 +211,9 @@ func doInstall(cmdline []string) {
|
||||
var minor int
|
||||
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
||||
|
||||
if minor < 11 {
|
||||
if minor < 13 {
|
||||
log.Println("You have Go version", runtime.Version())
|
||||
log.Println("go-ethereum requires at least Go version 1.11 and cannot")
|
||||
log.Println("go-ethereum requires at least Go version 1.13 and cannot")
|
||||
log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -233,6 +229,7 @@ func doInstall(cmdline []string) {
|
||||
if runtime.GOARCH == "arm64" {
|
||||
goinstall.Args = append(goinstall.Args, "-p", "1")
|
||||
}
|
||||
goinstall.Args = append(goinstall.Args, "-trimpath")
|
||||
goinstall.Args = append(goinstall.Args, "-v")
|
||||
goinstall.Args = append(goinstall.Args, packages...)
|
||||
build.MustRun(goinstall)
|
||||
@@ -241,6 +238,7 @@ func doInstall(cmdline []string) {
|
||||
|
||||
// Seems we are cross compiling, work around forbidden GOBIN
|
||||
goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...)
|
||||
goinstall.Args = append(goinstall.Args, "-trimpath")
|
||||
goinstall.Args = append(goinstall.Args, "-v")
|
||||
goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...)
|
||||
goinstall.Args = append(goinstall.Args, packages...)
|
||||
@@ -884,11 +882,12 @@ func gomobileTool(subcmd string, args ...string) *exec.Cmd {
|
||||
"PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"),
|
||||
}
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") {
|
||||
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") || strings.HasPrefix(e, "GOBIN=") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, e)
|
||||
}
|
||||
cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -957,7 +956,7 @@ func doXCodeFramework(cmdline []string) {
|
||||
|
||||
if *local {
|
||||
// If we're building locally, use the build folder and stop afterwards
|
||||
bind.Dir, _ = filepath.Abs(GOBIN)
|
||||
bind.Dir = GOBIN
|
||||
build.MustRun(bind)
|
||||
return
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common/compiler"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
@@ -100,7 +101,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
|
||||
app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
|
||||
app.Flags = []cli.Flag{
|
||||
abiFlag,
|
||||
binFlag,
|
||||
@@ -117,7 +118,7 @@ func init() {
|
||||
aliasFlag,
|
||||
}
|
||||
app.Action = utils.MigrateFlags(abigen)
|
||||
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
||||
cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate
|
||||
}
|
||||
|
||||
func abigen(c *cli.Context) error {
|
||||
|
@@ -22,8 +22,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
@@ -37,7 +37,7 @@ var (
|
||||
var app *cli.App
|
||||
|
||||
func init() {
|
||||
app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
|
||||
app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
|
||||
app.Commands = []cli.Command{
|
||||
commandStatus,
|
||||
commandDeploy,
|
||||
@@ -48,7 +48,7 @@ func init() {
|
||||
oracleFlag,
|
||||
nodeURLFlag,
|
||||
}
|
||||
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
||||
cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate
|
||||
}
|
||||
|
||||
// Commonly used command line flags.
|
||||
|
@@ -33,12 +33,12 @@ GLOBAL OPTIONS:
|
||||
--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")
|
||||
--rpcaddr value HTTP-RPC server listening interface (default: "localhost")
|
||||
--rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost")
|
||||
--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)
|
||||
--rpc Enable the HTTP-RPC server
|
||||
--rpcport value HTTP-RPC server listening port (default: 8550)
|
||||
--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")
|
||||
@@ -113,7 +113,7 @@ Some snags and todos
|
||||
|
||||
### External API
|
||||
|
||||
Clef listens to HTTP requests on `rpcaddr`:`rpcport` (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).
|
||||
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.
|
||||
|
||||
|
@@ -94,7 +94,7 @@ with minimal requirements.
|
||||
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/client/qubes-client.py):
|
||||
[qubes-client.py](qubes/qubes-client.py):
|
||||
|
||||
```python
|
||||
|
||||
|
@@ -32,6 +32,7 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -43,6 +44,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@@ -52,6 +54,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/signer/fourbyte"
|
||||
"github.com/ethereum/go-ethereum/signer/rules"
|
||||
"github.com/ethereum/go-ethereum/signer/storage"
|
||||
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
@@ -101,10 +104,15 @@ var (
|
||||
Usage: "Chain id to use for signing (1=mainnet, 3=Ropsten, 4=Rinkeby, 5=Goerli)",
|
||||
}
|
||||
rpcPortFlag = cli.IntFlag{
|
||||
Name: "rpcport",
|
||||
Name: "http.port",
|
||||
Usage: "HTTP-RPC server listening port",
|
||||
Value: node.DefaultHTTPPort + 5,
|
||||
}
|
||||
legacyRPCPortFlag = cli.IntFlag{
|
||||
Name: "rpcport",
|
||||
Usage: "HTTP-RPC server listening port (Deprecated, please use --http.port).",
|
||||
Value: node.DefaultHTTPPort + 5,
|
||||
}
|
||||
signerSecretFlag = cli.StringFlag{
|
||||
Name: "signersecret",
|
||||
Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash",
|
||||
@@ -215,6 +223,42 @@ The gendoc generates example structures of the json-rpc communication types.
|
||||
`}
|
||||
)
|
||||
|
||||
// AppHelpFlagGroups is the application flags, grouped by functionality.
|
||||
var AppHelpFlagGroups = []flags.FlagGroup{
|
||||
{
|
||||
Name: "FLAGS",
|
||||
Flags: []cli.Flag{
|
||||
logLevelFlag,
|
||||
keystoreFlag,
|
||||
configdirFlag,
|
||||
chainIdFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.HTTPListenAddrFlag,
|
||||
utils.HTTPVirtualHostsFlag,
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.HTTPEnabledFlag,
|
||||
rpcPortFlag,
|
||||
signerSecretFlag,
|
||||
customDBFlag,
|
||||
auditLogFlag,
|
||||
ruleFlag,
|
||||
stdiouiFlag,
|
||||
testFlag,
|
||||
advancedMode,
|
||||
acceptFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ALIASED (deprecated)",
|
||||
Flags: []cli.Flag{
|
||||
legacyRPCPortFlag,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
app.Name = "Clef"
|
||||
app.Usage = "Manage Ethereum account operations"
|
||||
@@ -240,6 +284,7 @@ func init() {
|
||||
testFlag,
|
||||
advancedMode,
|
||||
acceptFlag,
|
||||
legacyRPCPortFlag,
|
||||
}
|
||||
app.Action = signer
|
||||
app.Commands = []cli.Command{initCommand,
|
||||
@@ -248,7 +293,41 @@ func init() {
|
||||
delCredentialCommand,
|
||||
newAccountCommand,
|
||||
gendocCommand}
|
||||
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
||||
cli.CommandHelpTemplate = flags.CommandHelpTemplate
|
||||
// Override the default app help template
|
||||
cli.AppHelpTemplate = flags.ClefAppHelpTemplate
|
||||
|
||||
// Override the default app help printer, but only for the global app help
|
||||
originalHelpPrinter := cli.HelpPrinter
|
||||
cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
|
||||
if tmpl == flags.ClefAppHelpTemplate {
|
||||
// Render out custom usage screen
|
||||
originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups})
|
||||
} else if tmpl == flags.CommandHelpTemplate {
|
||||
// Iterate over all command specific flags and categorize them
|
||||
categorized := make(map[string][]cli.Flag)
|
||||
for _, flag := range data.(cli.Command).Flags {
|
||||
if _, ok := categorized[flag.String()]; !ok {
|
||||
categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag)
|
||||
}
|
||||
}
|
||||
|
||||
// sort to get a stable ordering
|
||||
sorted := make([]flags.FlagGroup, 0, len(categorized))
|
||||
for cat, flgs := range categorized {
|
||||
sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs})
|
||||
}
|
||||
sort.Sort(flags.ByCategory(sorted))
|
||||
|
||||
// add sorted array to data and render with default printer
|
||||
originalHelpPrinter(w, tmpl, map[string]interface{}{
|
||||
"cmd": data,
|
||||
"categorizedFlags": sorted,
|
||||
})
|
||||
} else {
|
||||
originalHelpPrinter(w, tmpl, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -597,8 +676,17 @@ func signer(c *cli.Context) error {
|
||||
}
|
||||
handler := node.NewHTTPHandlerStack(srv, cors, vhosts)
|
||||
|
||||
// set port
|
||||
port := c.Int(rpcPortFlag.Name)
|
||||
if c.GlobalIsSet(legacyRPCPortFlag.Name) {
|
||||
if !c.GlobalIsSet(rpcPortFlag.Name) {
|
||||
port = c.Int(legacyRPCPortFlag.Name)
|
||||
}
|
||||
log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port")
|
||||
}
|
||||
|
||||
// start http server
|
||||
httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
|
||||
httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port)
|
||||
httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler)
|
||||
if err != nil {
|
||||
utils.Fatalf("Could not start RPC api: %v", err)
|
||||
|
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
@@ -69,22 +70,30 @@ func enrdump(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("INVALID: %v", err)
|
||||
}
|
||||
fmt.Print(dumpRecord(r))
|
||||
dumpRecord(os.Stdout, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
// dumpRecord creates a human-readable description of the given node record.
|
||||
func dumpRecord(r *enr.Record) string {
|
||||
out := new(bytes.Buffer)
|
||||
if n, err := enode.New(enode.ValidSchemes, r); err != nil {
|
||||
func dumpRecord(out io.Writer, r *enr.Record) {
|
||||
n, err := enode.New(enode.ValidSchemes, r)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "INVALID: %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(out, "Node ID: %v\n", n.ID())
|
||||
dumpNodeURL(out, n)
|
||||
}
|
||||
kv := r.AppendElements(nil)[1:]
|
||||
fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2)
|
||||
fmt.Fprint(out, dumpRecordKV(kv, 2))
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func dumpNodeURL(out io.Writer, n *enode.Node) {
|
||||
var key enode.Secp256k1
|
||||
if n.Load(&key) != nil {
|
||||
return // no secp256k1 public key
|
||||
}
|
||||
fmt.Fprintf(out, "URLv4: %s\n", n.URLv4())
|
||||
}
|
||||
|
||||
func dumpRecordKV(kv []interface{}, indent int) string {
|
||||
|
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ var gitDate = ""
|
||||
var app *cli.App
|
||||
|
||||
func init() {
|
||||
app = utils.NewApp(gitCommit, gitDate, "an Ethereum key manager")
|
||||
app = flags.NewApp(gitCommit, gitDate, "an Ethereum key manager")
|
||||
app.Commands = []cli.Command{
|
||||
commandGenerate,
|
||||
commandInspect,
|
||||
@@ -43,7 +43,7 @@ func init() {
|
||||
commandSignMessage,
|
||||
commandVerifyMessage,
|
||||
}
|
||||
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
||||
cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate
|
||||
}
|
||||
|
||||
// Commonly used command line flags.
|
||||
|
@@ -29,6 +29,8 @@ Command line params that has to be supported are
|
||||
--trace Output full trace logs to files <txhash>.jsonl
|
||||
--trace.nomemory Disable full memory dump in traces
|
||||
--trace.nostack Disable stack output in traces
|
||||
--trace.noreturndata Disable return data output in traces
|
||||
--output.basedir value Specifies where output files are placed. Will be created if it does not exist. (default: ".")
|
||||
--output.alloc alloc Determines where to put the alloc of the post-state.
|
||||
`stdout` - into the stdout output
|
||||
`stderr` - into the stderr output
|
||||
@@ -232,13 +234,13 @@ Example where blockhashes are provided:
|
||||
./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace
|
||||
```
|
||||
```
|
||||
cat trace-0.jsonl | grep BLOCKHASH -C2
|
||||
cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2
|
||||
```
|
||||
```
|
||||
{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"depth":1,"refund":0,"opName":"PUSH1","error":""}
|
||||
{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"depth":1,"refund":0,"opName":"BLOCKHASH","error":""}
|
||||
{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"depth":1,"refund":0,"opName":"STOP","error":""}
|
||||
{"output":"","gasUsed":"0x17","time":155861}
|
||||
{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"PUSH1","error":""}
|
||||
{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"BLOCKHASH","error":""}
|
||||
{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"STOP","error":""}
|
||||
{"output":"","gasUsed":"0x17","time":112885}
|
||||
```
|
||||
|
||||
In this example, the caller has not provided the required blockhash:
|
||||
@@ -254,9 +256,9 @@ Error code: 4
|
||||
Another thing that can be done, is to chain invocations:
|
||||
```
|
||||
./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json
|
||||
INFO [06-29|11:52:04.934] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||
INFO [06-29|11:52:04.936] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||
INFO [06-29|11:52:04.936] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||
INFO [08-03|15:25:15.168] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||
INFO [08-03|15:25:15.169] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||
INFO [08-03|15:25:15.169] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||
|
||||
```
|
||||
What happened here, is that we first applied two identical transactions, so the second one was rejected.
|
||||
|
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -81,7 +82,7 @@ type stEnvMarshaling struct {
|
||||
// Apply applies a set of transactions to a pre-state
|
||||
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
txs types.Transactions, miningReward int64,
|
||||
getTracerFn func(txIndex int) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) {
|
||||
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) {
|
||||
|
||||
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
|
||||
// required blockhashes
|
||||
@@ -135,7 +136,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
rejectedTxs = append(rejectedTxs, i)
|
||||
continue
|
||||
}
|
||||
tracer, err := getTracerFn(txIndex)
|
||||
tracer, err := getTracerFn(txIndex, tx.Hash())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -220,8 +221,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
}
|
||||
execRs := &ExecutionResult{
|
||||
StateRoot: root,
|
||||
TxRoot: types.DeriveSha(includedTxs),
|
||||
ReceiptRoot: types.DeriveSha(receipts),
|
||||
TxRoot: types.DeriveSha(includedTxs, new(trie.Trie)),
|
||||
ReceiptRoot: types.DeriveSha(receipts, new(trie.Trie)),
|
||||
Bloom: types.CreateBloom(receipts),
|
||||
LogsHash: rlpHash(statedb.Logs()),
|
||||
Receipts: receipts,
|
||||
|
@@ -38,6 +38,15 @@ var (
|
||||
Name: "trace.nostack",
|
||||
Usage: "Disable stack output in traces",
|
||||
}
|
||||
TraceDisableReturnDataFlag = cli.BoolFlag{
|
||||
Name: "trace.noreturndata",
|
||||
Usage: "Disable return data output in traces",
|
||||
}
|
||||
OutputBasedir = cli.StringFlag{
|
||||
Name: "output.basedir",
|
||||
Usage: "Specifies where output files are placed. Will be created if it does not exist.",
|
||||
Value: "",
|
||||
}
|
||||
OutputAllocFlag = cli.StringFlag{
|
||||
Name: "output.alloc",
|
||||
Usage: "Determines where to put the `alloc` of the post-state.\n" +
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@@ -75,17 +76,29 @@ func Main(ctx *cli.Context) error {
|
||||
log.Root().SetHandler(glogger)
|
||||
|
||||
var (
|
||||
err error
|
||||
tracer vm.Tracer
|
||||
err error
|
||||
tracer vm.Tracer
|
||||
baseDir = ""
|
||||
)
|
||||
var getTracer func(txIndex int) (vm.Tracer, error)
|
||||
var getTracer func(txIndex int, txHash common.Hash) (vm.Tracer, error)
|
||||
|
||||
// If user specified a basedir, make sure it exists
|
||||
if ctx.IsSet(OutputBasedir.Name) {
|
||||
if base := ctx.String(OutputBasedir.Name); len(base) > 0 {
|
||||
err := os.MkdirAll(base, 0755) // //rw-r--r--
|
||||
if err != nil {
|
||||
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
|
||||
}
|
||||
baseDir = base
|
||||
}
|
||||
}
|
||||
if ctx.Bool(TraceFlag.Name) {
|
||||
// Configure the EVM logger
|
||||
logConfig := &vm.LogConfig{
|
||||
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
|
||||
DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name),
|
||||
Debug: true,
|
||||
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
|
||||
DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name),
|
||||
DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name),
|
||||
Debug: true,
|
||||
}
|
||||
var prevFile *os.File
|
||||
// This one closes the last file
|
||||
@@ -94,11 +107,11 @@ func Main(ctx *cli.Context) error {
|
||||
prevFile.Close()
|
||||
}
|
||||
}()
|
||||
getTracer = func(txIndex int) (vm.Tracer, error) {
|
||||
getTracer = func(txIndex int, txHash common.Hash) (vm.Tracer, error) {
|
||||
if prevFile != nil {
|
||||
prevFile.Close()
|
||||
}
|
||||
traceFile, err := os.Create(fmt.Sprintf("trace-%d.jsonl", txIndex))
|
||||
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String())))
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
|
||||
}
|
||||
@@ -106,7 +119,7 @@ func Main(ctx *cli.Context) error {
|
||||
return vm.NewJSONLogger(logConfig, traceFile), nil
|
||||
}
|
||||
} else {
|
||||
getTracer = func(txIndex int) (tracer vm.Tracer, err error) {
|
||||
getTracer = func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
@@ -196,7 +209,7 @@ func Main(ctx *cli.Context) error {
|
||||
//postAlloc := state.DumpGenesisFormat(false, false, false)
|
||||
collector := make(Alloc)
|
||||
state.DumpToCollector(collector, false, false, false, nil, -1)
|
||||
return dispatchOutput(ctx, result, collector)
|
||||
return dispatchOutput(ctx, baseDir, result, collector)
|
||||
|
||||
}
|
||||
|
||||
@@ -223,12 +236,12 @@ func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) {
|
||||
}
|
||||
|
||||
// saveFile marshalls the object to the given file
|
||||
func saveFile(filename string, data interface{}) error {
|
||||
func saveFile(baseDir, filename string, data interface{}) error {
|
||||
b, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
|
||||
}
|
||||
if err = ioutil.WriteFile(filename, b, 0644); err != nil {
|
||||
if err = ioutil.WriteFile(path.Join(baseDir, filename), b, 0644); err != nil {
|
||||
return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err))
|
||||
}
|
||||
return nil
|
||||
@@ -236,26 +249,26 @@ func saveFile(filename string, data interface{}) error {
|
||||
|
||||
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||
// files
|
||||
func dispatchOutput(ctx *cli.Context, result *ExecutionResult, alloc Alloc) error {
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc) error {
|
||||
stdOutObject := make(map[string]interface{})
|
||||
stdErrObject := make(map[string]interface{})
|
||||
dispatch := func(fName, name string, obj interface{}) error {
|
||||
dispatch := func(baseDir, fName, name string, obj interface{}) error {
|
||||
switch fName {
|
||||
case "stdout":
|
||||
stdOutObject[name] = obj
|
||||
case "stderr":
|
||||
stdErrObject[name] = obj
|
||||
default: // save to file
|
||||
if err := saveFile(fName, obj); err != nil {
|
||||
if err := saveFile(baseDir, fName, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := dispatch(ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil {
|
||||
if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dispatch(ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
||||
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stdOutObject) > 0 {
|
||||
|
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,7 @@ var gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags)
|
||||
var gitDate = ""
|
||||
|
||||
var (
|
||||
app = utils.NewApp(gitCommit, gitDate, "the evm command line interface")
|
||||
app = flags.NewApp(gitCommit, gitDate, "the evm command line interface")
|
||||
|
||||
DebugFlag = cli.BoolFlag{
|
||||
Name: "debug",
|
||||
@@ -120,6 +121,14 @@ var (
|
||||
Name: "nostack",
|
||||
Usage: "disable stack output",
|
||||
}
|
||||
DisableStorageFlag = cli.BoolFlag{
|
||||
Name: "nostorage",
|
||||
Usage: "disable storage output",
|
||||
}
|
||||
DisableReturnDataFlag = cli.BoolFlag{
|
||||
Name: "noreturndata",
|
||||
Usage: "disable return data output",
|
||||
}
|
||||
EVMInterpreterFlag = cli.StringFlag{
|
||||
Name: "vm.evm",
|
||||
Usage: "External EVM configuration (default = built-in interpreter)",
|
||||
@@ -136,6 +145,8 @@ var stateTransitionCommand = cli.Command{
|
||||
t8ntool.TraceFlag,
|
||||
t8ntool.TraceDisableMemoryFlag,
|
||||
t8ntool.TraceDisableStackFlag,
|
||||
t8ntool.TraceDisableReturnDataFlag,
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputAllocFlag,
|
||||
t8ntool.OutputResultFlag,
|
||||
t8ntool.InputAllocFlag,
|
||||
@@ -171,6 +182,8 @@ func init() {
|
||||
ReceiverFlag,
|
||||
DisableMemoryFlag,
|
||||
DisableStackFlag,
|
||||
DisableStorageFlag,
|
||||
DisableReturnDataFlag,
|
||||
EVMInterpreterFlag,
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
@@ -180,7 +193,7 @@ func init() {
|
||||
stateTestCommand,
|
||||
stateTransitionCommand,
|
||||
}
|
||||
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
||||
cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@@ -108,9 +108,11 @@ func runCmd(ctx *cli.Context) error {
|
||||
glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
|
||||
log.Root().SetHandler(glogger)
|
||||
logconfig := &vm.LogConfig{
|
||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||
Debug: ctx.GlobalBool(DebugFlag.Name),
|
||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||
DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name),
|
||||
DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
|
||||
Debug: ctx.GlobalBool(DebugFlag.Name),
|
||||
}
|
||||
|
||||
var (
|
||||
|
@@ -59,8 +59,10 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||
|
||||
// Configure the EVM logger
|
||||
config := &vm.LogConfig{
|
||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||
DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name),
|
||||
DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
|
||||
}
|
||||
var (
|
||||
tracer vm.Tracer
|
||||
|
@@ -155,10 +155,10 @@ echo "Example where blockhashes are provided: "
|
||||
cmd="./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace"
|
||||
tick && echo $cmd && tick
|
||||
$cmd 2>&1 >/dev/null
|
||||
cmd="cat trace-0.jsonl | grep BLOCKHASH -C2"
|
||||
cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2"
|
||||
tick && echo $cmd && tick
|
||||
echo "$ticks"
|
||||
cat trace-0.jsonl | grep BLOCKHASH -C2
|
||||
cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2
|
||||
echo "$ticks"
|
||||
echo ""
|
||||
|
||||
|
@@ -235,23 +235,20 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Assemble the Ethereum light client protocol
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
cfg := eth.DefaultConfig
|
||||
cfg.SyncMode = downloader.LightSync
|
||||
cfg.NetworkId = network
|
||||
cfg.Genesis = genesis
|
||||
return les.New(ctx, &cfg)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
cfg := eth.DefaultConfig
|
||||
cfg.SyncMode = downloader.LightSync
|
||||
cfg.NetworkId = network
|
||||
cfg.Genesis = genesis
|
||||
lesBackend, err := les.New(stack, &cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
|
||||
}
|
||||
|
||||
// Assemble the ethstats monitoring and reporting service'
|
||||
if stats != "" {
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var serv *les.LightEthereum
|
||||
ctx.Service(&serv)
|
||||
return ethstats.New(stats, nil, serv)
|
||||
}); err != nil {
|
||||
if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -268,7 +265,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
||||
// Attach to the client and retrieve and interesting metadatas
|
||||
api, err := stack.Attach()
|
||||
if err != nil {
|
||||
stack.Stop()
|
||||
stack.Close()
|
||||
return nil, err
|
||||
}
|
||||
client := ethclient.NewClient(api)
|
||||
|
@@ -49,7 +49,7 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-8 col-lg-offset-2">
|
||||
<div class="input-group">
|
||||
<input id="url" name="url" type="text" class="form-control" placeholder="Social network URL containing your Ethereum address...">
|
||||
<input id="url" name="url" type="text" class="form-control" placeholder="Social network URL containing your Ethereum address..."/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Give me Ether <i class="fa fa-caret-down" aria-hidden="true"></i></button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">{{range $idx, $amount := .Amounts}}
|
||||
|
@@ -239,8 +239,8 @@ func initGenesis(ctx *cli.Context) error {
|
||||
if err := json.NewDecoder(file).Decode(genesis); err != nil {
|
||||
utils.Fatalf("invalid genesis file: %v", err)
|
||||
}
|
||||
// Open an initialise both full and light databases
|
||||
stack := makeFullNode(ctx)
|
||||
// Open and initialise both full and light databases
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
for _, name := range []string{"chaindata", "lightchaindata"} {
|
||||
@@ -277,7 +277,8 @@ func importChain(ctx *cli.Context) error {
|
||||
utils.SetupMetrics(ctx)
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
stack := makeFullNode(ctx)
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chain, db := utils.MakeChain(ctx, stack, false)
|
||||
@@ -371,7 +372,8 @@ func exportChain(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
stack := makeFullNode(ctx)
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chain, _ := utils.MakeChain(ctx, stack, true)
|
||||
@@ -406,7 +408,8 @@ func importPreimages(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
stack := makeFullNode(ctx)
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
db := utils.MakeChainDatabase(ctx, stack)
|
||||
@@ -424,7 +427,8 @@ func exportPreimages(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
stack := makeFullNode(ctx)
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
db := utils.MakeChainDatabase(ctx, stack)
|
||||
@@ -446,7 +450,7 @@ func copyDb(ctx *cli.Context) error {
|
||||
utils.Fatalf("Source ancient chain directory path argument missing")
|
||||
}
|
||||
// Initialize a new chain for the running node to sync into
|
||||
stack := makeFullNode(ctx)
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chain, chainDb := utils.MakeChain(ctx, stack, false)
|
||||
@@ -554,7 +558,7 @@ func confirmAndRemoveDB(database string, kind string) {
|
||||
}
|
||||
|
||||
func dump(ctx *cli.Context) error {
|
||||
stack := makeFullNode(ctx)
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chain, chainDb := utils.MakeChain(ctx, stack, true)
|
||||
|
@@ -28,9 +28,10 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/naoina/toml"
|
||||
)
|
||||
|
||||
@@ -74,7 +75,6 @@ type ethstatsConfig struct {
|
||||
|
||||
type gethConfig struct {
|
||||
Eth eth.Config
|
||||
Shh whisper.Config
|
||||
Node node.Config
|
||||
Ethstats ethstatsConfig
|
||||
}
|
||||
@@ -104,11 +104,11 @@ func defaultNodeConfig() node.Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// makeConfigNode loads geth configuration and creates a blank node instance.
|
||||
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
// Load defaults.
|
||||
cfg := gethConfig{
|
||||
Eth: eth.DefaultConfig,
|
||||
Shh: whisper.DefaultConfig,
|
||||
Node: defaultNodeConfig(),
|
||||
}
|
||||
|
||||
@@ -129,49 +129,36 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) {
|
||||
cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
|
||||
}
|
||||
utils.SetShhConfig(ctx, stack, &cfg.Shh)
|
||||
utils.SetShhConfig(ctx, stack)
|
||||
|
||||
return stack, cfg
|
||||
}
|
||||
|
||||
// enableWhisper returns true in case one of the whisper flags is set.
|
||||
func enableWhisper(ctx *cli.Context) bool {
|
||||
func checkWhisper(ctx *cli.Context) {
|
||||
for _, flag := range whisperFlags {
|
||||
if ctx.GlobalIsSet(flag.GetName()) {
|
||||
return true
|
||||
log.Warn("deprecated whisper flag detected. Whisper has been moved to github.com/ethereum/whisper")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
// makeFullNode loads geth configuration and creates the Ethereum backend.
|
||||
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
||||
stack, cfg := makeConfigNode(ctx)
|
||||
utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
||||
shhEnabled := enableWhisper(ctx)
|
||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
|
||||
if shhEnabled || shhAutoEnabled {
|
||||
if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
|
||||
cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
|
||||
}
|
||||
if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
|
||||
cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(utils.WhisperRestrictConnectionBetweenLightClientsFlag.Name) {
|
||||
cfg.Shh.RestrictConnectionBetweenLightClients = true
|
||||
}
|
||||
utils.RegisterShhService(stack, &cfg.Shh)
|
||||
}
|
||||
backend := utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
checkWhisper(ctx)
|
||||
// Configure GraphQL if requested
|
||||
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
|
||||
utils.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts)
|
||||
utils.RegisterGraphQLService(stack, backend, cfg.Node)
|
||||
}
|
||||
// Add the Ethereum Stats daemon if requested.
|
||||
if cfg.Ethstats.URL != "" {
|
||||
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
|
||||
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
|
||||
}
|
||||
return stack
|
||||
return stack, backend
|
||||
}
|
||||
|
||||
// dumpConfig is the dumpconfig command.
|
||||
|
@@ -78,12 +78,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons
|
||||
func localConsole(ctx *cli.Context) error {
|
||||
// Create and start the node based on the CLI flags
|
||||
prepare(ctx)
|
||||
node := makeFullNode(ctx)
|
||||
startNode(ctx, node)
|
||||
defer node.Close()
|
||||
stack, backend := makeFullNode(ctx)
|
||||
startNode(ctx, stack, backend)
|
||||
defer stack.Close()
|
||||
|
||||
// Attach to the newly started node and start the JavaScript console
|
||||
client, err := node.Attach()
|
||||
client, err := stack.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||
}
|
||||
@@ -190,12 +190,12 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||
// everything down.
|
||||
func ephemeralConsole(ctx *cli.Context) error {
|
||||
// Create and start the node based on the CLI flags
|
||||
node := makeFullNode(ctx)
|
||||
startNode(ctx, node)
|
||||
defer node.Close()
|
||||
stack, backend := makeFullNode(ctx)
|
||||
startNode(ctx, stack, backend)
|
||||
defer stack.Close()
|
||||
|
||||
// Attach to the newly started node and start the JavaScript console
|
||||
client, err := node.Attach()
|
||||
client, err := stack.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
|
||||
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
|
||||
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestConsoleWelcome(t *testing.T) {
|
||||
// Start a geth console, make sure it's cleaned up and terminate the console
|
||||
geth := runGeth(t,
|
||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||
"--etherbase", coinbase, "--shh",
|
||||
"--etherbase", coinbase,
|
||||
"console")
|
||||
|
||||
// Gather all the infos the welcome message needs to contain
|
||||
@@ -83,11 +83,9 @@ func TestIPCAttachWelcome(t *testing.T) {
|
||||
defer os.RemoveAll(ws)
|
||||
ipc = filepath.Join(ws, "geth.ipc")
|
||||
}
|
||||
// Note: we need --shh because testAttachWelcome checks for default
|
||||
// list of ipc modules and shh is included there.
|
||||
geth := runGeth(t,
|
||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
|
||||
"--etherbase", coinbase, "--ipcpath", ipc)
|
||||
|
||||
defer func() {
|
||||
geth.Interrupt()
|
||||
|
@@ -119,8 +119,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc
|
||||
} else {
|
||||
// Force chain initialization
|
||||
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
|
||||
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
|
||||
geth.WaitExit()
|
||||
runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit()
|
||||
}
|
||||
// Retrieve the DAO config flag from the database
|
||||
path := filepath.Join(datadir, "geth", "chaindata")
|
||||
|
@@ -95,9 +95,9 @@ func (g *gethrpc) waitSynced() {
|
||||
}
|
||||
}
|
||||
|
||||
func startGethWithRpc(t *testing.T, name string, args ...string) *gethrpc {
|
||||
func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc {
|
||||
g := &gethrpc{name: name}
|
||||
args = append([]string{"--networkid=42", "--port=0", "--nousb", "--http", "--http.port=0", "--http.api=admin,eth,les"}, args...)
|
||||
args = append([]string{"--networkid=42", "--port=0", "--nousb"}, args...)
|
||||
t.Logf("Starting %v with rpc: %v", name, args)
|
||||
g.geth = runGeth(t, args...)
|
||||
// wait before we can attach to it. TODO: probe for it properly
|
||||
@@ -112,7 +112,7 @@ func startGethWithRpc(t *testing.T, name string, args ...string) *gethrpc {
|
||||
}
|
||||
|
||||
func initGeth(t *testing.T) string {
|
||||
g := runGeth(t, "--networkid=42", "init", "./testdata/clique.json")
|
||||
g := runGeth(t, "--nousb", "--networkid=42", "init", "./testdata/clique.json")
|
||||
datadir := g.Datadir
|
||||
g.WaitExit()
|
||||
return datadir
|
||||
@@ -120,15 +120,15 @@ func initGeth(t *testing.T) string {
|
||||
|
||||
func startLightServer(t *testing.T) *gethrpc {
|
||||
datadir := initGeth(t)
|
||||
runGeth(t, "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit()
|
||||
runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit()
|
||||
account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105"
|
||||
server := startGethWithRpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1")
|
||||
server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1")
|
||||
return server
|
||||
}
|
||||
|
||||
func startClient(t *testing.T, name string) *gethrpc {
|
||||
datadir := initGeth(t)
|
||||
return startGethWithRpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1")
|
||||
return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1")
|
||||
}
|
||||
|
||||
func TestPriorityClient(t *testing.T) {
|
||||
@@ -166,6 +166,7 @@ func TestPriorityClient(t *testing.T) {
|
||||
freeCli.getNodeInfo().ID: freeCli,
|
||||
prioCli.getNodeInfo().ID: prioCli,
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
lightServer.callRPC(&peers, "admin_peers")
|
||||
peersWithNames := make(map[string]string)
|
||||
for _, p := range peers {
|
||||
|
@@ -36,7 +36,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
@@ -53,7 +54,7 @@ var (
|
||||
gitCommit = ""
|
||||
gitDate = ""
|
||||
// The app that holds all commands and flags.
|
||||
app = utils.NewApp(gitCommit, gitDate, "the go-ethereum command line interface")
|
||||
app = flags.NewApp(gitCommit, gitDate, "the go-ethereum command line interface")
|
||||
// flags that configure the node
|
||||
nodeFlags = []cli.Flag{
|
||||
utils.IdentityFlag,
|
||||
@@ -98,6 +99,7 @@ var (
|
||||
utils.LightEgressFlag,
|
||||
utils.LightMaxPeersFlag,
|
||||
utils.LegacyLightPeersFlag,
|
||||
utils.LightNoPruneFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.UltraLightServersFlag,
|
||||
utils.UltraLightFractionFlag,
|
||||
@@ -106,6 +108,8 @@ var (
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheTrieFlag,
|
||||
utils.CacheTrieJournalFlag,
|
||||
utils.CacheTrieRejournalFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.CacheSnapshotFlag,
|
||||
utils.CacheNoPrefetchFlag,
|
||||
@@ -167,8 +171,6 @@ var (
|
||||
utils.LegacyRPCCORSDomainFlag,
|
||||
utils.LegacyRPCVirtualHostsFlag,
|
||||
utils.GraphQLEnabledFlag,
|
||||
utils.GraphQLListenAddrFlag,
|
||||
utils.GraphQLPortFlag,
|
||||
utils.GraphQLCORSDomainFlag,
|
||||
utils.GraphQLVirtualHostsFlag,
|
||||
utils.HTTPApiFlag,
|
||||
@@ -346,18 +348,20 @@ func geth(ctx *cli.Context) error {
|
||||
if args := ctx.Args(); len(args) > 0 {
|
||||
return fmt.Errorf("invalid command: %q", args[0])
|
||||
}
|
||||
|
||||
prepare(ctx)
|
||||
node := makeFullNode(ctx)
|
||||
defer node.Close()
|
||||
startNode(ctx, node)
|
||||
node.Wait()
|
||||
stack, backend := makeFullNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
startNode(ctx, stack, backend)
|
||||
stack.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// startNode boots up the system node and all registered protocols, after which
|
||||
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
||||
// miner.
|
||||
func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) {
|
||||
debug.Memsize.Add("node", stack)
|
||||
|
||||
// Start up the node itself
|
||||
@@ -377,25 +381,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
}
|
||||
ethClient := ethclient.NewClient(rpcClient)
|
||||
|
||||
// Set contract backend for ethereum service if local node
|
||||
// is serving LES requests.
|
||||
if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 {
|
||||
var ethService *eth.Ethereum
|
||||
if err := stack.Service(ðService); err != nil {
|
||||
utils.Fatalf("Failed to retrieve ethereum service: %v", err)
|
||||
}
|
||||
ethService.SetContractBackend(ethClient)
|
||||
}
|
||||
// Set contract backend for les service if local node is
|
||||
// running as a light client.
|
||||
if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||
var lesService *les.LightEthereum
|
||||
if err := stack.Service(&lesService); err != nil {
|
||||
utils.Fatalf("Failed to retrieve light ethereum service: %v", err)
|
||||
}
|
||||
lesService.SetContractBackend(ethClient)
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Open any wallets already attached
|
||||
for _, wallet := range stack.AccountManager().Wallets() {
|
||||
@@ -447,7 +432,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
|
||||
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
|
||||
"age", common.PrettyAge(timestamp))
|
||||
stack.Stop()
|
||||
stack.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -459,24 +444,24 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||
utils.Fatalf("Light clients do not support mining")
|
||||
}
|
||||
var ethereum *eth.Ethereum
|
||||
if err := stack.Service(ðereum); err != nil {
|
||||
ethBackend, ok := backend.(*eth.EthAPIBackend)
|
||||
if !ok {
|
||||
utils.Fatalf("Ethereum service not running: %v", err)
|
||||
}
|
||||
|
||||
// Set the gas price to the limits from the CLI and start mining
|
||||
gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
|
||||
if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) {
|
||||
gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name)
|
||||
}
|
||||
ethereum.TxPool().SetGasPrice(gasprice)
|
||||
|
||||
ethBackend.TxPool().SetGasPrice(gasprice)
|
||||
// start mining
|
||||
threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)
|
||||
if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) {
|
||||
threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name)
|
||||
log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads")
|
||||
}
|
||||
|
||||
if err := ethereum.StartMining(threads); err != nil {
|
||||
if err := ethBackend.StartMining(threads); err != nil {
|
||||
utils.Fatalf("Failed to start mining: %v", err)
|
||||
}
|
||||
}
|
||||
|
@@ -200,11 +200,11 @@ func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) {
|
||||
return e.inner.Author(header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
|
||||
func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
|
||||
return e.inner.VerifyHeader(chain, header, seal)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
return e.inner.VerifyHeaders(chain, headers, seals)
|
||||
}
|
||||
|
||||
@@ -212,11 +212,11 @@ func (e *NoRewardEngine) VerifyUncles(chain consensus.ChainReader, block *types.
|
||||
return e.inner.VerifyUncles(chain, block)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||
func (e *NoRewardEngine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
return e.inner.VerifySeal(chain, header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||
func (e *NoRewardEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
return e.inner.Prepare(chain, header)
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func (e *NoRewardEngine) accumulateRewards(config *params.ChainConfig, state *st
|
||||
state.AddBalance(header.Coinbase, reward)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Finalize(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
|
||||
func (e *NoRewardEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
|
||||
uncles []*types.Header) {
|
||||
if e.rewardsOn {
|
||||
e.inner.Finalize(chain, header, statedb, txs, uncles)
|
||||
@@ -239,7 +239,7 @@ func (e *NoRewardEngine) Finalize(chain consensus.ChainReader, header *types.Hea
|
||||
}
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
|
||||
func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
|
||||
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if e.rewardsOn {
|
||||
return e.inner.FinalizeAndAssemble(chain, header, statedb, txs, uncles, receipts)
|
||||
@@ -248,11 +248,11 @@ func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainReader, header
|
||||
header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// Header seems complete, assemble into a block and return
|
||||
return types.NewBlock(header, txs, uncles, receipts), nil
|
||||
return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
func (e *NoRewardEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
return e.inner.Seal(chain, block, results, stop)
|
||||
}
|
||||
|
||||
@@ -260,11 +260,11 @@ func (e *NoRewardEngine) SealHash(header *types.Header) common.Hash {
|
||||
return e.inner.SealHash(header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||
func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
||||
return e.inner.CalcDifficulty(chain, time, parent)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) APIs(chain consensus.ChainReader) []rpc.API {
|
||||
func (e *NoRewardEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
return e.inner.APIs(chain)
|
||||
}
|
||||
|
||||
|
@@ -24,44 +24,12 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// AppHelpTemplate is the test template for the default, global app help topic.
|
||||
var AppHelpTemplate = `NAME:
|
||||
{{.App.Name}} - {{.App.Usage}}
|
||||
|
||||
Copyright 2013-2019 The go-ethereum Authors
|
||||
|
||||
USAGE:
|
||||
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
{{if .App.Version}}
|
||||
VERSION:
|
||||
{{.App.Version}}
|
||||
{{end}}{{if len .App.Authors}}
|
||||
AUTHOR(S):
|
||||
{{range .App.Authors}}{{ . }}{{end}}
|
||||
{{end}}{{if .App.Commands}}
|
||||
COMMANDS:
|
||||
{{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{end}}{{if .FlagGroups}}
|
||||
{{range .FlagGroups}}{{.Name}} OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}
|
||||
{{end}}{{end}}{{if .App.Copyright }}
|
||||
COPYRIGHT:
|
||||
{{.App.Copyright}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// flagGroup is a collection of flags belonging to a single topic.
|
||||
type flagGroup struct {
|
||||
Name string
|
||||
Flags []cli.Flag
|
||||
}
|
||||
|
||||
// AppHelpFlagGroups is the application flags, grouped by functionality.
|
||||
var AppHelpFlagGroups = []flagGroup{
|
||||
var AppHelpFlagGroups = []flags.FlagGroup{
|
||||
{
|
||||
Name: "ETHEREUM",
|
||||
Flags: []cli.Flag{
|
||||
@@ -96,6 +64,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.UltraLightServersFlag,
|
||||
utils.UltraLightFractionFlag,
|
||||
utils.UltraLightOnlyAnnounceFlag,
|
||||
utils.LightNoPruneFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -140,6 +109,8 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheTrieFlag,
|
||||
utils.CacheTrieJournalFlag,
|
||||
utils.CacheTrieRejournalFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.CacheSnapshotFlag,
|
||||
utils.CacheNoPrefetchFlag,
|
||||
@@ -171,8 +142,6 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.WSApiFlag,
|
||||
utils.WSAllowedOriginsFlag,
|
||||
utils.GraphQLEnabledFlag,
|
||||
utils.GraphQLListenAddrFlag,
|
||||
utils.GraphQLPortFlag,
|
||||
utils.GraphQLCORSDomainFlag,
|
||||
utils.GraphQLVirtualHostsFlag,
|
||||
utils.RPCGlobalGasCap,
|
||||
@@ -242,7 +211,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
Flags: metricsFlags,
|
||||
},
|
||||
{
|
||||
Name: "WHISPER (EXPERIMENTAL)",
|
||||
Name: "WHISPER (deprecated)",
|
||||
Flags: whisperFlags,
|
||||
},
|
||||
{
|
||||
@@ -260,6 +229,8 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.LegacyWSApiFlag,
|
||||
utils.LegacyGpoBlocksFlag,
|
||||
utils.LegacyGpoPercentileFlag,
|
||||
utils.LegacyGraphQLListenAddrFlag,
|
||||
utils.LegacyGraphQLPortFlag,
|
||||
}, debug.DeprecatedFlags...),
|
||||
},
|
||||
{
|
||||
@@ -271,53 +242,14 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
},
|
||||
}
|
||||
|
||||
// byCategory sorts an array of flagGroup by Name in the order
|
||||
// defined in AppHelpFlagGroups.
|
||||
type byCategory []flagGroup
|
||||
|
||||
func (a byCategory) Len() int { return len(a) }
|
||||
func (a byCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byCategory) Less(i, j int) bool {
|
||||
iCat, jCat := a[i].Name, a[j].Name
|
||||
iIdx, jIdx := len(AppHelpFlagGroups), len(AppHelpFlagGroups) // ensure non categorized flags come last
|
||||
|
||||
for i, group := range AppHelpFlagGroups {
|
||||
if iCat == group.Name {
|
||||
iIdx = i
|
||||
}
|
||||
if jCat == group.Name {
|
||||
jIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
return iIdx < jIdx
|
||||
}
|
||||
|
||||
func flagCategory(flag cli.Flag) string {
|
||||
for _, category := range AppHelpFlagGroups {
|
||||
for _, flg := range category.Flags {
|
||||
if flg.GetName() == flag.GetName() {
|
||||
return category.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return "MISC"
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Override the default app help template
|
||||
cli.AppHelpTemplate = AppHelpTemplate
|
||||
|
||||
// Define a one shot struct to pass to the usage template
|
||||
type helpData struct {
|
||||
App interface{}
|
||||
FlagGroups []flagGroup
|
||||
}
|
||||
cli.AppHelpTemplate = flags.AppHelpTemplate
|
||||
|
||||
// Override the default app help printer, but only for the global app help
|
||||
originalHelpPrinter := cli.HelpPrinter
|
||||
cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
|
||||
if tmpl == AppHelpTemplate {
|
||||
if tmpl == flags.AppHelpTemplate {
|
||||
// Iterate over all the flags and add any uncategorized ones
|
||||
categorized := make(map[string]struct{})
|
||||
for _, group := range AppHelpFlagGroups {
|
||||
@@ -349,22 +281,22 @@ func init() {
|
||||
}()
|
||||
}
|
||||
// Render out custom usage screen
|
||||
originalHelpPrinter(w, tmpl, helpData{data, AppHelpFlagGroups})
|
||||
} else if tmpl == utils.CommandHelpTemplate {
|
||||
originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups})
|
||||
} else if tmpl == flags.CommandHelpTemplate {
|
||||
// Iterate over all command specific flags and categorize them
|
||||
categorized := make(map[string][]cli.Flag)
|
||||
for _, flag := range data.(cli.Command).Flags {
|
||||
if _, ok := categorized[flag.String()]; !ok {
|
||||
categorized[flagCategory(flag)] = append(categorized[flagCategory(flag)], flag)
|
||||
categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag)
|
||||
}
|
||||
}
|
||||
|
||||
// sort to get a stable ordering
|
||||
sorted := make([]flagGroup, 0, len(categorized))
|
||||
sorted := make([]flags.FlagGroup, 0, len(categorized))
|
||||
for cat, flgs := range categorized {
|
||||
sorted = append(sorted, flagGroup{cat, flgs})
|
||||
sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs})
|
||||
}
|
||||
sort.Sort(byCategory(sorted))
|
||||
sort.Sort(flags.ByCategory(sorted))
|
||||
|
||||
// add sorted array to data and render with default printer
|
||||
originalHelpPrinter(w, tmpl, map[string]interface{}{
|
||||
|
@@ -289,7 +289,7 @@ func createNode(ctx *cli.Context) error {
|
||||
config.PrivateKey = privKey
|
||||
}
|
||||
if services := ctx.String("services"); services != "" {
|
||||
config.Services = strings.Split(services, ",")
|
||||
config.Lifecycles = strings.Split(services, ",")
|
||||
}
|
||||
node, err := client.CreateNode(config)
|
||||
if err != nil {
|
||||
|
@@ -73,7 +73,7 @@ func StartNode(stack *node.Node) {
|
||||
defer signal.Stop(sigc)
|
||||
<-sigc
|
||||
log.Info("Got interrupt, shutting down...")
|
||||
go stack.Stop()
|
||||
go stack.Close()
|
||||
for i := 10; i > 0; i-- {
|
||||
<-sigc
|
||||
if i > 1 {
|
||||
|
@@ -19,7 +19,6 @@ package utils
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -40,6 +39,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
@@ -48,6 +48,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethstats"
|
||||
"github.com/ethereum/go-ethereum/graphql"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
@@ -61,36 +63,10 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
pcsclite "github.com/gballet/go-libpcsclite"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} [arguments...]
|
||||
{{if .cmd.Description}}{{.cmd.Description}}
|
||||
{{end}}{{if .cmd.Subcommands}}
|
||||
SUBCOMMANDS:
|
||||
{{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{end}}{{if .categorizedFlags}}
|
||||
{{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS:
|
||||
{{range $categorized.Flags}}{{"\t"}}{{.}}
|
||||
{{end}}
|
||||
{{end}}{{end}}`
|
||||
|
||||
OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...]
|
||||
{{if .Description}}{{.Description}}
|
||||
{{end}}{{if .Subcommands}}
|
||||
SUBCOMMANDS:
|
||||
{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{end}}{{if .Flags}}
|
||||
OPTIONS:
|
||||
{{range $.Flags}} {{.}}
|
||||
{{end}}
|
||||
{{end}}`
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.AppHelpTemplate = `{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||
|
||||
@@ -104,21 +80,10 @@ GLOBAL OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
cli.CommandHelpTemplate = CommandHelpTemplate
|
||||
cli.CommandHelpTemplate = flags.CommandHelpTemplate
|
||||
cli.HelpPrinter = printHelp
|
||||
}
|
||||
|
||||
// NewApp creates an app with sane defaults.
|
||||
func NewApp(gitCommit, gitDate, usage string) *cli.App {
|
||||
app := cli.NewApp()
|
||||
app.Name = filepath.Base(os.Args[0])
|
||||
app.Author = ""
|
||||
app.Email = ""
|
||||
app.Version = params.VersionWithCommit(gitCommit, gitDate)
|
||||
app.Usage = usage
|
||||
return app
|
||||
}
|
||||
|
||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
funcMap := template.FuncMap{"join": strings.Join}
|
||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||
@@ -282,6 +247,10 @@ var (
|
||||
Name: "ulc.onlyannounce",
|
||||
Usage: "Ultra light server sends announcements only",
|
||||
}
|
||||
LightNoPruneFlag = cli.BoolFlag{
|
||||
Name: "light.nopruning",
|
||||
Usage: "Disable ancient light chain data pruning",
|
||||
}
|
||||
// Ethash settings
|
||||
EthashCacheDirFlag = DirectoryFlag{
|
||||
Name: "ethash.cachedir",
|
||||
@@ -390,6 +359,16 @@ var (
|
||||
Usage: "Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)",
|
||||
Value: 15,
|
||||
}
|
||||
CacheTrieJournalFlag = cli.StringFlag{
|
||||
Name: "cache.trie.journal",
|
||||
Usage: "Disk journal directory for trie cache to survive node restarts",
|
||||
Value: eth.DefaultConfig.TrieCleanCacheJournal,
|
||||
}
|
||||
CacheTrieRejournalFlag = cli.DurationFlag{
|
||||
Name: "cache.trie.rejournal",
|
||||
Usage: "Time interval to regenerate the trie cache journal",
|
||||
Value: eth.DefaultConfig.TrieCleanCacheRejournal,
|
||||
}
|
||||
CacheGCFlag = cli.IntFlag{
|
||||
Name: "cache.gc",
|
||||
Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)",
|
||||
@@ -536,6 +515,20 @@ var (
|
||||
Usage: "API's offered over the HTTP-RPC interface",
|
||||
Value: "",
|
||||
}
|
||||
GraphQLEnabledFlag = cli.BoolFlag{
|
||||
Name: "graphql",
|
||||
Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.",
|
||||
}
|
||||
GraphQLCORSDomainFlag = cli.StringFlag{
|
||||
Name: "graphql.corsdomain",
|
||||
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
|
||||
Value: "",
|
||||
}
|
||||
GraphQLVirtualHostsFlag = cli.StringFlag{
|
||||
Name: "graphql.vhosts",
|
||||
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
|
||||
Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","),
|
||||
}
|
||||
WSEnabledFlag = cli.BoolFlag{
|
||||
Name: "ws",
|
||||
Usage: "Enable the WS-RPC server",
|
||||
@@ -560,30 +553,6 @@ var (
|
||||
Usage: "Origins from which to accept websockets requests",
|
||||
Value: "",
|
||||
}
|
||||
GraphQLEnabledFlag = cli.BoolFlag{
|
||||
Name: "graphql",
|
||||
Usage: "Enable the GraphQL server",
|
||||
}
|
||||
GraphQLListenAddrFlag = cli.StringFlag{
|
||||
Name: "graphql.addr",
|
||||
Usage: "GraphQL server listening interface",
|
||||
Value: node.DefaultGraphQLHost,
|
||||
}
|
||||
GraphQLPortFlag = cli.IntFlag{
|
||||
Name: "graphql.port",
|
||||
Usage: "GraphQL server listening port",
|
||||
Value: node.DefaultGraphQLPort,
|
||||
}
|
||||
GraphQLCORSDomainFlag = cli.StringFlag{
|
||||
Name: "graphql.corsdomain",
|
||||
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
|
||||
Value: "",
|
||||
}
|
||||
GraphQLVirtualHostsFlag = cli.StringFlag{
|
||||
Name: "graphql.vhosts",
|
||||
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
|
||||
Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","),
|
||||
}
|
||||
ExecFlag = cli.StringFlag{
|
||||
Name: "exec",
|
||||
Usage: "Execute JavaScript statement",
|
||||
@@ -669,12 +638,12 @@ var (
|
||||
WhisperMaxMessageSizeFlag = cli.IntFlag{
|
||||
Name: "shh.maxmessagesize",
|
||||
Usage: "Max message size accepted",
|
||||
Value: int(whisper.DefaultMaxMessageSize),
|
||||
Value: 1024 * 1024,
|
||||
}
|
||||
WhisperMinPOWFlag = cli.Float64Flag{
|
||||
Name: "shh.pow",
|
||||
Usage: "Minimum POW accepted",
|
||||
Value: whisper.DefaultMinimumPoW,
|
||||
Value: 0.2,
|
||||
}
|
||||
WhisperRestrictConnectionBetweenLightClientsFlag = cli.BoolFlag{
|
||||
Name: "shh.restrict-light",
|
||||
@@ -970,13 +939,6 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
|
||||
// setGraphQL creates the GraphQL listener interface string from the set
|
||||
// command line flags, returning empty if the GraphQL endpoint is disabled.
|
||||
func setGraphQL(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.GlobalBool(GraphQLEnabledFlag.Name) && cfg.GraphQLHost == "" {
|
||||
cfg.GraphQLHost = "127.0.0.1"
|
||||
if ctx.GlobalIsSet(GraphQLListenAddrFlag.Name) {
|
||||
cfg.GraphQLHost = ctx.GlobalString(GraphQLListenAddrFlag.Name)
|
||||
}
|
||||
}
|
||||
cfg.GraphQLPort = ctx.GlobalInt(GraphQLPortFlag.Name)
|
||||
if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) {
|
||||
cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name))
|
||||
}
|
||||
@@ -1070,6 +1032,9 @@ func setLes(ctx *cli.Context, cfg *eth.Config) {
|
||||
if ctx.GlobalIsSet(UltraLightOnlyAnnounceFlag.Name) {
|
||||
cfg.UltraLightOnlyAnnounce = ctx.GlobalBool(UltraLightOnlyAnnounceFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(LightNoPruneFlag.Name) {
|
||||
cfg.LightNoPrune = ctx.GlobalBool(LightNoPruneFlag.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// makeDatabaseHandles raises out the number of allowed file handles per process
|
||||
@@ -1499,15 +1464,12 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) {
|
||||
}
|
||||
|
||||
// SetShhConfig applies shh-related command line flags to the config.
|
||||
func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
|
||||
if ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) {
|
||||
cfg.MaxMessageSize = uint32(ctx.GlobalUint(WhisperMaxMessageSizeFlag.Name))
|
||||
}
|
||||
if ctx.GlobalIsSet(WhisperMinPOWFlag.Name) {
|
||||
cfg.MinimumAcceptedPOW = ctx.GlobalFloat64(WhisperMinPOWFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) {
|
||||
cfg.RestrictConnectionBetweenLightClients = true
|
||||
func SetShhConfig(ctx *cli.Context, stack *node.Node) {
|
||||
if ctx.GlobalIsSet(WhisperEnabledFlag.Name) ||
|
||||
ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) ||
|
||||
ctx.GlobalIsSet(WhisperMinPOWFlag.Name) ||
|
||||
ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) {
|
||||
log.Warn("Whisper support has been deprecated and the code has been moved to github.com/ethereum/whisper")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1563,6 +1525,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
|
||||
cfg.TrieCleanCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
|
||||
}
|
||||
if ctx.GlobalIsSet(CacheTrieJournalFlag.Name) {
|
||||
cfg.TrieCleanCacheJournal = ctx.GlobalString(CacheTrieJournalFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(CacheTrieRejournalFlag.Name) {
|
||||
cfg.TrieCleanCacheRejournal = ctx.GlobalDuration(CacheTrieRejournalFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
|
||||
cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
|
||||
}
|
||||
@@ -1570,6 +1538,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
|
||||
}
|
||||
if !ctx.GlobalIsSet(SnapshotFlag.Name) {
|
||||
cfg.TrieCleanCache += cfg.SnapshotCache
|
||||
cfg.SnapshotCache = 0 // Disabled
|
||||
}
|
||||
if ctx.GlobalIsSet(DocRootFlag.Name) {
|
||||
@@ -1638,23 +1607,43 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
}
|
||||
// Create new developer account or reuse existing one
|
||||
var (
|
||||
developer accounts.Account
|
||||
err error
|
||||
developer accounts.Account
|
||||
passphrase string
|
||||
err error
|
||||
)
|
||||
if accs := ks.Accounts(); len(accs) > 0 {
|
||||
if list := MakePasswordList(ctx); len(list) > 0 {
|
||||
// Just take the first value. Although the function returns a possible multiple values and
|
||||
// some usages iterate through them as attempts, that doesn't make sense in this setting,
|
||||
// when we're definitely concerned with only one account.
|
||||
passphrase = list[0]
|
||||
}
|
||||
// setEtherbase has been called above, configuring the miner address from command line flags.
|
||||
if cfg.Miner.Etherbase != (common.Address{}) {
|
||||
developer = accounts.Account{Address: cfg.Miner.Etherbase}
|
||||
} else if accs := ks.Accounts(); len(accs) > 0 {
|
||||
developer = ks.Accounts()[0]
|
||||
} else {
|
||||
developer, err = ks.NewAccount("")
|
||||
developer, err = ks.NewAccount(passphrase)
|
||||
if err != nil {
|
||||
Fatalf("Failed to create developer account: %v", err)
|
||||
}
|
||||
}
|
||||
if err := ks.Unlock(developer, ""); err != nil {
|
||||
if err := ks.Unlock(developer, passphrase); err != nil {
|
||||
Fatalf("Failed to unlock developer account: %v", err)
|
||||
}
|
||||
log.Info("Using developer account", "address", developer.Address)
|
||||
|
||||
// Create a new developer genesis block or reuse existing one
|
||||
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||
if ctx.GlobalIsSet(DataDirFlag.Name) {
|
||||
// Check if we have an already initialized chain and fall back to
|
||||
// that if so. Otherwise we need to generate a new genesis spec.
|
||||
chaindb := MakeChainDatabase(ctx, stack)
|
||||
if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
|
||||
cfg.Genesis = nil // fallback to db content
|
||||
}
|
||||
chaindb.Close()
|
||||
}
|
||||
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) {
|
||||
cfg.Miner.GasPrice = big.NewInt(1)
|
||||
}
|
||||
@@ -1682,70 +1671,39 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) {
|
||||
}
|
||||
|
||||
// RegisterEthService adds an Ethereum client to the stack.
|
||||
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||
var err error
|
||||
func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend {
|
||||
if cfg.SyncMode == downloader.LightSync {
|
||||
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return les.New(ctx, cfg)
|
||||
})
|
||||
backend, err := les.New(stack, cfg)
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
}
|
||||
return backend.ApiBackend
|
||||
} else {
|
||||
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
fullNode, err := eth.New(ctx, cfg)
|
||||
if fullNode != nil && cfg.LightServ > 0 {
|
||||
ls, _ := les.NewLesServer(fullNode, cfg)
|
||||
fullNode.AddLesServer(ls)
|
||||
backend, err := eth.New(stack, cfg)
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
}
|
||||
if cfg.LightServ > 0 {
|
||||
_, err := les.NewLesServer(stack, backend, cfg)
|
||||
if err != nil {
|
||||
Fatalf("Failed to create the LES server: %v", err)
|
||||
}
|
||||
return fullNode, err
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterShhService configures Whisper and adds it to the given node.
|
||||
func RegisterShhService(stack *node.Node, cfg *whisper.Config) {
|
||||
if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) {
|
||||
return whisper.New(cfg), nil
|
||||
}); err != nil {
|
||||
Fatalf("Failed to register the Whisper service: %v", err)
|
||||
}
|
||||
return backend.APIBackend
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to
|
||||
// the given node.
|
||||
func RegisterEthStatsService(stack *node.Node, url string) {
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
// Retrieve both eth and les services
|
||||
var ethServ *eth.Ethereum
|
||||
ctx.Service(ðServ)
|
||||
|
||||
var lesServ *les.LightEthereum
|
||||
ctx.Service(&lesServ)
|
||||
|
||||
// Let ethstats use whichever is not nil
|
||||
return ethstats.New(url, ethServ, lesServ)
|
||||
}); err != nil {
|
||||
func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) {
|
||||
if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil {
|
||||
Fatalf("Failed to register the Ethereum Stats service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterGraphQLService is a utility function to construct a new service and register it against a node.
|
||||
func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) {
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
// Try to construct the GraphQL service backed by a full node
|
||||
var ethServ *eth.Ethereum
|
||||
if err := ctx.Service(ðServ); err == nil {
|
||||
return graphql.New(ethServ.APIBackend, endpoint, cors, vhosts, timeouts)
|
||||
}
|
||||
// Try to construct the GraphQL service backed by a light node
|
||||
var lesServ *les.LightEthereum
|
||||
if err := ctx.Service(&lesServ); err == nil {
|
||||
return graphql.New(lesServ.ApiBackend, endpoint, cors, vhosts, timeouts)
|
||||
}
|
||||
// Well, this should not have happened, bail out
|
||||
return nil, errors.New("no Ethereum service")
|
||||
}); err != nil {
|
||||
func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) {
|
||||
if err := graphql.New(stack, backend, cfg.GraphQLCors, cfg.GraphQLVirtualHosts); err != nil {
|
||||
Fatalf("Failed to register the GraphQL service: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1800,12 +1758,17 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
|
||||
var (
|
||||
cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
|
||||
handles = makeDatabaseHandles()
|
||||
|
||||
err error
|
||||
chainDb ethdb.Database
|
||||
)
|
||||
name := "chaindata"
|
||||
if ctx.GlobalString(SyncModeFlag.Name) == "light" {
|
||||
name = "lightchaindata"
|
||||
name := "lightchaindata"
|
||||
chainDb, err = stack.OpenDatabase(name, cache, handles, "")
|
||||
} else {
|
||||
name := "chaindata"
|
||||
chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "")
|
||||
}
|
||||
chainDb, err := stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "")
|
||||
if err != nil {
|
||||
Fatalf("Could not open database: %v", err)
|
||||
}
|
||||
|
@@ -89,6 +89,8 @@ var (
|
||||
Name: "testnet",
|
||||
Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)",
|
||||
}
|
||||
|
||||
// (Deprecated May 2020, shown in aliased flags section)
|
||||
LegacyRPCEnabledFlag = cli.BoolFlag{
|
||||
Name: "rpc",
|
||||
Usage: "Enable the HTTP-RPC server (deprecated, use --http)",
|
||||
@@ -158,6 +160,17 @@ var (
|
||||
Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) (deprecated, use --bootnodes)",
|
||||
Value: "",
|
||||
}
|
||||
|
||||
// (Deprecated July 2020, shown in aliased flags section)
|
||||
LegacyGraphQLListenAddrFlag = cli.StringFlag{
|
||||
Name: "graphql.addr",
|
||||
Usage: "GraphQL server listening interface (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)",
|
||||
}
|
||||
LegacyGraphQLPortFlag = cli.IntFlag{
|
||||
Name: "graphql.port",
|
||||
Usage: "GraphQL server listening port (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)",
|
||||
Value: node.DefaultHTTPPort,
|
||||
}
|
||||
)
|
||||
|
||||
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
||||
|
@@ -1,774 +0,0 @@
|
||||
// Copyright 2017 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/>.
|
||||
|
||||
// This is a simple Whisper node. It could be used as a stand-alone bootstrap node.
|
||||
// Also, could be used for different test and diagnostics purposes.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/console/prompt"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/whisper/mailserver"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
const quitCommand = "~Q"
|
||||
const entropySize = 32
|
||||
|
||||
// singletons
|
||||
var (
|
||||
server *p2p.Server
|
||||
shh *whisper.Whisper
|
||||
done chan struct{}
|
||||
mailServer mailserver.WMailServer
|
||||
entropy [entropySize]byte
|
||||
|
||||
input = bufio.NewReader(os.Stdin)
|
||||
)
|
||||
|
||||
// encryption
|
||||
var (
|
||||
symKey []byte
|
||||
pub *ecdsa.PublicKey
|
||||
asymKey *ecdsa.PrivateKey
|
||||
nodeid *ecdsa.PrivateKey
|
||||
topic whisper.TopicType
|
||||
|
||||
asymKeyID string
|
||||
asymFilterID string
|
||||
symFilterID string
|
||||
symPass string
|
||||
msPassword string
|
||||
)
|
||||
|
||||
// cmd arguments
|
||||
var (
|
||||
bootstrapMode = flag.Bool("standalone", false, "boostrap node: don't initiate connection to peers, just wait for incoming connections")
|
||||
forwarderMode = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither encrypt nor decrypt messages")
|
||||
mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand")
|
||||
requestMail = flag.Bool("mailclient", false, "request expired messages from the bootstrap server")
|
||||
asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption")
|
||||
generateKey = flag.Bool("generatekey", false, "generate and show the private key")
|
||||
fileExMode = flag.Bool("fileexchange", false, "file exchange mode")
|
||||
fileReader = flag.Bool("filereader", false, "load and decrypt messages saved as files, display as plain text")
|
||||
testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics (password, etc.)")
|
||||
echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics")
|
||||
|
||||
argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level")
|
||||
argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
|
||||
argWorkTime = flag.Uint("work", 5, "work time in seconds")
|
||||
argMaxSize = flag.Uint("maxsize", uint(whisper.DefaultMaxMessageSize), "max size of message")
|
||||
argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)")
|
||||
argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request")
|
||||
|
||||
argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)")
|
||||
argPub = flag.String("pub", "", "public key for asymmetric encryption")
|
||||
argDBPath = flag.String("dbpath", "", "path to the server's DB directory")
|
||||
argIDFile = flag.String("idfile", "", "file name with node id (private key)")
|
||||
argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)")
|
||||
argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)")
|
||||
argSaveDir = flag.String("savedir", "", "directory where all incoming messages will be saved as files")
|
||||
)
|
||||
|
||||
func main() {
|
||||
processArgs()
|
||||
initialize()
|
||||
run()
|
||||
shutdown()
|
||||
}
|
||||
|
||||
func processArgs() {
|
||||
flag.Parse()
|
||||
|
||||
if len(*argIDFile) > 0 {
|
||||
var err error
|
||||
nodeid, err = crypto.LoadECDSA(*argIDFile)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to load file [%s]: %s.", *argIDFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
const enodePrefix = "enode://"
|
||||
if len(*argEnode) > 0 {
|
||||
if (*argEnode)[:len(enodePrefix)] != enodePrefix {
|
||||
*argEnode = enodePrefix + *argEnode
|
||||
}
|
||||
}
|
||||
|
||||
if len(*argTopic) > 0 {
|
||||
x, err := hex.DecodeString(*argTopic)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to parse the topic: %s", err)
|
||||
}
|
||||
topic = whisper.BytesToTopic(x)
|
||||
}
|
||||
|
||||
if *asymmetricMode && len(*argPub) > 0 {
|
||||
var err error
|
||||
if pub, err = crypto.UnmarshalPubkey(common.FromHex(*argPub)); err != nil {
|
||||
utils.Fatalf("invalid public key")
|
||||
}
|
||||
}
|
||||
|
||||
if len(*argSaveDir) > 0 {
|
||||
if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) {
|
||||
utils.Fatalf("Download directory '%s' does not exist", *argSaveDir)
|
||||
}
|
||||
} else if *fileExMode {
|
||||
utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode")
|
||||
}
|
||||
|
||||
if *echoMode {
|
||||
echo()
|
||||
}
|
||||
}
|
||||
|
||||
func echo() {
|
||||
fmt.Printf("ttl = %d \n", *argTTL)
|
||||
fmt.Printf("workTime = %d \n", *argWorkTime)
|
||||
fmt.Printf("pow = %f \n", *argPoW)
|
||||
fmt.Printf("mspow = %f \n", *argServerPoW)
|
||||
fmt.Printf("ip = %s \n", *argIP)
|
||||
fmt.Printf("pub = %s \n", hexutil.Encode(crypto.FromECDSAPub(pub)))
|
||||
fmt.Printf("idfile = %s \n", *argIDFile)
|
||||
fmt.Printf("dbpath = %s \n", *argDBPath)
|
||||
fmt.Printf("boot = %s \n", *argEnode)
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||
|
||||
done = make(chan struct{})
|
||||
var peers []*enode.Node
|
||||
var err error
|
||||
|
||||
if *generateKey {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate private key: %s", err)
|
||||
}
|
||||
k := hex.EncodeToString(crypto.FromECDSA(key))
|
||||
fmt.Printf("Random private key: %s \n", k)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *testMode {
|
||||
symPass = "wwww" // ascii code: 0x77777777
|
||||
msPassword = "wwww"
|
||||
}
|
||||
|
||||
if *bootstrapMode {
|
||||
if len(*argIP) == 0 {
|
||||
argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ")
|
||||
}
|
||||
} else if *fileReader {
|
||||
*bootstrapMode = true
|
||||
} else {
|
||||
if len(*argEnode) == 0 {
|
||||
argEnode = scanLineA("Please enter the peer's enode: ")
|
||||
}
|
||||
peer := enode.MustParse(*argEnode)
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
|
||||
if *mailServerMode {
|
||||
if len(msPassword) == 0 {
|
||||
msPassword, err = prompt.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &whisper.Config{
|
||||
MaxMessageSize: uint32(*argMaxSize),
|
||||
MinimumAcceptedPOW: *argPoW,
|
||||
}
|
||||
|
||||
shh = whisper.New(cfg)
|
||||
|
||||
if *argPoW != whisper.DefaultMinimumPoW {
|
||||
err := shh.SetMinimumPoW(*argPoW)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to set PoW: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if uint32(*argMaxSize) != whisper.DefaultMaxMessageSize {
|
||||
err := shh.SetMaxMessageSize(uint32(*argMaxSize))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to set max message size: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
asymKeyID, err = shh.NewKeyPair()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate a new key pair: %s", err)
|
||||
}
|
||||
|
||||
asymKey, err = shh.GetPrivateKey(asymKeyID)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to retrieve a new key pair: %s", err)
|
||||
}
|
||||
|
||||
if nodeid == nil {
|
||||
tmpID, err := shh.NewKeyPair()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate a new key pair: %s", err)
|
||||
}
|
||||
|
||||
nodeid, err = shh.GetPrivateKey(tmpID)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to retrieve a new key pair: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
maxPeers := 80
|
||||
if *bootstrapMode {
|
||||
maxPeers = 800
|
||||
}
|
||||
|
||||
_, err = crand.Read(entropy[:])
|
||||
if err != nil {
|
||||
utils.Fatalf("crypto/rand failed: %s", err)
|
||||
}
|
||||
|
||||
if *mailServerMode {
|
||||
shh.RegisterServer(&mailServer)
|
||||
if err := mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW); err != nil {
|
||||
utils.Fatalf("Failed to init MailServer: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
server = &p2p.Server{
|
||||
Config: p2p.Config{
|
||||
PrivateKey: nodeid,
|
||||
MaxPeers: maxPeers,
|
||||
Name: common.MakeName("wnode", "6.0"),
|
||||
Protocols: shh.Protocols(),
|
||||
ListenAddr: *argIP,
|
||||
NAT: nat.Any(),
|
||||
BootstrapNodes: peers,
|
||||
StaticNodes: peers,
|
||||
TrustedNodes: peers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func startServer() error {
|
||||
err := server.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start Whisper peer: %s.", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("my public key: %s \n", hexutil.Encode(crypto.FromECDSAPub(&asymKey.PublicKey)))
|
||||
fmt.Println(server.NodeInfo().Enode)
|
||||
|
||||
if *bootstrapMode {
|
||||
configureNode()
|
||||
fmt.Println("Bootstrap Whisper node started")
|
||||
} else {
|
||||
fmt.Println("Whisper node started")
|
||||
// first see if we can establish connection, then ask for user input
|
||||
waitForConnection(true)
|
||||
configureNode()
|
||||
}
|
||||
|
||||
if *fileExMode {
|
||||
fmt.Printf("Please type the file name to be send. To quit type: '%s'\n", quitCommand)
|
||||
} else if *fileReader {
|
||||
fmt.Printf("Please type the file name to be decrypted. To quit type: '%s'\n", quitCommand)
|
||||
} else if !*forwarderMode {
|
||||
fmt.Printf("Please type the message. To quit type: '%s'\n", quitCommand)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureNode() {
|
||||
var err error
|
||||
var p2pAccept bool
|
||||
|
||||
if *forwarderMode {
|
||||
return
|
||||
}
|
||||
|
||||
if *asymmetricMode {
|
||||
if len(*argPub) == 0 {
|
||||
s := scanLine("Please enter the peer's public key: ")
|
||||
b := common.FromHex(s)
|
||||
if b == nil {
|
||||
utils.Fatalf("Error: can not convert hexadecimal string")
|
||||
}
|
||||
if pub, err = crypto.UnmarshalPubkey(b); err != nil {
|
||||
utils.Fatalf("Error: invalid peer public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *requestMail {
|
||||
p2pAccept = true
|
||||
if len(msPassword) == 0 {
|
||||
msPassword, err = prompt.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !*asymmetricMode && !*forwarderMode {
|
||||
if len(symPass) == 0 {
|
||||
symPass, err = prompt.Stdin.PromptPassword("Please enter the password for symmetric encryption: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read password: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
symKeyID, err := shh.AddSymKeyFromPassword(symPass)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to create symmetric key: %s", err)
|
||||
}
|
||||
symKey, err = shh.GetSymKey(symKeyID)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to save symmetric key: %s", err)
|
||||
}
|
||||
if len(*argTopic) == 0 {
|
||||
generateTopic([]byte(symPass))
|
||||
}
|
||||
|
||||
fmt.Printf("Filter is configured for the topic: %x \n", topic)
|
||||
}
|
||||
|
||||
if *mailServerMode {
|
||||
if len(*argDBPath) == 0 {
|
||||
argDBPath = scanLineA("Please enter the path to DB file: ")
|
||||
}
|
||||
}
|
||||
|
||||
symFilter := whisper.Filter{
|
||||
KeySym: symKey,
|
||||
Topics: [][]byte{topic[:]},
|
||||
AllowP2P: p2pAccept,
|
||||
}
|
||||
symFilterID, err = shh.Subscribe(&symFilter)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to install filter: %s", err)
|
||||
}
|
||||
|
||||
asymFilter := whisper.Filter{
|
||||
KeyAsym: asymKey,
|
||||
Topics: [][]byte{topic[:]},
|
||||
AllowP2P: p2pAccept,
|
||||
}
|
||||
asymFilterID, err = shh.Subscribe(&asymFilter)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to install filter: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateTopic(password []byte) {
|
||||
x := pbkdf2.Key(password, password, 4096, 128, sha512.New)
|
||||
for i := 0; i < len(x); i++ {
|
||||
topic[i%whisper.TopicLength] ^= x[i]
|
||||
}
|
||||
}
|
||||
|
||||
func waitForConnection(timeout bool) {
|
||||
var cnt int
|
||||
var connected bool
|
||||
for !connected {
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
connected = server.PeerCount() > 0
|
||||
if timeout {
|
||||
cnt++
|
||||
if cnt > 1000 {
|
||||
utils.Fatalf("Timeout expired, failed to connect")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Connected to peer.")
|
||||
}
|
||||
|
||||
func run() {
|
||||
err := startServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer server.Stop()
|
||||
shh.Start(nil)
|
||||
defer shh.Stop()
|
||||
|
||||
if !*forwarderMode {
|
||||
go messageLoop()
|
||||
}
|
||||
|
||||
if *requestMail {
|
||||
requestExpiredMessagesLoop()
|
||||
} else if *fileExMode {
|
||||
sendFilesLoop()
|
||||
} else if *fileReader {
|
||||
fileReaderLoop()
|
||||
} else {
|
||||
sendLoop()
|
||||
}
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
close(done)
|
||||
mailServer.Close()
|
||||
}
|
||||
|
||||
func sendLoop() {
|
||||
for {
|
||||
s := scanLine("")
|
||||
if s == quitCommand {
|
||||
fmt.Println("Quit command received")
|
||||
return
|
||||
}
|
||||
sendMsg([]byte(s))
|
||||
if *asymmetricMode {
|
||||
// print your own message for convenience,
|
||||
// because in asymmetric mode it is impossible to decrypt it
|
||||
timestamp := time.Now().Unix()
|
||||
from := crypto.PubkeyToAddress(asymKey.PublicKey)
|
||||
fmt.Printf("\n%d <%x>: %s\n", timestamp, from, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendFilesLoop() {
|
||||
for {
|
||||
s := scanLine("")
|
||||
if s == quitCommand {
|
||||
fmt.Println("Quit command received")
|
||||
return
|
||||
}
|
||||
b, err := ioutil.ReadFile(s)
|
||||
if err != nil {
|
||||
fmt.Printf(">>> Error: %s \n", err)
|
||||
} else {
|
||||
h := sendMsg(b)
|
||||
if (h == common.Hash{}) {
|
||||
fmt.Printf(">>> Error: message was not sent \n")
|
||||
} else {
|
||||
timestamp := time.Now().Unix()
|
||||
from := crypto.PubkeyToAddress(asymKey.PublicKey)
|
||||
fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fileReaderLoop() {
|
||||
watcher1 := shh.GetFilter(symFilterID)
|
||||
watcher2 := shh.GetFilter(asymFilterID)
|
||||
if watcher1 == nil && watcher2 == nil {
|
||||
fmt.Println("Error: neither symmetric nor asymmetric filter is installed")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
s := scanLine("")
|
||||
if s == quitCommand {
|
||||
fmt.Println("Quit command received")
|
||||
return
|
||||
}
|
||||
raw, err := ioutil.ReadFile(s)
|
||||
if err != nil {
|
||||
fmt.Printf(">>> Error: %s \n", err)
|
||||
} else {
|
||||
env := whisper.Envelope{Data: raw} // the topic is zero
|
||||
msg := env.Open(watcher1) // force-open envelope regardless of the topic
|
||||
if msg == nil {
|
||||
msg = env.Open(watcher2)
|
||||
}
|
||||
if msg == nil {
|
||||
fmt.Printf(">>> Error: failed to decrypt the message \n")
|
||||
} else {
|
||||
printMessageInfo(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scanLine(prompt string) string {
|
||||
if len(prompt) > 0 {
|
||||
fmt.Print(prompt)
|
||||
}
|
||||
txt, err := input.ReadString('\n')
|
||||
if err != nil {
|
||||
utils.Fatalf("input error: %s", err)
|
||||
}
|
||||
txt = strings.TrimRight(txt, "\n\r")
|
||||
return txt
|
||||
}
|
||||
|
||||
func scanLineA(prompt string) *string {
|
||||
s := scanLine(prompt)
|
||||
return &s
|
||||
}
|
||||
|
||||
func scanUint(prompt string) uint32 {
|
||||
s := scanLine(prompt)
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
utils.Fatalf("Fail to parse the lower time limit: %s", err)
|
||||
}
|
||||
return uint32(i)
|
||||
}
|
||||
|
||||
func sendMsg(payload []byte) common.Hash {
|
||||
params := whisper.MessageParams{
|
||||
Src: asymKey,
|
||||
Dst: pub,
|
||||
KeySym: symKey,
|
||||
Payload: payload,
|
||||
Topic: topic,
|
||||
TTL: uint32(*argTTL),
|
||||
PoW: *argPoW,
|
||||
WorkTime: uint32(*argWorkTime),
|
||||
}
|
||||
|
||||
msg, err := whisper.NewSentMessage(¶ms)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed to create new message: %s", err)
|
||||
}
|
||||
|
||||
envelope, err := msg.Wrap(¶ms)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to seal message: %v \n", err)
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
err = shh.Send(envelope)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to send message: %v \n", err)
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
return envelope.Hash()
|
||||
}
|
||||
|
||||
func messageLoop() {
|
||||
sf := shh.GetFilter(symFilterID)
|
||||
if sf == nil {
|
||||
utils.Fatalf("symmetric filter is not installed")
|
||||
}
|
||||
|
||||
af := shh.GetFilter(asymFilterID)
|
||||
if af == nil {
|
||||
utils.Fatalf("asymmetric filter is not installed")
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
m1 := sf.Retrieve()
|
||||
m2 := af.Retrieve()
|
||||
messages := append(m1, m2...)
|
||||
for _, msg := range messages {
|
||||
reportedOnce := false
|
||||
if !*fileExMode && len(msg.Payload) <= 2048 {
|
||||
printMessageInfo(msg)
|
||||
reportedOnce = true
|
||||
}
|
||||
|
||||
// All messages are saved upon specifying argSaveDir.
|
||||
// fileExMode only specifies how messages are displayed on the console after they are saved.
|
||||
// if fileExMode == true, only the hashes are displayed, since messages might be too big.
|
||||
if len(*argSaveDir) > 0 {
|
||||
writeMessageToFile(*argSaveDir, msg, !reportedOnce)
|
||||
}
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printMessageInfo(msg *whisper.ReceivedMessage) {
|
||||
timestamp := fmt.Sprintf("%d", msg.Sent) // unix timestamp for diagnostics
|
||||
text := string(msg.Payload)
|
||||
|
||||
var address common.Address
|
||||
if msg.Src != nil {
|
||||
address = crypto.PubkeyToAddress(*msg.Src)
|
||||
}
|
||||
|
||||
if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) {
|
||||
fmt.Printf("\n%s <%x>: %s\n", timestamp, address, text) // message from myself
|
||||
} else {
|
||||
fmt.Printf("\n%s [%x]: %s\n", timestamp, address, text) // message from a peer
|
||||
}
|
||||
}
|
||||
|
||||
func writeMessageToFile(dir string, msg *whisper.ReceivedMessage, show bool) {
|
||||
if len(dir) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := fmt.Sprintf("%d", msg.Sent)
|
||||
name := fmt.Sprintf("%x", msg.EnvelopeHash)
|
||||
|
||||
var address common.Address
|
||||
if msg.Src != nil {
|
||||
address = crypto.PubkeyToAddress(*msg.Src)
|
||||
}
|
||||
|
||||
env := shh.GetEnvelope(msg.EnvelopeHash)
|
||||
if env == nil {
|
||||
fmt.Printf("\nUnexpected error: envelope not found: %x\n", msg.EnvelopeHash)
|
||||
return
|
||||
}
|
||||
|
||||
// this is a sample code; uncomment if you don't want to save your own messages.
|
||||
//if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) {
|
||||
// fmt.Printf("\n%s <%x>: message from myself received, not saved: '%s'\n", timestamp, address, name)
|
||||
// return
|
||||
//}
|
||||
|
||||
fullpath := filepath.Join(dir, name)
|
||||
err := ioutil.WriteFile(fullpath, env.Data, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err)
|
||||
} else if show {
|
||||
fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(env.Data))
|
||||
}
|
||||
}
|
||||
|
||||
func requestExpiredMessagesLoop() {
|
||||
var key, peerID, bloom []byte
|
||||
var timeLow, timeUpp uint32
|
||||
var t string
|
||||
var xt whisper.TopicType
|
||||
|
||||
keyID, err := shh.AddSymKeyFromPassword(msPassword)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
|
||||
}
|
||||
key, err = shh.GetSymKey(keyID)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to save symmetric key for mail request: %s", err)
|
||||
}
|
||||
peerID = extractIDFromEnode(*argEnode)
|
||||
shh.AllowP2PMessagesFromPeer(peerID)
|
||||
|
||||
for {
|
||||
timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
|
||||
timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ")
|
||||
t = scanLine("Enter the topic (hex). Press enter to request all messages, regardless of the topic: ")
|
||||
if len(t) == whisper.TopicLength*2 {
|
||||
x, err := hex.DecodeString(t)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to parse the topic: %s \n", err)
|
||||
continue
|
||||
}
|
||||
xt = whisper.BytesToTopic(x)
|
||||
bloom = whisper.TopicToBloom(xt)
|
||||
obfuscateBloom(bloom)
|
||||
} else if len(t) == 0 {
|
||||
bloom = whisper.MakeFullNodeBloom()
|
||||
} else {
|
||||
fmt.Println("Error: topic is invalid, request aborted")
|
||||
continue
|
||||
}
|
||||
|
||||
if timeUpp == 0 {
|
||||
timeUpp = 0xFFFFFFFF
|
||||
}
|
||||
|
||||
data := make([]byte, 8, 8+whisper.BloomFilterSize)
|
||||
binary.BigEndian.PutUint32(data, timeLow)
|
||||
binary.BigEndian.PutUint32(data[4:], timeUpp)
|
||||
data = append(data, bloom...)
|
||||
|
||||
var params whisper.MessageParams
|
||||
params.PoW = *argServerPoW
|
||||
params.Payload = data
|
||||
params.KeySym = key
|
||||
params.Src = asymKey
|
||||
params.WorkTime = 5
|
||||
|
||||
msg, err := whisper.NewSentMessage(¶ms)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed to create new message: %s", err)
|
||||
}
|
||||
env, err := msg.Wrap(¶ms)
|
||||
if err != nil {
|
||||
utils.Fatalf("Wrap failed: %s", err)
|
||||
}
|
||||
|
||||
err = shh.RequestHistoricMessages(peerID, env)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to send P2P message: %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}
|
||||
|
||||
func extractIDFromEnode(s string) []byte {
|
||||
n, err := enode.Parse(enode.ValidSchemes, s)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to parse node: %s", err)
|
||||
}
|
||||
return n.ID().Bytes()
|
||||
}
|
||||
|
||||
// obfuscateBloom adds 16 random bits to the bloom
|
||||
// filter, in order to obfuscate the containing topics.
|
||||
// it does so deterministically within every session.
|
||||
// despite additional bits, it will match on average
|
||||
// 32000 times less messages than full node's bloom filter.
|
||||
func obfuscateBloom(bloom []byte) {
|
||||
const half = entropySize / 2
|
||||
for i := 0; i < half; i++ {
|
||||
x := int(entropy[i])
|
||||
if entropy[half+i] < 128 {
|
||||
x += 256
|
||||
}
|
||||
|
||||
bloom[x/8] = 1 << uint(x%8) // set the bit number X
|
||||
}
|
||||
}
|
@@ -28,7 +28,7 @@ import (
|
||||
// API is a user facing RPC API to allow controlling the signer and voting
|
||||
// mechanisms of the proof-of-authority scheme.
|
||||
type API struct {
|
||||
chain consensus.ChainReader
|
||||
chain consensus.ChainHeaderReader
|
||||
clique *Clique
|
||||
}
|
||||
|
||||
|
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
@@ -137,9 +138,8 @@ var (
|
||||
errRecentlySigned = errors.New("recently signed")
|
||||
)
|
||||
|
||||
// SignerFn is a signer callback function to request a header to be signed by a
|
||||
// backing account.
|
||||
type SignerFn func(accounts.Account, string, []byte) ([]byte, error)
|
||||
// SignerFn hashes and signs the data to be signed by a backing account.
|
||||
type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error)
|
||||
|
||||
// ecrecover extracts the Ethereum account address from a signed header.
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
@@ -213,14 +213,14 @@ func (c *Clique) Author(header *types.Header) (common.Address, error) {
|
||||
}
|
||||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules.
|
||||
func (c *Clique) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
|
||||
func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
|
||||
return c.verifyHeader(chain, header, nil)
|
||||
}
|
||||
|
||||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
|
||||
// method returns a quit channel to abort the operations and a results channel to
|
||||
// retrieve the async verifications (the order is that of the input slice).
|
||||
func (c *Clique) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
abort := make(chan struct{})
|
||||
results := make(chan error, len(headers))
|
||||
|
||||
@@ -242,7 +242,7 @@ func (c *Clique) VerifyHeaders(chain consensus.ChainReader, headers []*types.Hea
|
||||
// caller may optionally pass in a batch of parents (ascending order) to avoid
|
||||
// looking those up from the database. This is useful for concurrently verifying
|
||||
// a batch of new headers.
|
||||
func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
|
||||
func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
|
||||
if header.Number == nil {
|
||||
return errUnknownBlock
|
||||
}
|
||||
@@ -305,7 +305,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header,
|
||||
// rather depend on a batch of previous headers. The caller may optionally pass
|
||||
// in a batch of parents (ascending order) to avoid looking those up from the
|
||||
// database. This is useful for concurrently verifying a batch of new headers.
|
||||
func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
|
||||
func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
|
||||
// The genesis block is the always valid dead-end
|
||||
number := header.Number.Uint64()
|
||||
if number == 0 {
|
||||
@@ -345,7 +345,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type
|
||||
}
|
||||
|
||||
// snapshot retrieves the authorization snapshot at a given point in time.
|
||||
func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
|
||||
func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
|
||||
// Search for a snapshot in memory or on disk for checkpoints
|
||||
var (
|
||||
headers []*types.Header
|
||||
@@ -369,7 +369,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
|
||||
// at a checkpoint block without a parent (light client CHT), or we have piled
|
||||
// up more headers than allowed to be reorged (chain reinit from a freezer),
|
||||
// consider the checkpoint trusted and snapshot it.
|
||||
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.ImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
|
||||
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
|
||||
checkpoint := chain.GetHeaderByNumber(number)
|
||||
if checkpoint != nil {
|
||||
hash := checkpoint.Hash()
|
||||
@@ -436,7 +436,7 @@ func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) e
|
||||
|
||||
// VerifySeal implements consensus.Engine, checking whether the signature contained
|
||||
// in the header satisfies the consensus protocol requirements.
|
||||
func (c *Clique) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||
func (c *Clique) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
return c.verifySeal(chain, header, nil)
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ func (c *Clique) VerifySeal(chain consensus.ChainReader, header *types.Header) e
|
||||
// consensus protocol requirements. The method accepts an optional list of parent
|
||||
// headers that aren't yet part of the local blockchain to generate the snapshots
|
||||
// from.
|
||||
func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
|
||||
func (c *Clique) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
|
||||
// Verifying the genesis block is not supported
|
||||
number := header.Number.Uint64()
|
||||
if number == 0 {
|
||||
@@ -487,7 +487,7 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p
|
||||
|
||||
// Prepare implements consensus.Engine, preparing all the consensus fields of the
|
||||
// header for running the transactions on top.
|
||||
func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||
func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
// If the block isn't a checkpoint, cast a random vote (good enough for now)
|
||||
header.Coinbase = common.Address{}
|
||||
header.Nonce = types.BlockNonce{}
|
||||
@@ -552,7 +552,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
|
||||
|
||||
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
|
||||
// rewards given.
|
||||
func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
|
||||
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
|
||||
// No block rewards in PoA, so the state remains as is and uncles are dropped
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
header.UncleHash = types.CalcUncleHash(nil)
|
||||
@@ -560,13 +560,13 @@ func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, sta
|
||||
|
||||
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||
// nor block rewards given, and returns the final block.
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
// No block rewards in PoA, so the state remains as is and uncles are dropped
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
header.UncleHash = types.CalcUncleHash(nil)
|
||||
|
||||
// Assemble and return the final block for sealing
|
||||
return types.NewBlock(header, txs, nil, receipts), nil
|
||||
return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil
|
||||
}
|
||||
|
||||
// Authorize injects a private key into the consensus engine to mint new blocks
|
||||
@@ -581,7 +581,7 @@ func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
|
||||
|
||||
// Seal implements consensus.Engine, attempting to create a sealed block using
|
||||
// the local signing credentials.
|
||||
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
header := block.Header()
|
||||
|
||||
// Sealing the genesis block is not supported
|
||||
@@ -654,7 +654,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
|
||||
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||
// that a new block should have based on the previous blocks in the chain and the
|
||||
// current signer.
|
||||
func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||
func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
||||
snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -684,7 +684,7 @@ func (c *Clique) Close() error {
|
||||
|
||||
// APIs implements consensus.Engine, returning the user facing RPC API to allow
|
||||
// controlling the signer voting.
|
||||
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
|
||||
func (c *Clique) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
return []rpc.API{{
|
||||
Namespace: "clique",
|
||||
Version: "1.0",
|
||||
|
@@ -27,9 +27,9 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// ChainReader defines a small collection of methods needed to access the local
|
||||
// blockchain during header and/or uncle verification.
|
||||
type ChainReader interface {
|
||||
// ChainHeaderReader defines a small collection of methods needed to access the local
|
||||
// blockchain during header verification.
|
||||
type ChainHeaderReader interface {
|
||||
// Config retrieves the blockchain's chain configuration.
|
||||
Config() *params.ChainConfig
|
||||
|
||||
@@ -44,6 +44,12 @@ type ChainReader interface {
|
||||
|
||||
// GetHeaderByHash retrieves a block header from the database by its hash.
|
||||
GetHeaderByHash(hash common.Hash) *types.Header
|
||||
}
|
||||
|
||||
// ChainReader defines a small collection of methods needed to access the local
|
||||
// blockchain during header and/or uncle verification.
|
||||
type ChainReader interface {
|
||||
ChainHeaderReader
|
||||
|
||||
// GetBlock retrieves a block from the database by hash and number.
|
||||
GetBlock(hash common.Hash, number uint64) *types.Block
|
||||
@@ -59,13 +65,13 @@ type Engine interface {
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules of a
|
||||
// given engine. Verifying the seal may be done optionally here, or explicitly
|
||||
// via the VerifySeal method.
|
||||
VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
|
||||
VerifyHeader(chain ChainHeaderReader, header *types.Header, seal bool) error
|
||||
|
||||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
|
||||
// concurrently. The method returns a quit channel to abort the operations and
|
||||
// a results channel to retrieve the async verifications (the order is that of
|
||||
// the input slice).
|
||||
VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
|
||||
VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
|
||||
|
||||
// VerifyUncles verifies that the given block's uncles conform to the consensus
|
||||
// rules of a given engine.
|
||||
@@ -73,18 +79,18 @@ type Engine interface {
|
||||
|
||||
// VerifySeal checks whether the crypto seal on a header is valid according to
|
||||
// the consensus rules of the given engine.
|
||||
VerifySeal(chain ChainReader, header *types.Header) error
|
||||
VerifySeal(chain ChainHeaderReader, header *types.Header) error
|
||||
|
||||
// Prepare initializes the consensus fields of a block header according to the
|
||||
// rules of a particular engine. The changes are executed inline.
|
||||
Prepare(chain ChainReader, header *types.Header) error
|
||||
Prepare(chain ChainHeaderReader, header *types.Header) error
|
||||
|
||||
// Finalize runs any post-transaction state modifications (e.g. block rewards)
|
||||
// but does not assemble the block.
|
||||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
|
||||
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
|
||||
uncles []*types.Header)
|
||||
|
||||
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
|
||||
@@ -92,7 +98,7 @@ type Engine interface {
|
||||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
FinalizeAndAssemble(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
|
||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
|
||||
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
@@ -100,17 +106,17 @@ type Engine interface {
|
||||
//
|
||||
// Note, the method returns immediately and will send the result async. More
|
||||
// than one result may also be returned depending on the consensus algorithm.
|
||||
Seal(chain ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
|
||||
Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
SealHash(header *types.Header) common.Hash
|
||||
|
||||
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||
// that a new block should have.
|
||||
CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
|
||||
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
|
||||
|
||||
// APIs returns the RPC APIs this consensus engine provides.
|
||||
APIs(chain ChainReader) []rpc.API
|
||||
APIs(chain ChainHeaderReader) []rpc.API
|
||||
|
||||
// Close terminates any background threads maintained by the consensus engine.
|
||||
Close() error
|
||||
|
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -86,7 +87,7 @@ func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
|
||||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules of the
|
||||
// stock Ethereum ethash engine.
|
||||
func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
|
||||
func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
|
||||
// If we're running a full engine faking, accept any input as valid
|
||||
if ethash.config.PowMode == ModeFullFake {
|
||||
return nil
|
||||
@@ -107,7 +108,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.He
|
||||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
|
||||
// concurrently. The method returns a quit channel to abort the operations and
|
||||
// a results channel to retrieve the async verifications.
|
||||
func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
// If we're running a full engine faking, accept any input as valid
|
||||
if ethash.config.PowMode == ModeFullFake || len(headers) == 0 {
|
||||
abort, results := make(chan struct{}), make(chan error, len(headers))
|
||||
@@ -169,7 +170,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*type
|
||||
return abort, errorsOut
|
||||
}
|
||||
|
||||
func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error {
|
||||
func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int) error {
|
||||
var parent *types.Header
|
||||
if index == 0 {
|
||||
parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1)
|
||||
@@ -243,7 +244,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo
|
||||
// verifyHeader checks whether a header conforms to the consensus rules of the
|
||||
// stock Ethereum ethash engine.
|
||||
// See YP section 4.3.4. "Block Header Validity"
|
||||
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
|
||||
func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error {
|
||||
// Ensure that the header's extra-data section is of a reasonable size
|
||||
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
|
||||
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
|
||||
@@ -306,7 +307,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
|
||||
// CalcDifficulty is the difficulty adjustment algorithm. It returns
|
||||
// the difficulty that a new block should have when created at time
|
||||
// given the parent block's time and difficulty.
|
||||
func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||
func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
||||
return CalcDifficulty(chain.Config(), time, parent)
|
||||
}
|
||||
|
||||
@@ -486,14 +487,14 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
|
||||
|
||||
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
||||
// the PoW difficulty requirements.
|
||||
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||
func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
return ethash.verifySeal(chain, header, false)
|
||||
}
|
||||
|
||||
// verifySeal checks whether a block satisfies the PoW difficulty requirements,
|
||||
// either using the usual ethash cache for it, or alternatively using a full DAG
|
||||
// to make remote mining fast.
|
||||
func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error {
|
||||
func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, fulldag bool) error {
|
||||
// If we're running a fake PoW, accept any seal as valid
|
||||
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
||||
time.Sleep(ethash.fakeDelay)
|
||||
@@ -558,7 +559,7 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Head
|
||||
|
||||
// Prepare implements consensus.Engine, initializing the difficulty field of a
|
||||
// header to conform to the ethash protocol. The changes are done inline.
|
||||
func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||
func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
|
||||
if parent == nil {
|
||||
return consensus.ErrUnknownAncestor
|
||||
@@ -569,7 +570,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
|
||||
|
||||
// Finalize implements consensus.Engine, accumulating the block and uncle rewards,
|
||||
// setting the final state on the header
|
||||
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
|
||||
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
|
||||
// Accumulate any block and uncle rewards and commit the final state root
|
||||
accumulateRewards(chain.Config(), state, header, uncles)
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
@@ -577,13 +578,13 @@ func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header
|
||||
|
||||
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||
// uncle rewards, setting the final state and assembling the block.
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
// Accumulate any block and uncle rewards and commit the final state root
|
||||
accumulateRewards(chain.Config(), state, header, uncles)
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// Header seems complete, assemble into a block and return
|
||||
return types.NewBlock(header, txs, uncles, receipts), nil
|
||||
return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil
|
||||
}
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
|
@@ -656,7 +656,7 @@ func (ethash *Ethash) Hashrate() float64 {
|
||||
}
|
||||
|
||||
// APIs implements consensus.Engine, returning the user facing RPC APIs.
|
||||
func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API {
|
||||
func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
// In order to ensure backward compatibility, we exposes ethash RPC APIs
|
||||
// to both eth and ethash namespaces.
|
||||
return []rpc.API{
|
||||
|
@@ -48,7 +48,7 @@ var (
|
||||
|
||||
// Seal implements consensus.Engine, attempting to find a nonce that satisfies
|
||||
// the block's difficulty requirements.
|
||||
func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
// If we're running a fake PoW, simply return a 0 nonce immediately
|
||||
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
||||
header := block.Header()
|
||||
|
@@ -306,9 +306,9 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) {
|
||||
}
|
||||
|
||||
// Send the request to the backend and return
|
||||
sign, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount"))
|
||||
sign, callable := goja.AssertFunction(getJeth(call.VM).Get("sign"))
|
||||
if !callable {
|
||||
return nil, fmt.Errorf("jeth.unlockAccount is not callable")
|
||||
return nil, fmt.Errorf("jeth.sign is not callable")
|
||||
}
|
||||
return sign(goja.Null(), message, account, passwd)
|
||||
}
|
||||
|
@@ -109,7 +109,8 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
|
||||
if confOverride != nil {
|
||||
confOverride(ethConf)
|
||||
}
|
||||
if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
|
||||
ethBackend, err := eth.New(stack, ethConf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register Ethereum protocol: %v", err)
|
||||
}
|
||||
// Start the node and assemble the JavaScript console around it
|
||||
@@ -135,13 +136,10 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
|
||||
t.Fatalf("failed to create JavaScript console: %v", err)
|
||||
}
|
||||
// Create the final tester and return
|
||||
var ethereum *eth.Ethereum
|
||||
stack.Service(ðereum)
|
||||
|
||||
return &tester{
|
||||
workspace: workspace,
|
||||
stack: stack,
|
||||
ethereum: ethereum,
|
||||
ethereum: ethBackend,
|
||||
console: console,
|
||||
input: prompter,
|
||||
output: printer,
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// BlockValidator is responsible for validating block headers, uncles and
|
||||
@@ -61,7 +62,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
|
||||
return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash)
|
||||
}
|
||||
if hash := types.DeriveSha(block.Transactions()); hash != header.TxHash {
|
||||
if hash := types.DeriveSha(block.Transactions(), new(trie.Trie)); hash != header.TxHash {
|
||||
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
|
||||
}
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
@@ -89,7 +90,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
|
||||
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
|
||||
}
|
||||
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]]))
|
||||
receiptSha := types.DeriveSha(receipts)
|
||||
receiptSha := types.DeriveSha(receipts, new(trie.Trie))
|
||||
if receiptSha != header.ReceiptHash {
|
||||
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
|
||||
}
|
||||
|
@@ -70,8 +70,11 @@ var (
|
||||
blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
|
||||
blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
|
||||
blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil)
|
||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
|
||||
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||
|
||||
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
|
||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
|
||||
blockReorgInvalidatedTx = metrics.NewRegisteredMeter("chain/reorg/invalidTx", nil)
|
||||
|
||||
blockPrefetchExecuteTimer = metrics.NewRegisteredTimer("chain/prefetch/executes", nil)
|
||||
blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
|
||||
@@ -109,13 +112,18 @@ const (
|
||||
// - Version 7
|
||||
// The following incompatible database changes were added:
|
||||
// * Use freezer as the ancient database to maintain all ancient data
|
||||
BlockChainVersion uint64 = 7
|
||||
// - Version 8
|
||||
// The following incompatible database changes were added:
|
||||
// * New scheme for contract code in order to separate the codes and trie nodes
|
||||
BlockChainVersion uint64 = 8
|
||||
)
|
||||
|
||||
// CacheConfig contains the configuration values for the trie caching/pruning
|
||||
// that's resident in a blockchain.
|
||||
type CacheConfig struct {
|
||||
TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
TrieCleanJournal string // Disk journal for saving clean cache entries.
|
||||
TrieCleanRejournal time.Duration // Time interval to dump clean cache to disk periodically
|
||||
TrieCleanNoPrefetch bool // Whether to disable heuristic state prefetching for followup blocks
|
||||
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
|
||||
TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node)
|
||||
@@ -125,6 +133,16 @@ type CacheConfig struct {
|
||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||
}
|
||||
|
||||
// defaultCacheConfig are the default caching values if none are specified by the
|
||||
// user (also used during testing).
|
||||
var defaultCacheConfig = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: true,
|
||||
}
|
||||
|
||||
// BlockChain represents the canonical chain given a database with a genesis
|
||||
// block. The Blockchain manages chain imports, reverts, chain reorganisations.
|
||||
//
|
||||
@@ -199,13 +217,7 @@ type BlockChain struct {
|
||||
// Processor.
|
||||
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool, txLookupLimit *uint64) (*BlockChain, error) {
|
||||
if cacheConfig == nil {
|
||||
cacheConfig = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: true,
|
||||
}
|
||||
cacheConfig = defaultCacheConfig
|
||||
}
|
||||
bodyCache, _ := lru.New(bodyCacheLimit)
|
||||
bodyRLPCache, _ := lru.New(bodyCacheLimit)
|
||||
@@ -220,7 +232,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit, cacheConfig.TrieCleanJournal),
|
||||
quit: make(chan struct{}),
|
||||
shouldPreserve: shouldPreserve,
|
||||
bodyCache: bodyCache,
|
||||
@@ -263,15 +275,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
txIndexBlock = frozen
|
||||
}
|
||||
}
|
||||
|
||||
if err := bc.loadLastState(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The first thing the node will do is reconstruct the verification data for
|
||||
// the head block (ethash cache or clique voting snapshot). Might as well do
|
||||
// it in advance.
|
||||
bc.engine.VerifyHeader(bc, bc.CurrentHeader(), true)
|
||||
|
||||
// Make sure the state associated with the block is available
|
||||
head := bc.CurrentBlock()
|
||||
if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash())
|
||||
if err := bc.SetHead(head.NumberU64()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Ensure that a previous crash in SetHead doesn't leave extra ancients
|
||||
if frozen, err := bc.db.Ancients(); err == nil && frozen > 0 {
|
||||
var (
|
||||
needRewind bool
|
||||
@@ -281,7 +296,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
// blockchain repair. If the head full block is even lower than the ancient
|
||||
// chain, truncate the ancient store.
|
||||
fullBlock := bc.CurrentBlock()
|
||||
if fullBlock != nil && fullBlock != bc.genesisBlock && fullBlock.NumberU64() < frozen-1 {
|
||||
if fullBlock != nil && fullBlock.Hash() != bc.genesisBlock.Hash() && fullBlock.NumberU64() < frozen-1 {
|
||||
needRewind = true
|
||||
low = fullBlock.NumberU64()
|
||||
}
|
||||
@@ -296,15 +311,17 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
}
|
||||
}
|
||||
if needRewind {
|
||||
var hashes []common.Hash
|
||||
previous := bc.CurrentHeader().Number.Uint64()
|
||||
for i := low + 1; i <= bc.CurrentHeader().Number.Uint64(); i++ {
|
||||
hashes = append(hashes, rawdb.ReadCanonicalHash(bc.db, i))
|
||||
log.Error("Truncating ancient chain", "from", bc.CurrentHeader().Number.Uint64(), "to", low)
|
||||
if err := bc.SetHead(low); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bc.Rollback(hashes)
|
||||
log.Warn("Truncate ancient chain", "from", previous, "to", low)
|
||||
}
|
||||
}
|
||||
// The first thing the node will do is reconstruct the verification data for
|
||||
// the head block (ethash cache or clique voting snapshot). Might as well do
|
||||
// it in advance.
|
||||
bc.engine.VerifyHeader(bc, bc.CurrentHeader(), true)
|
||||
|
||||
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
|
||||
for hash := range BadHashes {
|
||||
if header := bc.GetHeaderByHash(hash); header != nil {
|
||||
@@ -313,7 +330,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
// make sure the headerByNumber (if present) is in our current canonical chain
|
||||
if headerByNumber != nil && headerByNumber.Hash() == header.Hash() {
|
||||
log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
|
||||
bc.SetHead(header.Number.Uint64() - 1)
|
||||
if err := bc.SetHead(header.Number.Uint64() - 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Error("Chain rewind was successful, resuming normal operation")
|
||||
}
|
||||
}
|
||||
@@ -328,6 +347,19 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
bc.txLookupLimit = *txLookupLimit
|
||||
go bc.maintainTxIndex(txIndexBlock)
|
||||
}
|
||||
// If periodic cache journal is required, spin it up.
|
||||
if bc.cacheConfig.TrieCleanRejournal > 0 {
|
||||
if bc.cacheConfig.TrieCleanRejournal < time.Minute {
|
||||
log.Warn("Sanitizing invalid trie cache journal time", "provided", bc.cacheConfig.TrieCleanRejournal, "updated", time.Minute)
|
||||
bc.cacheConfig.TrieCleanRejournal = time.Minute
|
||||
}
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
bc.wg.Add(1)
|
||||
go func() {
|
||||
defer bc.wg.Done()
|
||||
triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit)
|
||||
}()
|
||||
}
|
||||
return bc, nil
|
||||
}
|
||||
|
||||
@@ -367,15 +399,6 @@ func (bc *BlockChain) loadLastState() error {
|
||||
log.Warn("Head block missing, resetting chain", "hash", head)
|
||||
return bc.Reset()
|
||||
}
|
||||
// Make sure the state associated with the block is available
|
||||
if _, err := state.New(currentBlock.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
// Dangling block without a state associated, init from scratch
|
||||
log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
|
||||
if err := bc.repair(¤tBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash())
|
||||
}
|
||||
// Everything seems to be fine, set as the head block
|
||||
bc.currentBlock.Store(currentBlock)
|
||||
headBlockGauge.Update(int64(currentBlock.NumberU64()))
|
||||
@@ -409,30 +432,48 @@ func (bc *BlockChain) loadLastState() error {
|
||||
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
|
||||
log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
|
||||
log.Info("Loaded most recent local fast block", "number", currentFastBlock.Number(), "hash", currentFastBlock.Hash(), "td", fastTd, "age", common.PrettyAge(time.Unix(int64(currentFastBlock.Time()), 0)))
|
||||
|
||||
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
|
||||
log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHead rewinds the local chain to a new head. In the case of headers, everything
|
||||
// above the new head will be deleted and the new one set. In the case of blocks
|
||||
// though, the head may be further rewound if block bodies are missing (non-archive
|
||||
// nodes after a fast sync).
|
||||
// SetHead rewinds the local chain to a new head. Depending on whether the node
|
||||
// was fast synced or full synced and in which state, the method will try to
|
||||
// delete minimal data from disk whilst retaining chain consistency.
|
||||
func (bc *BlockChain) SetHead(head uint64) error {
|
||||
log.Warn("Rewinding blockchain", "target", head)
|
||||
|
||||
bc.chainmu.Lock()
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) {
|
||||
// Rewind the block chain, ensuring we don't end up with a stateless head block
|
||||
if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() < currentBlock.NumberU64() {
|
||||
// Retrieve the last pivot block to short circuit rollbacks beyond it and the
|
||||
// current freezer limit to start nuking id underflown
|
||||
pivot := rawdb.ReadLastPivotNumber(bc.db)
|
||||
frozen, _ := bc.db.Ancients()
|
||||
|
||||
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (uint64, bool) {
|
||||
// Rewind the block chain, ensuring we don't end up with a stateless head
|
||||
// block. Note, depth equality is permitted to allow using SetHead as a
|
||||
// chain reparation mechanism without deleting any data!
|
||||
if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() <= currentBlock.NumberU64() {
|
||||
newHeadBlock := bc.GetBlock(header.Hash(), header.Number.Uint64())
|
||||
if newHeadBlock == nil {
|
||||
log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
|
||||
newHeadBlock = bc.genesisBlock
|
||||
} else {
|
||||
if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
// Rewound state missing, rolled back to before pivot, reset to genesis
|
||||
newHeadBlock = bc.genesisBlock
|
||||
// Block exists, keep rewinding until we find one with state
|
||||
for {
|
||||
if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
||||
if pivot == nil || newHeadBlock.NumberU64() > *pivot {
|
||||
newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1)
|
||||
continue
|
||||
} else {
|
||||
log.Trace("Rewind passed pivot, aiming genesis", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "pivot", *pivot)
|
||||
newHeadBlock = bc.genesisBlock
|
||||
}
|
||||
}
|
||||
log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
||||
break
|
||||
}
|
||||
}
|
||||
rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash())
|
||||
@@ -444,7 +485,6 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||
bc.currentBlock.Store(newHeadBlock)
|
||||
headBlockGauge.Update(int64(newHeadBlock.NumberU64()))
|
||||
}
|
||||
|
||||
// Rewind the fast block in a simpleton way to the target head
|
||||
if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock != nil && header.Number.Uint64() < currentFastBlock.NumberU64() {
|
||||
newHeadFastBlock := bc.GetBlock(header.Hash(), header.Number.Uint64())
|
||||
@@ -461,8 +501,17 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||
bc.currentFastBlock.Store(newHeadFastBlock)
|
||||
headFastBlockGauge.Update(int64(newHeadFastBlock.NumberU64()))
|
||||
}
|
||||
}
|
||||
head := bc.CurrentBlock().NumberU64()
|
||||
|
||||
// If setHead underflown the freezer threshold and the block processing
|
||||
// intent afterwards is full block importing, delete the chain segment
|
||||
// between the stateful-block and the sethead target.
|
||||
var wipe bool
|
||||
if head+1 < frozen {
|
||||
wipe = pivot == nil || head >= *pivot
|
||||
}
|
||||
return head, wipe // Only force wipe if full synced
|
||||
}
|
||||
// Rewind the header chain, deleting all block bodies until then
|
||||
delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
|
||||
// Ignore the error here since light client won't hit this path
|
||||
@@ -470,10 +519,9 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||
if num+1 <= frozen {
|
||||
// Truncate all relative data(header, total difficulty, body, receipt
|
||||
// and canonical hash) from ancient store.
|
||||
if err := bc.db.TruncateAncients(num + 1); err != nil {
|
||||
if err := bc.db.TruncateAncients(num); err != nil {
|
||||
log.Crit("Failed to truncate ancient data", "number", num, "err", err)
|
||||
}
|
||||
|
||||
// Remove the hash <-> number mapping from the active store.
|
||||
rawdb.DeleteHeaderNumber(db, hash)
|
||||
} else {
|
||||
@@ -485,8 +533,18 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||
}
|
||||
// Todo(rjl493456442) txlookup, bloombits, etc
|
||||
}
|
||||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
|
||||
// If SetHead was only called as a chain reparation method, try to skip
|
||||
// touching the header chain altogether, unless the freezer is broken
|
||||
if block := bc.CurrentBlock(); block.NumberU64() == head {
|
||||
if target, force := updateFn(bc.db, block.Header()); force {
|
||||
bc.hc.SetHead(target, updateFn, delFn)
|
||||
}
|
||||
} else {
|
||||
// Rewind the chain to the requested head and keep going backwards until a
|
||||
// block with a state is found or fast sync pivot is passed
|
||||
log.Warn("Rewinding blockchain", "target", head)
|
||||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
}
|
||||
// Clear out any stale content from the caches
|
||||
bc.bodyCache.Purge()
|
||||
bc.bodyRLPCache.Purge()
|
||||
@@ -609,28 +667,6 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// repair tries to repair the current blockchain by rolling back the current block
|
||||
// until one with associated state is found. This is needed to fix incomplete db
|
||||
// writes caused either by crashes/power outages, or simply non-committed tries.
|
||||
//
|
||||
// This method only rolls back the current block. The current header and current
|
||||
// fast block are left intact.
|
||||
func (bc *BlockChain) repair(head **types.Block) error {
|
||||
for {
|
||||
// Abort if we've rewound to a head block that does have associated state
|
||||
if _, err := state.New((*head).Root(), bc.stateCache, bc.snaps); err == nil {
|
||||
log.Info("Rewound blockchain to past state", "number", (*head).Number(), "hash", (*head).Hash())
|
||||
return nil
|
||||
}
|
||||
// Otherwise rewind one block and recheck state availability there
|
||||
block := bc.GetBlock((*head).ParentHash(), (*head).NumberU64()-1)
|
||||
if block == nil {
|
||||
return fmt.Errorf("missing block %d [%x]", (*head).NumberU64()-1, (*head).ParentHash())
|
||||
}
|
||||
*head = block
|
||||
}
|
||||
}
|
||||
|
||||
// Export writes the active chain to the given writer.
|
||||
func (bc *BlockChain) Export(w io.Writer) error {
|
||||
return bc.ExportN(w, uint64(0), bc.CurrentBlock().NumberU64())
|
||||
@@ -862,12 +898,30 @@ func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.
|
||||
return uncles
|
||||
}
|
||||
|
||||
// TrieNode retrieves a blob of data associated with a trie node (or code hash)
|
||||
// TrieNode retrieves a blob of data associated with a trie node
|
||||
// either from ephemeral in-memory cache, or from persistent storage.
|
||||
func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte, error) {
|
||||
return bc.stateCache.TrieDB().Node(hash)
|
||||
}
|
||||
|
||||
// ContractCode retrieves a blob of data associated with a contract hash
|
||||
// either from ephemeral in-memory cache, or from persistent storage.
|
||||
func (bc *BlockChain) ContractCode(hash common.Hash) ([]byte, error) {
|
||||
return bc.stateCache.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
|
||||
// ContractCodeWithPrefix retrieves a blob of data associated with a contract
|
||||
// hash either from ephemeral in-memory cache, or from persistent storage.
|
||||
//
|
||||
// If the code doesn't exist in the in-memory cache, check the storage with
|
||||
// new code scheme.
|
||||
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) {
|
||||
type codeReader interface {
|
||||
ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error)
|
||||
}
|
||||
return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash)
|
||||
}
|
||||
|
||||
// Stop stops the blockchain service. If any imports are currently in progress
|
||||
// it will abort them using the procInterrupt.
|
||||
func (bc *BlockChain) Stop() {
|
||||
@@ -901,14 +955,14 @@ func (bc *BlockChain) Stop() {
|
||||
recent := bc.GetBlockByNumber(number - offset)
|
||||
|
||||
log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
|
||||
if err := triedb.Commit(recent.Root(), true); err != nil {
|
||||
if err := triedb.Commit(recent.Root(), true, nil); err != nil {
|
||||
log.Error("Failed to commit recent state trie", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if snapBase != (common.Hash{}) {
|
||||
log.Info("Writing snapshot state to disk", "root", snapBase)
|
||||
if err := triedb.Commit(snapBase, true); err != nil {
|
||||
if err := triedb.Commit(snapBase, true, nil); err != nil {
|
||||
log.Error("Failed to commit recent state trie", "err", err)
|
||||
}
|
||||
}
|
||||
@@ -919,6 +973,12 @@ func (bc *BlockChain) Stop() {
|
||||
log.Error("Dangling trie nodes after full cleanup")
|
||||
}
|
||||
}
|
||||
// Ensure all live cached entries be saved into disk, so that we can skip
|
||||
// cache warmup when node restarts.
|
||||
if bc.cacheConfig.TrieCleanJournal != "" {
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
triedb.SaveCache(bc.cacheConfig.TrieCleanJournal)
|
||||
}
|
||||
log.Info("Blockchain stopped")
|
||||
}
|
||||
|
||||
@@ -961,52 +1021,6 @@ const (
|
||||
SideStatTy
|
||||
)
|
||||
|
||||
// Rollback is designed to remove a chain of links from the database that aren't
|
||||
// certain enough to be valid.
|
||||
func (bc *BlockChain) Rollback(chain []common.Hash) {
|
||||
bc.chainmu.Lock()
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
batch := bc.db.NewBatch()
|
||||
for i := len(chain) - 1; i >= 0; i-- {
|
||||
hash := chain[i]
|
||||
|
||||
// Degrade the chain markers if they are explicitly reverted.
|
||||
// In theory we should update all in-memory markers in the
|
||||
// last step, however the direction of rollback is from high
|
||||
// to low, so it's safe the update in-memory markers directly.
|
||||
currentHeader := bc.hc.CurrentHeader()
|
||||
if currentHeader.Hash() == hash {
|
||||
newHeadHeader := bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1)
|
||||
rawdb.WriteHeadHeaderHash(batch, currentHeader.ParentHash)
|
||||
bc.hc.SetCurrentHeader(newHeadHeader)
|
||||
}
|
||||
if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock.Hash() == hash {
|
||||
newFastBlock := bc.GetBlock(currentFastBlock.ParentHash(), currentFastBlock.NumberU64()-1)
|
||||
rawdb.WriteHeadFastBlockHash(batch, currentFastBlock.ParentHash())
|
||||
bc.currentFastBlock.Store(newFastBlock)
|
||||
headFastBlockGauge.Update(int64(newFastBlock.NumberU64()))
|
||||
}
|
||||
if currentBlock := bc.CurrentBlock(); currentBlock.Hash() == hash {
|
||||
newBlock := bc.GetBlock(currentBlock.ParentHash(), currentBlock.NumberU64()-1)
|
||||
rawdb.WriteHeadBlockHash(batch, currentBlock.ParentHash())
|
||||
bc.currentBlock.Store(newBlock)
|
||||
headBlockGauge.Update(int64(newBlock.NumberU64()))
|
||||
}
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to rollback chain markers", "err", err)
|
||||
}
|
||||
// Truncate ancient data which exceeds the current header.
|
||||
//
|
||||
// Notably, it can happen that system crashes without truncating the ancient data
|
||||
// but the head indicator has been updated in the active store. Regarding this issue,
|
||||
// system will self recovery by truncating the extra data during the setup phase.
|
||||
if err := bc.truncateAncient(bc.hc.CurrentHeader().Number.Uint64()); err != nil {
|
||||
log.Crit("Truncate ancient store failed", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// truncateAncient rewinds the blockchain to the specified header and deletes all
|
||||
// data in the ancient store that exceeds the specified header.
|
||||
func (bc *BlockChain) truncateAncient(head uint64) error {
|
||||
@@ -1263,6 +1277,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||
}
|
||||
// writeLive writes blockchain and corresponding receipt chain into active store.
|
||||
writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
|
||||
skipPresenceCheck := false
|
||||
batch := bc.db.NewBatch()
|
||||
for i, block := range blockChain {
|
||||
// Short circuit insertion if shutting down or processing failed
|
||||
@@ -1273,9 +1288,17 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||
if !bc.HasHeader(block.Hash(), block.NumberU64()) {
|
||||
return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
|
||||
}
|
||||
if bc.HasBlock(block.Hash(), block.NumberU64()) {
|
||||
stats.ignored++
|
||||
continue
|
||||
if !skipPresenceCheck {
|
||||
// Ignore if the entire data is already known
|
||||
if bc.HasBlock(block.Hash(), block.NumberU64()) {
|
||||
stats.ignored++
|
||||
continue
|
||||
} else {
|
||||
// If block N is not present, neither are the later blocks.
|
||||
// This should be true, but if we are mistaken, the shortcut
|
||||
// here will only cause overwriting of some existing data
|
||||
skipPresenceCheck = true
|
||||
}
|
||||
}
|
||||
// Write all the data out into the database
|
||||
rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body())
|
||||
@@ -1442,7 +1465,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
|
||||
// If we're running an archive node, always flush
|
||||
if bc.cacheConfig.TrieDirtyDisabled {
|
||||
if err := triedb.Commit(root, false); err != nil {
|
||||
if err := triedb.Commit(root, false, nil); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
} else {
|
||||
@@ -1476,7 +1499,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/TriesInMemory)
|
||||
}
|
||||
// Flush an entire trie and restart the counters
|
||||
triedb.Commit(header.Root, true)
|
||||
triedb.Commit(header.Root, true, nil)
|
||||
lastWrite = chosen
|
||||
bc.gcproc = 0
|
||||
}
|
||||
@@ -1685,13 +1708,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
}
|
||||
switch {
|
||||
// First block is pruned, insert as sidechain and reorg only if TD grows enough
|
||||
case err == consensus.ErrPrunedAncestor:
|
||||
case errors.Is(err, consensus.ErrPrunedAncestor):
|
||||
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
|
||||
return bc.insertSideChain(block, it)
|
||||
|
||||
// First block is future, shove it (and all children) to the future queue (unknown ancestor)
|
||||
case err == consensus.ErrFutureBlock || (err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(it.first().ParentHash())):
|
||||
for block != nil && (it.index == 0 || err == consensus.ErrUnknownAncestor) {
|
||||
case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
|
||||
for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) {
|
||||
log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash())
|
||||
if err := bc.addFutureBlock(block); err != nil {
|
||||
return it.index, err
|
||||
@@ -1874,13 +1897,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
stats.report(chain, it.index, dirty)
|
||||
}
|
||||
// Any blocks remaining here? The only ones we care about are the future ones
|
||||
if block != nil && err == consensus.ErrFutureBlock {
|
||||
if block != nil && errors.Is(err, consensus.ErrFutureBlock) {
|
||||
if err := bc.addFutureBlock(block); err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
block, err = it.next()
|
||||
|
||||
for ; block != nil && err == consensus.ErrUnknownAncestor; block, err = it.next() {
|
||||
for ; block != nil && errors.Is(err, consensus.ErrUnknownAncestor); block, err = it.next() {
|
||||
if err := bc.addFutureBlock(block); err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
@@ -1908,7 +1931,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||
// ones. Any other errors means that the block is invalid, and should not be written
|
||||
// to disk.
|
||||
err := consensus.ErrPrunedAncestor
|
||||
for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() {
|
||||
for ; block != nil && errors.Is(err, consensus.ErrPrunedAncestor); block, err = it.next() {
|
||||
// Check the canonical state root for that number
|
||||
if number := block.NumberU64(); current.NumberU64() >= number {
|
||||
canonical := bc.GetBlockByNumber(number)
|
||||
@@ -2131,6 +2154,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
"drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash())
|
||||
blockReorgAddMeter.Mark(int64(len(newChain)))
|
||||
blockReorgDropMeter.Mark(int64(len(oldChain)))
|
||||
blockReorgMeter.Mark(1)
|
||||
} else {
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "newnum", newBlock.Number(), "newhash", newBlock.Hash())
|
||||
}
|
||||
|
1653
core/blockchain_repair_test.go
Normal file
1653
core/blockchain_repair_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1949
core/blockchain_sethead_test.go
Normal file
1949
core/blockchain_sethead_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// So we can deterministically seed different blockchains
|
||||
@@ -681,12 +682,12 @@ func TestFastVsFullChains(t *testing.T) {
|
||||
}
|
||||
if fblock, arblock, anblock := fast.GetBlockByHash(hash), archive.GetBlockByHash(hash), ancient.GetBlockByHash(hash); fblock.Hash() != arblock.Hash() || anblock.Hash() != arblock.Hash() {
|
||||
t.Errorf("block #%d [%x]: block mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock, anblock, arblock)
|
||||
} else if types.DeriveSha(fblock.Transactions()) != types.DeriveSha(arblock.Transactions()) || types.DeriveSha(anblock.Transactions()) != types.DeriveSha(arblock.Transactions()) {
|
||||
} else if types.DeriveSha(fblock.Transactions(), new(trie.Trie)) != types.DeriveSha(arblock.Transactions(), new(trie.Trie)) || types.DeriveSha(anblock.Transactions(), new(trie.Trie)) != types.DeriveSha(arblock.Transactions(), new(trie.Trie)) {
|
||||
t.Errorf("block #%d [%x]: transactions mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Transactions(), anblock.Transactions(), arblock.Transactions())
|
||||
} else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) || types.CalcUncleHash(anblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) {
|
||||
t.Errorf("block #%d [%x]: uncles mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Uncles(), anblock, arblock.Uncles())
|
||||
}
|
||||
if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
|
||||
if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts, new(trie.Trie)) != types.DeriveSha(areceipts, new(trie.Trie)) {
|
||||
t.Errorf("block #%d [%x]: receipts mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, freceipts, anreceipts, areceipts)
|
||||
}
|
||||
}
|
||||
@@ -731,12 +732,12 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
return db, func() { os.RemoveAll(dir) }
|
||||
}
|
||||
// Configure a subchain to roll back
|
||||
remove := []common.Hash{}
|
||||
for _, block := range blocks[height/2:] {
|
||||
remove = append(remove, block.Hash())
|
||||
}
|
||||
remove := blocks[height/2].NumberU64()
|
||||
|
||||
// Create a small assertion method to check the three heads
|
||||
assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) {
|
||||
t.Helper()
|
||||
|
||||
if num := chain.CurrentBlock().NumberU64(); num != block {
|
||||
t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block)
|
||||
}
|
||||
@@ -750,14 +751,18 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
// Import the chain as an archive node and ensure all pointers are updated
|
||||
archiveDb, delfn := makeDb()
|
||||
defer delfn()
|
||||
archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
|
||||
archiveCaching := *defaultCacheConfig
|
||||
archiveCaching.TrieDirtyDisabled = true
|
||||
|
||||
archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if n, err := archive.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to process block %d: %v", n, err)
|
||||
}
|
||||
defer archive.Stop()
|
||||
|
||||
assert(t, "archive", archive, height, height, height)
|
||||
archive.Rollback(remove)
|
||||
archive.SetHead(remove - 1)
|
||||
assert(t, "archive", archive, height/2, height/2, height/2)
|
||||
|
||||
// Import the chain as a non-archive node and ensure all pointers are updated
|
||||
@@ -777,7 +782,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
t.Fatalf("failed to insert receipt %d: %v", n, err)
|
||||
}
|
||||
assert(t, "fast", fast, height, height, 0)
|
||||
fast.Rollback(remove)
|
||||
fast.SetHead(remove - 1)
|
||||
assert(t, "fast", fast, height/2, height/2, 0)
|
||||
|
||||
// Import the chain as a ancient-first node and ensure all pointers are updated
|
||||
@@ -793,12 +798,12 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
t.Fatalf("failed to insert receipt %d: %v", n, err)
|
||||
}
|
||||
assert(t, "ancient", ancient, height, height, 0)
|
||||
ancient.Rollback(remove)
|
||||
assert(t, "ancient", ancient, height/2, height/2, 0)
|
||||
if frozen, err := ancientDb.Ancients(); err != nil || frozen != height/2+1 {
|
||||
t.Fatalf("failed to truncate ancient store, want %v, have %v", height/2+1, frozen)
|
||||
}
|
||||
ancient.SetHead(remove - 1)
|
||||
assert(t, "ancient", ancient, 0, 0, 0)
|
||||
|
||||
if frozen, err := ancientDb.Ancients(); err != nil || frozen != 1 {
|
||||
t.Fatalf("failed to truncate ancient store, want %v, have %v", 1, frozen)
|
||||
}
|
||||
// Import the chain as a light node and ensure all pointers are updated
|
||||
lightDb, delfn := makeDb()
|
||||
defer delfn()
|
||||
@@ -809,7 +814,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
defer light.Stop()
|
||||
|
||||
assert(t, "light", light, height, 0, 0)
|
||||
light.Rollback(remove)
|
||||
light.SetHead(remove - 1)
|
||||
assert(t, "light", light, height/2, 0, 0)
|
||||
}
|
||||
|
||||
@@ -1585,6 +1590,7 @@ func TestBlockchainRecovery(t *testing.T) {
|
||||
t.Fatalf("failed to create temp freezer dir: %v", err)
|
||||
}
|
||||
defer os.Remove(frdir)
|
||||
|
||||
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
@@ -1602,6 +1608,7 @@ func TestBlockchainRecovery(t *testing.T) {
|
||||
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil {
|
||||
t.Fatalf("failed to insert receipt %d: %v", n, err)
|
||||
}
|
||||
rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior
|
||||
ancient.Stop()
|
||||
|
||||
// Destroy head fast block manually
|
||||
@@ -1912,11 +1919,9 @@ func testInsertKnownChainData(t *testing.T, typ string) {
|
||||
asserter(t, blocks[len(blocks)-1])
|
||||
|
||||
// Import a long canonical chain with some known data as prefix.
|
||||
var rollback []common.Hash
|
||||
for i := len(blocks) / 2; i < len(blocks); i++ {
|
||||
rollback = append(rollback, blocks[i].Hash())
|
||||
}
|
||||
chain.Rollback(rollback)
|
||||
rollback := blocks[len(blocks)/2].NumberU64()
|
||||
|
||||
chain.SetHead(rollback - 1)
|
||||
if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
@@ -1936,11 +1941,7 @@ func testInsertKnownChainData(t *testing.T, typ string) {
|
||||
asserter(t, blocks3[len(blocks3)-1])
|
||||
|
||||
// Rollback the heavier chain and re-insert the longer chain again
|
||||
for i := 0; i < len(blocks3); i++ {
|
||||
rollback = append(rollback, blocks3[i].Hash())
|
||||
}
|
||||
chain.Rollback(rollback)
|
||||
|
||||
chain.SetHead(rollback - 1)
|
||||
if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
|
@@ -155,7 +155,6 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin
|
||||
session := &MatcherSession{
|
||||
matcher: m,
|
||||
quit: make(chan struct{}),
|
||||
kill: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
}
|
||||
for _, scheduler := range m.schedulers {
|
||||
@@ -386,10 +385,8 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
|
||||
requests = make(map[uint][]uint64) // Per-bit list of section requests, ordered by section number
|
||||
unallocs = make(map[uint]struct{}) // Bits with pending requests but not allocated to any retriever
|
||||
retrievers chan chan uint // Waiting retrievers (toggled to nil if unallocs is empty)
|
||||
)
|
||||
var (
|
||||
allocs int // Number of active allocations to handle graceful shutdown requests
|
||||
shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests
|
||||
allocs int // Number of active allocations to handle graceful shutdown requests
|
||||
shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests
|
||||
)
|
||||
|
||||
// assign is a helper method fo try to assign a pending bit an actively
|
||||
@@ -409,15 +406,12 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
|
||||
for {
|
||||
select {
|
||||
case <-shutdown:
|
||||
// Graceful shutdown requested, wait until all pending requests are honoured
|
||||
// Shutdown requested. No more retrievers can be allocated,
|
||||
// but we still need to wait until all pending requests have returned.
|
||||
shutdown = nil
|
||||
if allocs == 0 {
|
||||
return
|
||||
}
|
||||
shutdown = nil
|
||||
|
||||
case <-session.kill:
|
||||
// Pending requests not honoured in time, hard terminate
|
||||
return
|
||||
|
||||
case req := <-dist:
|
||||
// New retrieval request arrived to be distributed to some fetcher process
|
||||
@@ -499,8 +493,9 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
|
||||
assign(result.Bit)
|
||||
}
|
||||
}
|
||||
// If we're in the process of shutting down, terminate
|
||||
if allocs == 0 && shutdown == nil {
|
||||
|
||||
// End the session when all pending deliveries have arrived.
|
||||
if shutdown == nil && allocs == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -514,7 +509,6 @@ type MatcherSession struct {
|
||||
|
||||
closer sync.Once // Sync object to ensure we only ever close once
|
||||
quit chan struct{} // Quit channel to request pipeline termination
|
||||
kill chan struct{} // Term channel to signal non-graceful forced shutdown
|
||||
|
||||
ctx context.Context // Context used by the light client to abort filtering
|
||||
err atomic.Value // Global error to track retrieval failures deep in the chain
|
||||
@@ -529,7 +523,6 @@ func (s *MatcherSession) Close() {
|
||||
s.closer.Do(func() {
|
||||
// Signal termination and wait for all goroutines to tear down
|
||||
close(s.quit)
|
||||
time.AfterFunc(time.Second, func() { close(s.kill) })
|
||||
s.pend.Wait()
|
||||
})
|
||||
}
|
||||
@@ -542,10 +535,10 @@ func (s *MatcherSession) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocateRetrieval assigns a bloom bit index to a client process that can either
|
||||
// allocateRetrieval assigns a bloom bit index to a client process that can either
|
||||
// immediately request and fetch the section contents assigned to this bit or wait
|
||||
// a little while for more sections to be requested.
|
||||
func (s *MatcherSession) AllocateRetrieval() (uint, bool) {
|
||||
func (s *MatcherSession) allocateRetrieval() (uint, bool) {
|
||||
fetcher := make(chan uint)
|
||||
|
||||
select {
|
||||
@@ -557,9 +550,9 @@ func (s *MatcherSession) AllocateRetrieval() (uint, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// PendingSections returns the number of pending section retrievals belonging to
|
||||
// pendingSections returns the number of pending section retrievals belonging to
|
||||
// the given bloom bit index.
|
||||
func (s *MatcherSession) PendingSections(bit uint) int {
|
||||
func (s *MatcherSession) pendingSections(bit uint) int {
|
||||
fetcher := make(chan uint)
|
||||
|
||||
select {
|
||||
@@ -571,9 +564,9 @@ func (s *MatcherSession) PendingSections(bit uint) int {
|
||||
}
|
||||
}
|
||||
|
||||
// AllocateSections assigns all or part of an already allocated bit-task queue
|
||||
// allocateSections assigns all or part of an already allocated bit-task queue
|
||||
// to the requesting process.
|
||||
func (s *MatcherSession) AllocateSections(bit uint, count int) []uint64 {
|
||||
func (s *MatcherSession) allocateSections(bit uint, count int) []uint64 {
|
||||
fetcher := make(chan *Retrieval)
|
||||
|
||||
select {
|
||||
@@ -589,14 +582,10 @@ func (s *MatcherSession) AllocateSections(bit uint, count int) []uint64 {
|
||||
}
|
||||
}
|
||||
|
||||
// DeliverSections delivers a batch of section bit-vectors for a specific bloom
|
||||
// deliverSections delivers a batch of section bit-vectors for a specific bloom
|
||||
// bit index to be injected into the processing pipeline.
|
||||
func (s *MatcherSession) DeliverSections(bit uint, sections []uint64, bitsets [][]byte) {
|
||||
select {
|
||||
case <-s.kill:
|
||||
return
|
||||
case s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets}:
|
||||
}
|
||||
func (s *MatcherSession) deliverSections(bit uint, sections []uint64, bitsets [][]byte) {
|
||||
s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets}
|
||||
}
|
||||
|
||||
// Multiplex polls the matcher session for retrieval tasks and multiplexes it into
|
||||
@@ -608,17 +597,17 @@ func (s *MatcherSession) DeliverSections(bit uint, sections []uint64, bitsets []
|
||||
func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
|
||||
for {
|
||||
// Allocate a new bloom bit index to retrieve data for, stopping when done
|
||||
bit, ok := s.AllocateRetrieval()
|
||||
bit, ok := s.allocateRetrieval()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// Bit allocated, throttle a bit if we're below our batch limit
|
||||
if s.PendingSections(bit) < batch {
|
||||
if s.pendingSections(bit) < batch {
|
||||
select {
|
||||
case <-s.quit:
|
||||
// Session terminating, we can't meaningfully service, abort
|
||||
s.AllocateSections(bit, 0)
|
||||
s.DeliverSections(bit, []uint64{}, [][]byte{})
|
||||
s.allocateSections(bit, 0)
|
||||
s.deliverSections(bit, []uint64{}, [][]byte{})
|
||||
return
|
||||
|
||||
case <-time.After(wait):
|
||||
@@ -626,13 +615,13 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
|
||||
}
|
||||
}
|
||||
// Allocate as much as we can handle and request servicing
|
||||
sections := s.AllocateSections(bit, batch)
|
||||
sections := s.allocateSections(bit, batch)
|
||||
request := make(chan *Retrieval)
|
||||
|
||||
select {
|
||||
case <-s.quit:
|
||||
// Session terminating, we can't meaningfully service, abort
|
||||
s.DeliverSections(bit, sections, make([][]byte, len(sections)))
|
||||
s.deliverSections(bit, sections, make([][]byte, len(sections)))
|
||||
return
|
||||
|
||||
case mux <- request:
|
||||
@@ -644,7 +633,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
|
||||
s.err.Store(result.Error)
|
||||
s.Close()
|
||||
}
|
||||
s.DeliverSections(result.Bit, result.Sections, result.Bitsets)
|
||||
s.deliverSections(result.Bit, result.Sections, result.Bitsets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -46,6 +46,9 @@ type ChainIndexerBackend interface {
|
||||
|
||||
// Commit finalizes the section metadata and stores it into the database.
|
||||
Commit() error
|
||||
|
||||
// Prune deletes the chain index older than the given threshold.
|
||||
Prune(threshold uint64) error
|
||||
}
|
||||
|
||||
// ChainIndexerChain interface is used for connecting the indexer to a blockchain
|
||||
@@ -386,7 +389,6 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||
c.log.Trace("Processing new chain section", "section", section)
|
||||
|
||||
// Reset and partial processing
|
||||
|
||||
if err := c.backend.Reset(c.ctx, section, lastHead); err != nil {
|
||||
c.setValidSections(0)
|
||||
return common.Hash{}, err
|
||||
@@ -459,6 +461,11 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
|
||||
}
|
||||
}
|
||||
|
||||
// Prune deletes all chain data older than given threshold.
|
||||
func (c *ChainIndexer) Prune(threshold uint64) error {
|
||||
return c.backend.Prune(threshold)
|
||||
}
|
||||
|
||||
// loadValidSections reads the number of valid sections from the index database
|
||||
// and caches is into the local state.
|
||||
func (c *ChainIndexer) loadValidSections() {
|
||||
|
@@ -236,3 +236,7 @@ func (b *testChainIndexBackend) Commit() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testChainIndexBackend) Prune(threshold uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
@@ -220,7 +220,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("state write error: %v", err))
|
||||
}
|
||||
if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
|
||||
if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil {
|
||||
panic(fmt.Sprintf("trie write error: %v", err))
|
||||
}
|
||||
return block, b.receipts
|
||||
|
@@ -79,7 +79,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
@@ -104,7 +104,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
@@ -130,7 +130,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
@@ -150,7 +150,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
|
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
//go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
|
||||
@@ -174,7 +175,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
||||
// We have the genesis block in database(perhaps in ancient database)
|
||||
// but the corresponding state is missing.
|
||||
header := rawdb.ReadHeader(db, stored, 0)
|
||||
if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0), nil); err != nil {
|
||||
if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0, ""), nil); err != nil {
|
||||
if genesis == nil {
|
||||
genesis = DefaultGenesisBlock()
|
||||
}
|
||||
@@ -285,9 +286,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
|
||||
head.Difficulty = params.GenesisDifficulty
|
||||
}
|
||||
statedb.Commit(false)
|
||||
statedb.Database().TrieDB().Commit(root, true)
|
||||
statedb.Database().TrieDB().Commit(root, true, nil)
|
||||
|
||||
return types.NewBlock(head, nil, nil, nil)
|
||||
return types.NewBlock(head, nil, nil, nil, new(trie.Trie))
|
||||
}
|
||||
|
||||
// Commit writes the block and state of a genesis specification to the database.
|
||||
@@ -400,7 +401,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
|
||||
return &Genesis{
|
||||
Config: &config,
|
||||
ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...),
|
||||
GasLimit: 6283185,
|
||||
GasLimit: 11500000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Alloc: map[common.Address]GenesisAccount{
|
||||
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
|
||||
@@ -411,6 +412,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
|
||||
common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
|
||||
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
||||
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
||||
faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))},
|
||||
},
|
||||
}
|
||||
|
@@ -488,8 +488,10 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
|
||||
|
||||
type (
|
||||
// UpdateHeadBlocksCallback is a callback function that is called by SetHead
|
||||
// before head header is updated.
|
||||
UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header)
|
||||
// before head header is updated. The method will return the actual block it
|
||||
// updated the head to (missing state) and a flag if setHead should continue
|
||||
// rewinding till that forcefully (exceeded ancient limits)
|
||||
UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (uint64, bool)
|
||||
|
||||
// DeleteBlockContentCallback is a callback function that is called by SetHead
|
||||
// before each header is deleted.
|
||||
@@ -502,9 +504,10 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
|
||||
var (
|
||||
parentHash common.Hash
|
||||
batch = hc.chainDb.NewBatch()
|
||||
origin = true
|
||||
)
|
||||
for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
|
||||
hash, num := hdr.Hash(), hdr.Number.Uint64()
|
||||
num := hdr.Number.Uint64()
|
||||
|
||||
// Rewind block chain to new head.
|
||||
parent := hc.GetHeader(hdr.ParentHash, num-1)
|
||||
@@ -512,16 +515,21 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
|
||||
parent = hc.genesisHeader
|
||||
}
|
||||
parentHash = hdr.ParentHash
|
||||
|
||||
// Notably, since geth has the possibility for setting the head to a low
|
||||
// height which is even lower than ancient head.
|
||||
// In order to ensure that the head is always no higher than the data in
|
||||
// the database(ancient store or active store), we need to update head
|
||||
// the database (ancient store or active store), we need to update head
|
||||
// first then remove the relative data from the database.
|
||||
//
|
||||
// Update head first(head fast block, head full block) before deleting the data.
|
||||
markerBatch := hc.chainDb.NewBatch()
|
||||
if updateFn != nil {
|
||||
updateFn(markerBatch, parent)
|
||||
newHead, force := updateFn(markerBatch, parent)
|
||||
if force && newHead < head {
|
||||
log.Warn("Force rewinding till ancient limit", "head", newHead)
|
||||
head = newHead
|
||||
}
|
||||
}
|
||||
// Update head header then.
|
||||
rawdb.WriteHeadHeaderHash(markerBatch, parentHash)
|
||||
@@ -532,14 +540,34 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
|
||||
hc.currentHeaderHash = parentHash
|
||||
headHeaderGauge.Update(parent.Number.Int64())
|
||||
|
||||
// Remove the relative data from the database.
|
||||
if delFn != nil {
|
||||
delFn(batch, hash, num)
|
||||
// If this is the first iteration, wipe any leftover data upwards too so
|
||||
// we don't end up with dangling daps in the database
|
||||
var nums []uint64
|
||||
if origin {
|
||||
for n := num + 1; len(rawdb.ReadAllHashes(hc.chainDb, n)) > 0; n++ {
|
||||
nums = append([]uint64{n}, nums...) // suboptimal, but we don't really expect this path
|
||||
}
|
||||
origin = false
|
||||
}
|
||||
nums = append(nums, num)
|
||||
|
||||
// Remove the related data from the database on all sidechains
|
||||
for _, num := range nums {
|
||||
// Gather all the side fork hashes
|
||||
hashes := rawdb.ReadAllHashes(hc.chainDb, num)
|
||||
if len(hashes) == 0 {
|
||||
// No hashes in the database whatsoever, probably frozen already
|
||||
hashes = append(hashes, hdr.Hash())
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
if delFn != nil {
|
||||
delFn(batch, hash, num)
|
||||
}
|
||||
rawdb.DeleteHeader(batch, hash, num)
|
||||
rawdb.DeleteTd(batch, hash, num)
|
||||
}
|
||||
rawdb.DeleteCanonicalHash(batch, num)
|
||||
}
|
||||
// Rewind header chain to new head.
|
||||
rawdb.DeleteHeader(batch, hash, num)
|
||||
rawdb.DeleteTd(batch, hash, num)
|
||||
rawdb.DeleteCanonicalHash(batch, num)
|
||||
}
|
||||
// Flush all accumulated deletions.
|
||||
if err := batch.Write(); err != nil {
|
||||
|
@@ -80,6 +80,39 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash {
|
||||
return hashes
|
||||
}
|
||||
|
||||
// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the
|
||||
// certain chain range. If the accumulated entries reaches the given threshold,
|
||||
// abort the iteration and return the semi-finish result.
|
||||
func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int) ([]uint64, []common.Hash) {
|
||||
// Short circuit if the limit is 0.
|
||||
if limit == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var (
|
||||
numbers []uint64
|
||||
hashes []common.Hash
|
||||
)
|
||||
// Construct the key prefix of start point.
|
||||
start, end := headerHashKey(from), headerHashKey(to)
|
||||
it := db.NewIterator(nil, start)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
if bytes.Compare(it.Key(), end) >= 0 {
|
||||
break
|
||||
}
|
||||
if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) {
|
||||
numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8]))
|
||||
hashes = append(hashes, common.BytesToHash(it.Value()))
|
||||
// If the accumulated entries reaches the limit threshold, return.
|
||||
if len(numbers) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return numbers, hashes
|
||||
}
|
||||
|
||||
// ReadHeaderNumber returns the header number assigned to a hash.
|
||||
func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
|
||||
data, _ := db.Get(headerNumberKey(hash))
|
||||
@@ -154,6 +187,32 @@ func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
|
||||
// full synced, the last pivot will always be nil.
|
||||
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(lastPivotKey)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
var pivot uint64
|
||||
if err := rlp.DecodeBytes(data, &pivot); err != nil {
|
||||
log.Error("Invalid pivot block number in database", "err", err)
|
||||
return nil
|
||||
}
|
||||
return &pivot
|
||||
}
|
||||
|
||||
// WriteLastPivotNumber stores the number of the last pivot block.
|
||||
func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) {
|
||||
enc, err := rlp.EncodeToBytes(pivot)
|
||||
if err != nil {
|
||||
log.Crit("Failed to encode pivot block number", "err", err)
|
||||
}
|
||||
if err := db.Put(lastPivotKey, enc); err != nil {
|
||||
log.Crit("Failed to store pivot block number", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFastTrieProgress retrieves the number of tries nodes fast synced to allow
|
||||
// reporting correct numbers across restarts.
|
||||
func ReadFastTrieProgress(db ethdb.KeyValueReader) uint64 {
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -83,7 +84,7 @@ func TestBodyStorage(t *testing.T) {
|
||||
WriteBody(db, hash, 0, body)
|
||||
if entry := ReadBody(db, hash, 0); entry == nil {
|
||||
t.Fatalf("Stored body not found")
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(types.Transactions(body.Transactions)) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
|
||||
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body)
|
||||
}
|
||||
if entry := ReadBodyRLP(db, hash, 0); entry == nil {
|
||||
@@ -137,7 +138,7 @@ func TestBlockStorage(t *testing.T) {
|
||||
}
|
||||
if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
t.Fatalf("Stored body not found")
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(block.Transactions()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(block.Transactions(), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
|
||||
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body())
|
||||
}
|
||||
// Delete the block and verify the execution
|
||||
@@ -424,3 +425,35 @@ func TestAncientStorage(t *testing.T) {
|
||||
t.Fatalf("invalid td returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalHashIteration(t *testing.T) {
|
||||
var cases = []struct {
|
||||
from, to uint64
|
||||
limit int
|
||||
expect []uint64
|
||||
}{
|
||||
{1, 8, 0, nil},
|
||||
{1, 8, 1, []uint64{1}},
|
||||
{1, 8, 10, []uint64{1, 2, 3, 4, 5, 6, 7}},
|
||||
{1, 9, 10, []uint64{1, 2, 3, 4, 5, 6, 7, 8}},
|
||||
{2, 9, 10, []uint64{2, 3, 4, 5, 6, 7, 8}},
|
||||
{9, 10, 10, nil},
|
||||
}
|
||||
// Test empty db iteration
|
||||
db := NewMemoryDatabase()
|
||||
numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10)
|
||||
if len(numbers) != 0 {
|
||||
t.Fatalf("No entry should be returned to iterate an empty db")
|
||||
}
|
||||
// Fill database with testing data.
|
||||
for i := uint64(1); i <= 8; i++ {
|
||||
WriteCanonicalHash(db, common.Hash{}, i)
|
||||
WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data
|
||||
}
|
||||
for i, c := range cases {
|
||||
numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit)
|
||||
if !reflect.DeepEqual(numbers, c.expect) {
|
||||
t.Fatalf("Case %d failed, want %v, got %v", i, c.expect, numbers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -151,3 +152,24 @@ func WriteBloomBits(db ethdb.KeyValueWriter, bit uint, section uint64, head comm
|
||||
log.Crit("Failed to store bloom bits", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteBloombits removes all compressed bloom bits vector belonging to the
|
||||
// given section range and bit index.
|
||||
func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) {
|
||||
start, end := bloomBitsKey(bit, from, common.Hash{}), bloomBitsKey(bit, to, common.Hash{})
|
||||
it := db.NewIterator(nil, start)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
if bytes.Compare(it.Key(), end) >= 0 {
|
||||
break
|
||||
}
|
||||
if len(it.Key()) != len(bloomBitsPrefix)+2+8+32 {
|
||||
continue
|
||||
}
|
||||
db.Delete(it.Key())
|
||||
}
|
||||
if it.Error() != nil {
|
||||
log.Crit("Failed to delete bloom bits", "err", it.Error())
|
||||
}
|
||||
}
|
||||
|
@@ -17,15 +17,43 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// testHasher is the helper tool for transaction/receipt list hashing.
|
||||
// The original hasher is trie, in order to get rid of import cycle,
|
||||
// use the testing hasher instead.
|
||||
type testHasher struct {
|
||||
hasher hash.Hash
|
||||
}
|
||||
|
||||
func newHasher() *testHasher {
|
||||
return &testHasher{hasher: sha3.NewLegacyKeccak256()}
|
||||
}
|
||||
|
||||
func (h *testHasher) Reset() {
|
||||
h.hasher.Reset()
|
||||
}
|
||||
|
||||
func (h *testHasher) Update(key, val []byte) {
|
||||
h.hasher.Write(key)
|
||||
h.hasher.Write(val)
|
||||
}
|
||||
|
||||
func (h *testHasher) Hash() common.Hash {
|
||||
return common.BytesToHash(h.hasher.Sum(nil))
|
||||
}
|
||||
|
||||
// Tests that positional lookup metadata can be stored and retrieved.
|
||||
func TestLookupStorage(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -71,7 +99,7 @@ func TestLookupStorage(t *testing.T) {
|
||||
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
|
||||
txs := []*types.Transaction{tx1, tx2, tx3}
|
||||
|
||||
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil)
|
||||
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newHasher())
|
||||
|
||||
// Check that no transactions entries are in a pristine database
|
||||
for i, tx := range txs {
|
||||
@@ -106,3 +134,46 @@ func TestLookupStorage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBloomBits(t *testing.T) {
|
||||
// Prepare testing data
|
||||
db := NewMemoryDatabase()
|
||||
for i := uint(0); i < 2; i++ {
|
||||
for s := uint64(0); s < 2; s++ {
|
||||
WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02})
|
||||
WriteBloomBits(db, i, s, params.RinkebyGenesisHash, []byte{0x01, 0x02})
|
||||
}
|
||||
}
|
||||
check := func(bit uint, section uint64, head common.Hash, exist bool) {
|
||||
bits, _ := ReadBloomBits(db, bit, section, head)
|
||||
if exist && !bytes.Equal(bits, []byte{0x01, 0x02}) {
|
||||
t.Fatalf("Bloombits mismatch")
|
||||
}
|
||||
if !exist && len(bits) > 0 {
|
||||
t.Fatalf("Bloombits should be removed")
|
||||
}
|
||||
}
|
||||
// Check the existence of written data.
|
||||
check(0, 0, params.MainnetGenesisHash, true)
|
||||
check(0, 0, params.RinkebyGenesisHash, true)
|
||||
|
||||
// Check the existence of deleted data.
|
||||
DeleteBloombits(db, 0, 0, 1)
|
||||
check(0, 0, params.MainnetGenesisHash, false)
|
||||
check(0, 0, params.RinkebyGenesisHash, false)
|
||||
check(0, 1, params.MainnetGenesisHash, true)
|
||||
check(0, 1, params.RinkebyGenesisHash, true)
|
||||
|
||||
// Check the existence of deleted data.
|
||||
DeleteBloombits(db, 0, 0, 2)
|
||||
check(0, 0, params.MainnetGenesisHash, false)
|
||||
check(0, 0, params.RinkebyGenesisHash, false)
|
||||
check(0, 1, params.MainnetGenesisHash, false)
|
||||
check(0, 1, params.RinkebyGenesisHash, false)
|
||||
|
||||
// Bit1 shouldn't be affect.
|
||||
check(1, 0, params.MainnetGenesisHash, true)
|
||||
check(1, 0, params.RinkebyGenesisHash, true)
|
||||
check(1, 1, params.MainnetGenesisHash, true)
|
||||
check(1, 1, params.RinkebyGenesisHash, true)
|
||||
}
|
||||
|
@@ -79,20 +79,3 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
|
||||
log.Crit("Failed to store chain config", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPreimage retrieves a single preimage of the provided hash.
|
||||
func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
data, _ := db.Get(preimageKey(hash))
|
||||
return data
|
||||
}
|
||||
|
||||
// WritePreimages writes the provided set of preimages to the database.
|
||||
func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
|
||||
for hash, preimage := range preimages {
|
||||
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
||||
log.Crit("Failed to store trie preimage", "err", err)
|
||||
}
|
||||
}
|
||||
preimageCounter.Inc(int64(len(preimages)))
|
||||
preimageHitCounter.Inc(int64(len(preimages)))
|
||||
}
|
||||
|
96
core/rawdb/accessors_state.go
Normal file
96
core/rawdb/accessors_state.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2020 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 rawdb
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// ReadPreimage retrieves a single preimage of the provided hash.
|
||||
func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
data, _ := db.Get(preimageKey(hash))
|
||||
return data
|
||||
}
|
||||
|
||||
// WritePreimages writes the provided set of preimages to the database.
|
||||
func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
|
||||
for hash, preimage := range preimages {
|
||||
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
||||
log.Crit("Failed to store trie preimage", "err", err)
|
||||
}
|
||||
}
|
||||
preimageCounter.Inc(int64(len(preimages)))
|
||||
preimageHitCounter.Inc(int64(len(preimages)))
|
||||
}
|
||||
|
||||
// ReadCode retrieves the contract code of the provided code hash.
|
||||
func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
// Try with the legacy code scheme first, if not then try with current
|
||||
// scheme. Since most of the code will be found with legacy scheme.
|
||||
//
|
||||
// todo(rjl493456442) change the order when we forcibly upgrade the code
|
||||
// scheme with snapshot.
|
||||
data, _ := db.Get(hash[:])
|
||||
if len(data) != 0 {
|
||||
return data
|
||||
}
|
||||
return ReadCodeWithPrefix(db, hash)
|
||||
}
|
||||
|
||||
// ReadCodeWithPrefix retrieves the contract code of the provided code hash.
|
||||
// The main difference between this function and ReadCode is this function
|
||||
// will only check the existence with latest scheme(with prefix).
|
||||
func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
data, _ := db.Get(codeKey(hash))
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteCode writes the provided contract code database.
|
||||
func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
|
||||
if err := db.Put(codeKey(hash), code); err != nil {
|
||||
log.Crit("Failed to store contract code", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCode deletes the specified contract code from the database.
|
||||
func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
if err := db.Delete(codeKey(hash)); err != nil {
|
||||
log.Crit("Failed to delete contract code", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTrieNode retrieves the trie node of the provided hash.
|
||||
func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
data, _ := db.Get(hash.Bytes())
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrieNode writes the provided trie node database.
|
||||
func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
|
||||
if err := db.Put(hash.Bytes(), node); err != nil {
|
||||
log.Crit("Failed to store trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrieNode deletes the specified trie node from the database.
|
||||
func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
if err := db.Delete(hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to delete trie node", "err", err)
|
||||
}
|
||||
}
|
@@ -34,11 +34,11 @@ func TestChainIterator(t *testing.T) {
|
||||
var txs []*types.Transaction
|
||||
for i := uint64(0); i <= 10; i++ {
|
||||
if i == 0 {
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil) // Empty genesis block
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil, newHasher()) // Empty genesis block
|
||||
} else {
|
||||
tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
|
||||
txs = append(txs, tx)
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil)
|
||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher())
|
||||
}
|
||||
WriteBlock(chainDb, block)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
|
@@ -21,6 +21,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -53,6 +54,22 @@ func (frdb *freezerdb) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Freeze is a helper method used for external testing to trigger and block until
|
||||
// a freeze cycle completes, without having to sleep for a minute to trigger the
|
||||
// automatic background run.
|
||||
func (frdb *freezerdb) Freeze(threshold uint64) {
|
||||
// Set the freezer threshold to a temporary value
|
||||
defer func(old uint64) {
|
||||
atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, old)
|
||||
}(atomic.LoadUint64(&frdb.AncientStore.(*freezer).threshold))
|
||||
atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, threshold)
|
||||
|
||||
// Trigger a freeze cycle and block until it's done
|
||||
trigger := make(chan struct{}, 1)
|
||||
frdb.AncientStore.(*freezer).trigger <- trigger
|
||||
<-trigger
|
||||
}
|
||||
|
||||
// nofreezedb is a database wrapper that disables freezer data retrievals.
|
||||
type nofreezedb struct {
|
||||
ethdb.KeyValueStore
|
||||
@@ -137,7 +154,10 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st
|
||||
// If the freezer already contains something, ensure that the genesis blocks
|
||||
// match, otherwise we might mix up freezers across chains and destroy both
|
||||
// the freezer and the key-value store.
|
||||
if frgenesis, _ := frdb.Ancient(freezerHashTable, 0); !bytes.Equal(kvgenesis, frgenesis) {
|
||||
frgenesis, err := frdb.Ancient(freezerHashTable, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err)
|
||||
} else if !bytes.Equal(kvgenesis, frgenesis) {
|
||||
return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis)
|
||||
}
|
||||
// Key-value store and freezer belong to the same network. Ensure that they
|
||||
@@ -238,6 +258,7 @@ func InspectDatabase(db ethdb.Database) error {
|
||||
numHashPairing common.StorageSize
|
||||
hashNumPairing common.StorageSize
|
||||
trieSize common.StorageSize
|
||||
codeSize common.StorageSize
|
||||
txlookupSize common.StorageSize
|
||||
accountSnapSize common.StorageSize
|
||||
storageSnapSize common.StorageSize
|
||||
@@ -296,6 +317,8 @@ func InspectDatabase(db ethdb.Database) error {
|
||||
chtTrieNodes += size
|
||||
case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength:
|
||||
bloomTrieNodes += size
|
||||
case bytes.HasPrefix(key, codePrefix) && len(key) == len(codePrefix)+common.HashLength:
|
||||
codeSize += size
|
||||
case len(key) == common.HashLength:
|
||||
trieSize += size
|
||||
default:
|
||||
@@ -335,6 +358,7 @@ func InspectDatabase(db ethdb.Database) error {
|
||||
{"Key-Value store", "Block hash->number", hashNumPairing.String()},
|
||||
{"Key-Value store", "Transaction index", txlookupSize.String()},
|
||||
{"Key-Value store", "Bloombit index", bloomBitsSize.String()},
|
||||
{"Key-Value store", "Contract codes", codeSize.String()},
|
||||
{"Key-Value store", "Trie nodes", trieSize.String()},
|
||||
{"Key-Value store", "Trie preimages", preimageSize.String()},
|
||||
{"Key-Value store", "Account snapshot", accountSnapSize.String()},
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -69,11 +70,16 @@ type freezer struct {
|
||||
// WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only
|
||||
// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
|
||||
// so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG).
|
||||
frozen uint64 // Number of blocks already frozen
|
||||
frozen uint64 // Number of blocks already frozen
|
||||
threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests)
|
||||
|
||||
tables map[string]*freezerTable // Data tables for storing everything
|
||||
instanceLock fileutil.Releaser // File-system lock to prevent double opens
|
||||
quit chan struct{}
|
||||
|
||||
trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
|
||||
|
||||
quit chan struct{}
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
// newFreezer creates a chain freezer that moves ancient chain data into
|
||||
@@ -100,8 +106,10 @@ func newFreezer(datadir string, namespace string) (*freezer, error) {
|
||||
}
|
||||
// Open all the supported data tables
|
||||
freezer := &freezer{
|
||||
threshold: params.FullImmutabilityThreshold,
|
||||
tables: make(map[string]*freezerTable),
|
||||
instanceLock: lock,
|
||||
trigger: make(chan chan struct{}),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
for name, disableSnappy := range freezerNoSnappy {
|
||||
@@ -128,16 +136,18 @@ func newFreezer(datadir string, namespace string) (*freezer, error) {
|
||||
|
||||
// Close terminates the chain freezer, unmapping all the data files.
|
||||
func (f *freezer) Close() error {
|
||||
f.quit <- struct{}{}
|
||||
var errs []error
|
||||
for _, table := range f.tables {
|
||||
if err := table.Close(); err != nil {
|
||||
f.closeOnce.Do(func() {
|
||||
f.quit <- struct{}{}
|
||||
for _, table := range f.tables {
|
||||
if err := table.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if err := f.instanceLock.Release(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if err := f.instanceLock.Release(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
})
|
||||
if errs != nil {
|
||||
return fmt.Errorf("%v", errs)
|
||||
}
|
||||
@@ -221,7 +231,7 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td
|
||||
return nil
|
||||
}
|
||||
|
||||
// Truncate discards any recent data above the provided threshold number.
|
||||
// TruncateAncients discards any recent data above the provided threshold number.
|
||||
func (f *freezer) TruncateAncients(items uint64) error {
|
||||
if atomic.LoadUint64(&f.frozen) <= items {
|
||||
return nil
|
||||
@@ -235,7 +245,7 @@ func (f *freezer) TruncateAncients(items uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sync flushes all data tables to disk.
|
||||
// Sync flushes all data tables to disk.
|
||||
func (f *freezer) Sync() error {
|
||||
var errs []error
|
||||
for _, table := range f.tables {
|
||||
@@ -257,7 +267,10 @@ func (f *freezer) Sync() error {
|
||||
func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
nfdb := &nofreezedb{KeyValueStore: db}
|
||||
|
||||
backoff := false
|
||||
var (
|
||||
backoff bool
|
||||
triggered chan struct{} // Used in tests
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case <-f.quit:
|
||||
@@ -266,9 +279,16 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
default:
|
||||
}
|
||||
if backoff {
|
||||
// If we were doing a manual trigger, notify it
|
||||
if triggered != nil {
|
||||
triggered <- struct{}{}
|
||||
triggered = nil
|
||||
}
|
||||
select {
|
||||
case <-time.NewTimer(freezerRecheckInterval).C:
|
||||
backoff = false
|
||||
case triggered = <-f.trigger:
|
||||
backoff = false
|
||||
case <-f.quit:
|
||||
return
|
||||
}
|
||||
@@ -281,18 +301,20 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
continue
|
||||
}
|
||||
number := ReadHeaderNumber(nfdb, hash)
|
||||
threshold := atomic.LoadUint64(&f.threshold)
|
||||
|
||||
switch {
|
||||
case number == nil:
|
||||
log.Error("Current full block number unavailable", "hash", hash)
|
||||
backoff = true
|
||||
continue
|
||||
|
||||
case *number < params.ImmutabilityThreshold:
|
||||
log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold)
|
||||
case *number < threshold:
|
||||
log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
|
||||
backoff = true
|
||||
continue
|
||||
|
||||
case *number-params.ImmutabilityThreshold <= f.frozen:
|
||||
case *number-threshold <= f.frozen:
|
||||
log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
|
||||
backoff = true
|
||||
continue
|
||||
@@ -304,7 +326,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
continue
|
||||
}
|
||||
// Seems we have data ready to be frozen, process in usable batches
|
||||
limit := *number - params.ImmutabilityThreshold
|
||||
limit := *number - threshold
|
||||
if limit-f.frozen > freezerBatchLimit {
|
||||
limit = f.frozen + freezerBatchLimit
|
||||
}
|
||||
@@ -313,7 +335,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
first = f.frozen
|
||||
ancients = make([]common.Hash, 0, limit-f.frozen)
|
||||
)
|
||||
for f.frozen < limit {
|
||||
for f.frozen <= limit {
|
||||
// Retrieves all the components of the canonical block
|
||||
hash := ReadCanonicalHash(nfdb, f.frozen)
|
||||
if hash == (common.Hash{}) {
|
||||
@@ -364,11 +386,15 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
log.Crit("Failed to delete frozen canonical blocks", "err", err)
|
||||
}
|
||||
batch.Reset()
|
||||
// Wipe out side chain also.
|
||||
|
||||
// Wipe out side chains also and track dangling side chians
|
||||
var dangling []common.Hash
|
||||
for number := first; number < f.frozen; number++ {
|
||||
// Always keep the genesis block in active database
|
||||
if number != 0 {
|
||||
for _, hash := range ReadAllHashes(db, number) {
|
||||
dangling = ReadAllHashes(db, number)
|
||||
for _, hash := range dangling {
|
||||
log.Trace("Deleting side chain", "number", number, "hash", hash)
|
||||
DeleteBlock(batch, hash, number)
|
||||
}
|
||||
}
|
||||
@@ -376,6 +402,41 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to delete frozen side blocks", "err", err)
|
||||
}
|
||||
batch.Reset()
|
||||
|
||||
// Step into the future and delete and dangling side chains
|
||||
if f.frozen > 0 {
|
||||
tip := f.frozen
|
||||
for len(dangling) > 0 {
|
||||
drop := make(map[common.Hash]struct{})
|
||||
for _, hash := range dangling {
|
||||
log.Debug("Dangling parent from freezer", "number", tip-1, "hash", hash)
|
||||
drop[hash] = struct{}{}
|
||||
}
|
||||
children := ReadAllHashes(db, tip)
|
||||
for i := 0; i < len(children); i++ {
|
||||
// Dig up the child and ensure it's dangling
|
||||
child := ReadHeader(nfdb, children[i], tip)
|
||||
if child == nil {
|
||||
log.Error("Missing dangling header", "number", tip, "hash", children[i])
|
||||
continue
|
||||
}
|
||||
if _, ok := drop[child.ParentHash]; !ok {
|
||||
children = append(children[:i], children[i+1:]...)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
// Delete all block data associated with the child
|
||||
log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash)
|
||||
DeleteBlock(batch, children[i], tip)
|
||||
}
|
||||
dangling = children
|
||||
tip++
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to delete dangling side blocks", "err", err)
|
||||
}
|
||||
}
|
||||
// Log something friendly for the user
|
||||
context := []interface{}{
|
||||
"blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1,
|
||||
|
@@ -330,7 +330,8 @@ func (t *freezerTable) truncate(items uint64) error {
|
||||
defer t.lock.Unlock()
|
||||
|
||||
// If our item count is correct, don't do anything
|
||||
if atomic.LoadUint64(&t.items) <= items {
|
||||
existing := atomic.LoadUint64(&t.items)
|
||||
if existing <= items {
|
||||
return nil
|
||||
}
|
||||
// We need to truncate, save the old size for metrics tracking
|
||||
@@ -339,7 +340,11 @@ func (t *freezerTable) truncate(items uint64) error {
|
||||
return err
|
||||
}
|
||||
// Something's out of sync, truncate the table's offset index
|
||||
t.logger.Warn("Truncating freezer table", "items", t.items, "limit", items)
|
||||
log := t.logger.Debug
|
||||
if existing > items+1 {
|
||||
log = t.logger.Warn // Only loud warn if we delete multiple items
|
||||
}
|
||||
log("Truncating freezer table", "items", existing, "limit", items)
|
||||
if err := truncateFreezerFile(t.index, int64(items+1)*indexEntrySize); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -38,6 +39,9 @@ var (
|
||||
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
|
||||
headFastBlockKey = []byte("LastFast")
|
||||
|
||||
// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
|
||||
lastPivotKey = []byte("LastPivot")
|
||||
|
||||
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
|
||||
fastTrieProgressKey = []byte("TrieSync")
|
||||
|
||||
@@ -66,6 +70,7 @@ var (
|
||||
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
|
||||
SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
|
||||
SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
|
||||
codePrefix = []byte("c") // codePrefix + code hash -> account code
|
||||
|
||||
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
|
||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||
@@ -189,6 +194,20 @@ func preimageKey(hash common.Hash) []byte {
|
||||
return append(preimagePrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// codeKey = codePrefix + hash
|
||||
func codeKey(hash common.Hash) []byte {
|
||||
return append(codePrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// IsCodeKey reports whether the given byte slice is the key of contract code,
|
||||
// if so return the raw code hash as well.
|
||||
func IsCodeKey(key []byte) (bool, []byte) {
|
||||
if bytes.HasPrefix(key, codePrefix) && len(key) == common.HashLength+len(codePrefix) {
|
||||
return true, key[len(codePrefix):]
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// configKey = configPrefix + hash
|
||||
func configKey(hash common.Hash) []byte {
|
||||
return append(configPrefix, hash.Bytes()...)
|
||||
|
@@ -17,9 +17,12 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
@@ -28,6 +31,9 @@ import (
|
||||
const (
|
||||
// Number of codehash->size associations to keep.
|
||||
codeSizeCacheSize = 100000
|
||||
|
||||
// Cache size granted for caching clean code.
|
||||
codeCacheSize = 64 * 1024 * 1024
|
||||
)
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
@@ -100,23 +106,25 @@ type Trie interface {
|
||||
// concurrent use, but does not retain any recent trie nodes in memory. To keep some
|
||||
// historical state in memory, use the NewDatabaseWithCache constructor.
|
||||
func NewDatabase(db ethdb.Database) Database {
|
||||
return NewDatabaseWithCache(db, 0)
|
||||
return NewDatabaseWithCache(db, 0, "")
|
||||
}
|
||||
|
||||
// NewDatabaseWithCache creates a backing store for state. The returned database
|
||||
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
|
||||
// large memory cache.
|
||||
func NewDatabaseWithCache(db ethdb.Database, cache int) Database {
|
||||
func NewDatabaseWithCache(db ethdb.Database, cache int, journal string) Database {
|
||||
csc, _ := lru.New(codeSizeCacheSize)
|
||||
return &cachingDB{
|
||||
db: trie.NewDatabaseWithCache(db, cache),
|
||||
db: trie.NewDatabaseWithCache(db, cache, journal),
|
||||
codeSizeCache: csc,
|
||||
codeCache: fastcache.New(codeCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
type cachingDB struct {
|
||||
db *trie.Database
|
||||
codeSizeCache *lru.Cache
|
||||
codeCache *fastcache.Cache
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
@@ -141,11 +149,32 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
code, err := db.db.Node(codeHash)
|
||||
if err == nil {
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
|
||||
return code, nil
|
||||
}
|
||||
return code, err
|
||||
code := rawdb.ReadCode(db.db.DiskDB(), codeHash)
|
||||
if len(code) > 0 {
|
||||
db.codeCache.Set(codeHash.Bytes(), code)
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
return code, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// ContractCodeWithPrefix retrieves a particular contract's code. If the
|
||||
// code can't be found in the cache, then check the existence with **new**
|
||||
// db scheme.
|
||||
func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
|
||||
return code, nil
|
||||
}
|
||||
code := rawdb.ReadCodeWithPrefix(db.db.DiskDB(), codeHash)
|
||||
if len(code) > 0 {
|
||||
db.codeCache.Set(codeHash.Bytes(), code)
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
return code, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
|
@@ -28,6 +28,7 @@ import (
|
||||
func TestNodeIteratorCoverage(t *testing.T) {
|
||||
// Create some arbitrary test state to iterate
|
||||
db, root, _ := makeTestState()
|
||||
db.TrieDB().Commit(root, false, nil)
|
||||
|
||||
state, err := New(root, db, nil)
|
||||
if err != nil {
|
||||
@@ -42,7 +43,10 @@ func TestNodeIteratorCoverage(t *testing.T) {
|
||||
}
|
||||
// Cross check the iterated hashes and the database/nodepool content
|
||||
for hash := range hashes {
|
||||
if _, err := db.TrieDB().Node(hash); err != nil {
|
||||
if _, err = db.TrieDB().Node(hash); err != nil {
|
||||
_, err = db.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("failed to retrieve reported node %x", hash)
|
||||
}
|
||||
}
|
||||
|
@@ -54,9 +54,11 @@ type generatorStats struct {
|
||||
|
||||
// Log creates an contextual log with the given message and the context pulled
|
||||
// from the internally maintained statistics.
|
||||
func (gs *generatorStats) Log(msg string, marker []byte) {
|
||||
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
|
||||
var ctx []interface{}
|
||||
|
||||
if root != (common.Hash{}) {
|
||||
ctx = append(ctx, []interface{}{"root", root}...)
|
||||
}
|
||||
// Figure out whether we're after or within an account
|
||||
switch len(marker) {
|
||||
case common.HashLength:
|
||||
@@ -120,7 +122,7 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i
|
||||
func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
// If a database wipe is in operation, wait until it's done
|
||||
if stats.wiping != nil {
|
||||
stats.Log("Wiper running, state snapshotting paused", dl.genMarker)
|
||||
stats.Log("Wiper running, state snapshotting paused", common.Hash{}, dl.genMarker)
|
||||
select {
|
||||
// If wiper is done, resume normal mode of operation
|
||||
case <-stats.wiping:
|
||||
@@ -137,13 +139,13 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
accTrie, err := trie.NewSecure(dl.root, dl.triedb)
|
||||
if err != nil {
|
||||
// The account trie is missing (GC), surf the chain until one becomes available
|
||||
stats.Log("Trie missing, state snapshotting paused", dl.genMarker)
|
||||
stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
|
||||
abort := <-dl.genAbort
|
||||
abort <- stats
|
||||
return
|
||||
}
|
||||
stats.Log("Resuming state snapshot generation", dl.genMarker)
|
||||
stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker)
|
||||
|
||||
var accMarker []byte
|
||||
if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that
|
||||
@@ -192,7 +194,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
dl.lock.Unlock()
|
||||
}
|
||||
if abort != nil {
|
||||
stats.Log("Aborting state snapshot generation", accountHash[:])
|
||||
stats.Log("Aborting state snapshot generation", dl.root, accountHash[:])
|
||||
abort <- stats
|
||||
return
|
||||
}
|
||||
@@ -230,7 +232,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
dl.lock.Unlock()
|
||||
}
|
||||
if abort != nil {
|
||||
stats.Log("Aborting state snapshot generation", append(accountHash[:], storeIt.Key...))
|
||||
stats.Log("Aborting state snapshot generation", dl.root, append(accountHash[:], storeIt.Key...))
|
||||
abort <- stats
|
||||
return
|
||||
}
|
||||
@@ -238,7 +240,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
}
|
||||
}
|
||||
if time.Since(logged) > 8*time.Second {
|
||||
stats.Log("Generating state snapshot", accIt.Key)
|
||||
stats.Log("Generating state snapshot", dl.root, accIt.Key)
|
||||
logged = time.Now()
|
||||
}
|
||||
// Some account processed, unmark the marker
|
||||
|
@@ -193,7 +193,7 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||
dl.genAbort <- abort
|
||||
|
||||
if stats = <-abort; stats != nil {
|
||||
stats.Log("Journalling in-progress snapshot", dl.genMarker)
|
||||
stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
|
||||
}
|
||||
}
|
||||
// Ensure the layer didn't get stale
|
||||
|
@@ -263,6 +263,13 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("snapshot [%#x] is disk layer", root)
|
||||
}
|
||||
// If the generator is still running, use a more aggressive cap
|
||||
diff.origin.lock.RLock()
|
||||
if diff.origin.genMarker != nil && layers > 8 {
|
||||
layers = 8
|
||||
}
|
||||
diff.origin.lock.RUnlock()
|
||||
|
||||
// Run the internal capping and discard all stale layers
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
@@ -375,22 +375,21 @@ func (s *stateObject) CommitTrie(db Database) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AddBalance removes amount from c's balance.
|
||||
// AddBalance adds amount to s's balance.
|
||||
// It is used to add funds to the destination account of a transfer.
|
||||
func (s *stateObject) AddBalance(amount *big.Int) {
|
||||
// EIP158: We must check emptiness for the objects such that the account
|
||||
// EIP161: We must check emptiness for the objects such that the account
|
||||
// clearing (0,0,0 objects) can take effect.
|
||||
if amount.Sign() == 0 {
|
||||
if s.empty() {
|
||||
s.touch()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
s.SetBalance(new(big.Int).Add(s.Balance(), amount))
|
||||
}
|
||||
|
||||
// SubBalance removes amount from c's balance.
|
||||
// SubBalance removes amount from s's balance.
|
||||
// It is used to remove funds from the origin account of a transfer.
|
||||
func (s *stateObject) SubBalance(amount *big.Int) {
|
||||
if amount.Sign() == 0 {
|
||||
@@ -455,7 +454,7 @@ func (s *stateObject) Code(db Database) []byte {
|
||||
}
|
||||
|
||||
// CodeSize returns the size of the contract code associated with this object,
|
||||
// or zero if none. This methos is an almost mirror of Code, but uses a cache
|
||||
// or zero if none. This method is an almost mirror of Code, but uses a cache
|
||||
// inside the database to avoid loading codes seen recently.
|
||||
func (s *stateObject) CodeSize(db Database) int {
|
||||
if s.code != nil {
|
||||
|
@@ -25,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -42,9 +43,6 @@ type revision struct {
|
||||
var (
|
||||
// emptyRoot is the known root hash of an empty trie.
|
||||
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
|
||||
// emptyCode is the known hash of the empty EVM bytecode.
|
||||
emptyCode = crypto.Keccak256Hash(nil)
|
||||
)
|
||||
|
||||
type proofList [][]byte
|
||||
@@ -58,7 +56,7 @@ func (n *proofList) Delete(key []byte) error {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// StateDBs within the ethereum protocol are used to store anything
|
||||
// StateDB structs within the ethereum protocol are used to store anything
|
||||
// within the merkle trie. StateDBs take care of caching and storing
|
||||
// nested states. It's the general query interface to retrieve:
|
||||
// * Contracts
|
||||
@@ -115,7 +113,7 @@ type StateDB struct {
|
||||
SnapshotCommits time.Duration
|
||||
}
|
||||
|
||||
// Create a new state from a given trie.
|
||||
// New creates a new state from a given trie.
|
||||
func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
|
||||
tr, err := db.OpenTrie(root)
|
||||
if err != nil {
|
||||
@@ -250,7 +248,7 @@ func (s *StateDB) Empty(addr common.Address) bool {
|
||||
return so == nil || so.empty()
|
||||
}
|
||||
|
||||
// Retrieve the balance from the given address or 0 if object not found
|
||||
// GetBalance retrieves the balance from the given address or 0 if object not found
|
||||
func (s *StateDB) GetBalance(addr common.Address) *big.Int {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
@@ -318,7 +316,7 @@ func (s *StateDB) GetProof(a common.Address) ([][]byte, error) {
|
||||
return [][]byte(proof), err
|
||||
}
|
||||
|
||||
// GetProof returns the StorageProof for given key
|
||||
// GetStorageProof returns the StorageProof for given key
|
||||
func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
|
||||
var proof proofList
|
||||
trie := s.StorageTrie(a)
|
||||
@@ -505,7 +503,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
}
|
||||
// If no live objects are available, attempt to use snapshots
|
||||
var (
|
||||
data Account
|
||||
data *Account
|
||||
err error
|
||||
)
|
||||
if s.snap != nil {
|
||||
@@ -517,11 +515,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
if acc == nil {
|
||||
return nil
|
||||
}
|
||||
data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash
|
||||
data = &Account{
|
||||
Nonce: acc.Nonce,
|
||||
Balance: acc.Balance,
|
||||
CodeHash: acc.CodeHash,
|
||||
Root: common.BytesToHash(acc.Root),
|
||||
}
|
||||
if len(data.CodeHash) == 0 {
|
||||
data.CodeHash = emptyCodeHash
|
||||
}
|
||||
data.Root = common.BytesToHash(acc.Root)
|
||||
if data.Root == (common.Hash{}) {
|
||||
data.Root = emptyRoot
|
||||
}
|
||||
@@ -540,13 +542,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
if len(enc) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
data = new(Account)
|
||||
if err := rlp.DecodeBytes(enc, data); err != nil {
|
||||
log.Error("Failed to decode state object", "addr", addr, "err", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Insert into the live set
|
||||
obj := newObject(s, addr, data)
|
||||
obj := newObject(s, addr, *data)
|
||||
s.setStateObject(obj)
|
||||
return obj
|
||||
}
|
||||
@@ -555,7 +558,7 @@ func (s *StateDB) setStateObject(object *stateObject) {
|
||||
s.stateObjects[object.Address()] = object
|
||||
}
|
||||
|
||||
// Retrieve a state object or create a new state object if nil.
|
||||
// GetOrNewStateObject retrieves a state object or create a new state object if nil.
|
||||
func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject == nil {
|
||||
@@ -584,7 +587,10 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
|
||||
s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
|
||||
}
|
||||
s.setStateObject(newobj)
|
||||
return newobj, prev
|
||||
if prev != nil && !prev.deleted {
|
||||
return newobj, prev
|
||||
}
|
||||
return newobj, nil
|
||||
}
|
||||
|
||||
// CreateAccount explicitly creates a state object. If a state object with the address
|
||||
@@ -811,11 +817,12 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
s.IntermediateRoot(deleteEmptyObjects)
|
||||
|
||||
// Commit objects to the trie, measuring the elapsed time
|
||||
codeWriter := s.db.TrieDB().DiskDB().NewBatch()
|
||||
for addr := range s.stateObjectsDirty {
|
||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||
// Write any contract code associated with the state object
|
||||
if obj.code != nil && obj.dirtyCode {
|
||||
s.db.TrieDB().InsertBlob(common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
obj.dirtyCode = false
|
||||
}
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
@@ -827,6 +834,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
if len(s.stateObjectsDirty) > 0 {
|
||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
}
|
||||
if codeWriter.ValueSize() > 0 {
|
||||
if err := codeWriter.Write(); err != nil {
|
||||
log.Crit("Failed to commit dirty codes", "error", err)
|
||||
}
|
||||
}
|
||||
// Write the account trie changes, measuing the amount of wasted time
|
||||
var start time.Time
|
||||
if metrics.EnabledExpensive {
|
||||
@@ -835,17 +847,13 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
// The onleaf func is called _serially_, so we can reuse the same account
|
||||
// for unmarshalling every time.
|
||||
var account Account
|
||||
root, err := s.trie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
root, err := s.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
|
||||
if err := rlp.DecodeBytes(leaf, &account); err != nil {
|
||||
return nil
|
||||
}
|
||||
if account.Root != emptyRoot {
|
||||
s.db.TrieDB().Reference(account.Root, parent)
|
||||
}
|
||||
code := common.BytesToHash(account.CodeHash)
|
||||
if code != emptyCode {
|
||||
s.db.TrieDB().Reference(code, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if metrics.EnabledExpensive {
|
||||
|
@@ -55,7 +55,7 @@ func TestUpdateLeaks(t *testing.T) {
|
||||
}
|
||||
|
||||
root := state.IntermediateRoot(false)
|
||||
if err := state.Database().TrieDB().Commit(root, false); err != nil {
|
||||
if err := state.Database().TrieDB().Commit(root, false, nil); err != nil {
|
||||
t.Errorf("can not commit trie %v to persistent database", root.Hex())
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit transition state: %v", err)
|
||||
}
|
||||
if err = transState.Database().TrieDB().Commit(transRoot, false); err != nil {
|
||||
if err = transState.Database().TrieDB().Commit(transRoot, false, nil); err != nil {
|
||||
t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit final state: %v", err)
|
||||
}
|
||||
if err = finalState.Database().TrieDB().Commit(finalRoot, false); err != nil {
|
||||
if err = finalState.Database().TrieDB().Commit(finalRoot, false, nil); err != nil {
|
||||
t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex())
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCopy tests that copying a statedb object indeed makes the original and
|
||||
// TestCopy tests that copying a StateDB object indeed makes the original and
|
||||
// the copy independent of each other. This test is a regression test against
|
||||
// https://github.com/ethereum/go-ethereum/pull/15549.
|
||||
func TestCopy(t *testing.T) {
|
||||
@@ -647,11 +647,11 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestDeleteCreateRevert tests a weird state transition corner case that we hit
|
||||
// while changing the internals of statedb. The workflow is that a contract is
|
||||
// self destructed, then in a followup transaction (but same block) it's created
|
||||
// while changing the internals of StateDB. The workflow is that a contract is
|
||||
// self-destructed, then in a follow-up transaction (but same block) it's created
|
||||
// again and the transaction reverted.
|
||||
//
|
||||
// The original statedb implementation flushed dirty objects to the tries after
|
||||
// The original StateDB implementation flushed dirty objects to the tries after
|
||||
// each transaction, so this works ok. The rework accumulated writes in memory
|
||||
// first, but the journal wiped the entire state object on create-revert.
|
||||
func TestDeleteCreateRevert(t *testing.T) {
|
||||
@@ -681,7 +681,7 @@ func TestDeleteCreateRevert(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMissingTrieNodes tests that if the statedb fails to load parts of the trie,
|
||||
// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie,
|
||||
// the Commit operation fails with an error
|
||||
// If we are missing trie nodes, we should not continue writing to the trie
|
||||
func TestMissingTrieNodes(t *testing.T) {
|
||||
|
@@ -28,13 +28,13 @@ import (
|
||||
// NewStateSync create a new state trie download scheduler.
|
||||
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom) *trie.Sync {
|
||||
var syncer *trie.Sync
|
||||
callback := func(leaf []byte, parent common.Hash) error {
|
||||
callback := func(path []byte, leaf []byte, parent common.Hash) error {
|
||||
var obj Account
|
||||
if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
|
||||
return err
|
||||
}
|
||||
syncer.AddSubTrie(obj.Root, 64, parent, nil)
|
||||
syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent)
|
||||
syncer.AddSubTrie(obj.Root, path, parent, nil)
|
||||
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent)
|
||||
return nil
|
||||
}
|
||||
syncer = trie.NewSync(root, database, callback, bloom)
|
||||
|
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
@@ -44,7 +45,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
|
||||
state, _ := New(common.Hash{}, db, nil)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
accounts := []*testAccount{}
|
||||
var accounts []*testAccount
|
||||
for i := byte(0); i < 96; i++ {
|
||||
obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
acc := &testAccount{address: common.BytesToAddress([]byte{i})}
|
||||
@@ -59,6 +60,11 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
|
||||
obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i})
|
||||
acc.code = []byte{i, i, i, i, i}
|
||||
}
|
||||
if i%5 == 0 {
|
||||
for j := byte(0); j < 5; j++ {
|
||||
obj.SetState(db, crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}), crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}))
|
||||
}
|
||||
}
|
||||
state.updateStateObject(obj)
|
||||
accounts = append(accounts, acc)
|
||||
}
|
||||
@@ -126,43 +132,109 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error {
|
||||
// Tests that an empty state is not scheduled for syncing.
|
||||
func TestEmptyStateSync(t *testing.T) {
|
||||
empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
if req := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New())).Missing(1); len(req) != 0 {
|
||||
t.Errorf("content requested for empty state: %v", req)
|
||||
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New()))
|
||||
if nodes, paths, codes := sync.Missing(1); len(nodes) != 0 || len(paths) != 0 || len(codes) != 0 {
|
||||
t.Errorf(" content requested for empty state: %v, %v, %v", nodes, paths, codes)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that given a root hash, a state can sync iteratively on a single thread,
|
||||
// requesting retrieval tasks and returning all of them in one go.
|
||||
func TestIterativeStateSyncIndividual(t *testing.T) { testIterativeStateSync(t, 1) }
|
||||
func TestIterativeStateSyncBatched(t *testing.T) { testIterativeStateSync(t, 100) }
|
||||
func TestIterativeStateSyncIndividual(t *testing.T) {
|
||||
testIterativeStateSync(t, 1, false, false)
|
||||
}
|
||||
func TestIterativeStateSyncBatched(t *testing.T) {
|
||||
testIterativeStateSync(t, 100, false, false)
|
||||
}
|
||||
func TestIterativeStateSyncIndividualFromDisk(t *testing.T) {
|
||||
testIterativeStateSync(t, 1, true, false)
|
||||
}
|
||||
func TestIterativeStateSyncBatchedFromDisk(t *testing.T) {
|
||||
testIterativeStateSync(t, 100, true, false)
|
||||
}
|
||||
func TestIterativeStateSyncIndividualByPath(t *testing.T) {
|
||||
testIterativeStateSync(t, 1, false, true)
|
||||
}
|
||||
func TestIterativeStateSyncBatchedByPath(t *testing.T) {
|
||||
testIterativeStateSync(t, 100, false, true)
|
||||
}
|
||||
|
||||
func testIterativeStateSync(t *testing.T, count int) {
|
||||
func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
if commit {
|
||||
srcDb.TrieDB().Commit(srcRoot, false, nil)
|
||||
}
|
||||
srcTrie, _ := trie.New(srcRoot, srcDb.TrieDB())
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
|
||||
|
||||
queue := append([]common.Hash{}, sched.Missing(count)...)
|
||||
for len(queue) > 0 {
|
||||
results := make([]trie.SyncResult, len(queue))
|
||||
for i, hash := range queue {
|
||||
nodes, paths, codes := sched.Missing(count)
|
||||
var (
|
||||
hashQueue []common.Hash
|
||||
pathQueue []trie.SyncPath
|
||||
)
|
||||
if !bypath {
|
||||
hashQueue = append(append(hashQueue[:0], nodes...), codes...)
|
||||
} else {
|
||||
hashQueue = append(hashQueue[:0], codes...)
|
||||
pathQueue = append(pathQueue[:0], paths...)
|
||||
}
|
||||
for len(hashQueue)+len(pathQueue) > 0 {
|
||||
results := make([]trie.SyncResult, len(hashQueue)+len(pathQueue))
|
||||
for i, hash := range hashQueue {
|
||||
data, err := srcDb.TrieDB().Node(hash)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
data, err = srcDb.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for hash %x", hash)
|
||||
}
|
||||
results[i] = trie.SyncResult{Hash: hash, Data: data}
|
||||
}
|
||||
if _, index, err := sched.Process(results); err != nil {
|
||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||
for i, path := range pathQueue {
|
||||
if len(path) == 1 {
|
||||
data, _, err := srcTrie.TryGetNode(path[0])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for path %x: %v", path, err)
|
||||
}
|
||||
results[len(hashQueue)+i] = trie.SyncResult{Hash: crypto.Keccak256Hash(data), Data: data}
|
||||
} else {
|
||||
var acc Account
|
||||
if err := rlp.DecodeBytes(srcTrie.Get(path[0]), &acc); err != nil {
|
||||
t.Fatalf("failed to decode account on path %x: %v", path, err)
|
||||
}
|
||||
stTrie, err := trie.New(acc.Root, srcDb.TrieDB())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retriev storage trie for path %x: %v", path, err)
|
||||
}
|
||||
data, _, err := stTrie.TryGetNode(path[1])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for path %x: %v", path, err)
|
||||
}
|
||||
results[len(hashQueue)+i] = trie.SyncResult{Hash: crypto.Keccak256Hash(data), Data: data}
|
||||
}
|
||||
}
|
||||
for _, result := range results {
|
||||
if err := sched.Process(result); err != nil {
|
||||
t.Errorf("failed to process result %v", err)
|
||||
}
|
||||
}
|
||||
batch := dstDb.NewBatch()
|
||||
if err := sched.Commit(batch); err != nil {
|
||||
t.Fatalf("failed to commit data: %v", err)
|
||||
}
|
||||
batch.Write()
|
||||
queue = append(queue[:0], sched.Missing(count)...)
|
||||
|
||||
nodes, paths, codes = sched.Missing(count)
|
||||
if !bypath {
|
||||
hashQueue = append(append(hashQueue[:0], nodes...), codes...)
|
||||
} else {
|
||||
hashQueue = append(hashQueue[:0], codes...)
|
||||
pathQueue = append(pathQueue[:0], paths...)
|
||||
}
|
||||
}
|
||||
// Cross check that the two states are in sync
|
||||
checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
|
||||
@@ -178,26 +250,35 @@ func TestIterativeDelayedStateSync(t *testing.T) {
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
|
||||
|
||||
queue := append([]common.Hash{}, sched.Missing(0)...)
|
||||
nodes, _, codes := sched.Missing(0)
|
||||
queue := append(append([]common.Hash{}, nodes...), codes...)
|
||||
|
||||
for len(queue) > 0 {
|
||||
// Sync only half of the scheduled nodes
|
||||
results := make([]trie.SyncResult, len(queue)/2+1)
|
||||
for i, hash := range queue[:len(results)] {
|
||||
data, err := srcDb.TrieDB().Node(hash)
|
||||
if err != nil {
|
||||
data, err = srcDb.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
results[i] = trie.SyncResult{Hash: hash, Data: data}
|
||||
}
|
||||
if _, index, err := sched.Process(results); err != nil {
|
||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||
for _, result := range results {
|
||||
if err := sched.Process(result); err != nil {
|
||||
t.Fatalf("failed to process result %v", err)
|
||||
}
|
||||
}
|
||||
batch := dstDb.NewBatch()
|
||||
if err := sched.Commit(batch); err != nil {
|
||||
t.Fatalf("failed to commit data: %v", err)
|
||||
}
|
||||
batch.Write()
|
||||
queue = append(queue[len(results):], sched.Missing(0)...)
|
||||
|
||||
nodes, _, codes = sched.Missing(0)
|
||||
queue = append(append(queue[len(results):], nodes...), codes...)
|
||||
}
|
||||
// Cross check that the two states are in sync
|
||||
checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
|
||||
@@ -218,7 +299,8 @@ func testIterativeRandomStateSync(t *testing.T, count int) {
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
|
||||
|
||||
queue := make(map[common.Hash]struct{})
|
||||
for _, hash := range sched.Missing(count) {
|
||||
nodes, _, codes := sched.Missing(count)
|
||||
for _, hash := range append(nodes, codes...) {
|
||||
queue[hash] = struct{}{}
|
||||
}
|
||||
for len(queue) > 0 {
|
||||
@@ -226,22 +308,29 @@ func testIterativeRandomStateSync(t *testing.T, count int) {
|
||||
results := make([]trie.SyncResult, 0, len(queue))
|
||||
for hash := range queue {
|
||||
data, err := srcDb.TrieDB().Node(hash)
|
||||
if err != nil {
|
||||
data, err = srcDb.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
results = append(results, trie.SyncResult{Hash: hash, Data: data})
|
||||
}
|
||||
// Feed the retrieved results back and queue new tasks
|
||||
if _, index, err := sched.Process(results); err != nil {
|
||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||
for _, result := range results {
|
||||
if err := sched.Process(result); err != nil {
|
||||
t.Fatalf("failed to process result %v", err)
|
||||
}
|
||||
}
|
||||
batch := dstDb.NewBatch()
|
||||
if err := sched.Commit(batch); err != nil {
|
||||
t.Fatalf("failed to commit data: %v", err)
|
||||
}
|
||||
batch.Write()
|
||||
|
||||
queue = make(map[common.Hash]struct{})
|
||||
for _, hash := range sched.Missing(count) {
|
||||
nodes, _, codes = sched.Missing(count)
|
||||
for _, hash := range append(nodes, codes...) {
|
||||
queue[hash] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +349,8 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
|
||||
|
||||
queue := make(map[common.Hash]struct{})
|
||||
for _, hash := range sched.Missing(0) {
|
||||
nodes, _, codes := sched.Missing(0)
|
||||
for _, hash := range append(nodes, codes...) {
|
||||
queue[hash] = struct{}{}
|
||||
}
|
||||
for len(queue) > 0 {
|
||||
@@ -270,6 +360,9 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
delete(queue, hash)
|
||||
|
||||
data, err := srcDb.TrieDB().Node(hash)
|
||||
if err != nil {
|
||||
data, err = srcDb.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
@@ -280,15 +373,21 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Feed the retrieved results back and queue new tasks
|
||||
if _, index, err := sched.Process(results); err != nil {
|
||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||
for _, result := range results {
|
||||
if err := sched.Process(result); err != nil {
|
||||
t.Fatalf("failed to process result %v", err)
|
||||
}
|
||||
}
|
||||
batch := dstDb.NewBatch()
|
||||
if err := sched.Commit(batch); err != nil {
|
||||
t.Fatalf("failed to commit data: %v", err)
|
||||
}
|
||||
batch.Write()
|
||||
for _, hash := range sched.Missing(0) {
|
||||
for _, result := range results {
|
||||
delete(queue, result.Hash)
|
||||
}
|
||||
nodes, _, codes = sched.Missing(0)
|
||||
for _, hash := range append(nodes, codes...) {
|
||||
queue[hash] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -302,27 +401,44 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// isCode reports whether the hash is contract code hash.
|
||||
isCode := func(hash common.Hash) bool {
|
||||
for _, acc := range srcAccounts {
|
||||
if hash == crypto.Keccak256Hash(acc.code) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot)
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
|
||||
|
||||
added := []common.Hash{}
|
||||
queue := append([]common.Hash{}, sched.Missing(1)...)
|
||||
var added []common.Hash
|
||||
|
||||
nodes, _, codes := sched.Missing(1)
|
||||
queue := append(append([]common.Hash{}, nodes...), codes...)
|
||||
|
||||
for len(queue) > 0 {
|
||||
// Fetch a batch of state nodes
|
||||
results := make([]trie.SyncResult, len(queue))
|
||||
for i, hash := range queue {
|
||||
data, err := srcDb.TrieDB().Node(hash)
|
||||
if err != nil {
|
||||
data, err = srcDb.ContractCode(common.Hash{}, hash)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
results[i] = trie.SyncResult{Hash: hash, Data: data}
|
||||
}
|
||||
// Process each of the state nodes
|
||||
if _, index, err := sched.Process(results); err != nil {
|
||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||
for _, result := range results {
|
||||
if err := sched.Process(result); err != nil {
|
||||
t.Fatalf("failed to process result %v", err)
|
||||
}
|
||||
}
|
||||
batch := dstDb.NewBatch()
|
||||
if err := sched.Commit(batch); err != nil {
|
||||
@@ -333,12 +449,9 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
added = append(added, result.Hash)
|
||||
}
|
||||
// Check that all known sub-tries added so far are complete or missing entirely.
|
||||
checkSubtries:
|
||||
for _, hash := range added {
|
||||
for _, acc := range srcAccounts {
|
||||
if hash == crypto.Keccak256Hash(acc.code) {
|
||||
continue checkSubtries // skip trie check of code nodes.
|
||||
}
|
||||
if isCode(hash) {
|
||||
continue
|
||||
}
|
||||
// Can't use checkStateConsistency here because subtrie keys may have odd
|
||||
// length and crash in LeafKey.
|
||||
@@ -347,17 +460,30 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Fetch the next batch to retrieve
|
||||
queue = append(queue[:0], sched.Missing(1)...)
|
||||
nodes, _, codes = sched.Missing(1)
|
||||
queue = append(append(queue[:0], nodes...), codes...)
|
||||
}
|
||||
// Sanity check that removing any node from the database is detected
|
||||
for _, node := range added[1:] {
|
||||
key := node.Bytes()
|
||||
value, _ := dstDb.Get(key)
|
||||
|
||||
dstDb.Delete(key)
|
||||
var (
|
||||
key = node.Bytes()
|
||||
code = isCode(node)
|
||||
val []byte
|
||||
)
|
||||
if code {
|
||||
val = rawdb.ReadCode(dstDb, node)
|
||||
rawdb.DeleteCode(dstDb, node)
|
||||
} else {
|
||||
val = rawdb.ReadTrieNode(dstDb, node)
|
||||
rawdb.DeleteTrieNode(dstDb, node)
|
||||
}
|
||||
if err := checkStateConsistency(dstDb, added[0]); err == nil {
|
||||
t.Fatalf("trie inconsistency not caught, missing: %x", key)
|
||||
}
|
||||
dstDb.Put(key, value)
|
||||
if code {
|
||||
rawdb.WriteCode(dstDb, node, val)
|
||||
} else {
|
||||
rawdb.WriteTrieNode(dstDb, node, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
106
core/tx_list.go
106
core/tx_list.go
@@ -99,7 +99,30 @@ func (m *txSortedMap) Forward(threshold uint64) types.Transactions {
|
||||
|
||||
// Filter iterates over the list of transactions and removes all of them for which
|
||||
// the specified function evaluates to true.
|
||||
// Filter, as opposed to 'filter', re-initialises the heap after the operation is done.
|
||||
// If you want to do several consecutive filterings, it's therefore better to first
|
||||
// do a .filter(func1) followed by .Filter(func2) or reheap()
|
||||
func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions {
|
||||
removed := m.filter(filter)
|
||||
// If transactions were removed, the heap and cache are ruined
|
||||
if len(removed) > 0 {
|
||||
m.reheap()
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
func (m *txSortedMap) reheap() {
|
||||
*m.index = make([]uint64, 0, len(m.items))
|
||||
for nonce := range m.items {
|
||||
*m.index = append(*m.index, nonce)
|
||||
}
|
||||
heap.Init(m.index)
|
||||
m.cache = nil
|
||||
}
|
||||
|
||||
// filter is identical to Filter, but **does not** regenerate the heap. This method
|
||||
// should only be used if followed immediately by a call to Filter or reheap()
|
||||
func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transactions {
|
||||
var removed types.Transactions
|
||||
|
||||
// Collect all the transactions to filter out
|
||||
@@ -109,14 +132,7 @@ func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transac
|
||||
delete(m.items, nonce)
|
||||
}
|
||||
}
|
||||
// If transactions were removed, the heap and cache are ruined
|
||||
if len(removed) > 0 {
|
||||
*m.index = make([]uint64, 0, len(m.items))
|
||||
for nonce := range m.items {
|
||||
*m.index = append(*m.index, nonce)
|
||||
}
|
||||
heap.Init(m.index)
|
||||
|
||||
m.cache = nil
|
||||
}
|
||||
return removed
|
||||
@@ -197,10 +213,7 @@ func (m *txSortedMap) Len() int {
|
||||
return len(m.items)
|
||||
}
|
||||
|
||||
// Flatten creates a nonce-sorted slice of transactions based on the loosely
|
||||
// sorted internal representation. The result of the sorting is cached in case
|
||||
// it's requested again before any modifications are made to the contents.
|
||||
func (m *txSortedMap) Flatten() types.Transactions {
|
||||
func (m *txSortedMap) flatten() types.Transactions {
|
||||
// If the sorting was not cached yet, create and cache it
|
||||
if m.cache == nil {
|
||||
m.cache = make(types.Transactions, 0, len(m.items))
|
||||
@@ -209,12 +222,27 @@ func (m *txSortedMap) Flatten() types.Transactions {
|
||||
}
|
||||
sort.Sort(types.TxByNonce(m.cache))
|
||||
}
|
||||
return m.cache
|
||||
}
|
||||
|
||||
// Flatten creates a nonce-sorted slice of transactions based on the loosely
|
||||
// sorted internal representation. The result of the sorting is cached in case
|
||||
// it's requested again before any modifications are made to the contents.
|
||||
func (m *txSortedMap) Flatten() types.Transactions {
|
||||
// Copy the cache to prevent accidental modifications
|
||||
txs := make(types.Transactions, len(m.cache))
|
||||
copy(txs, m.cache)
|
||||
cache := m.flatten()
|
||||
txs := make(types.Transactions, len(cache))
|
||||
copy(txs, cache)
|
||||
return txs
|
||||
}
|
||||
|
||||
// LastElement returns the last element of a flattened list, thus, the
|
||||
// transaction with the highest nonce
|
||||
func (m *txSortedMap) LastElement() *types.Transaction {
|
||||
cache := m.flatten()
|
||||
return cache[len(cache)-1]
|
||||
}
|
||||
|
||||
// txList is a "list" of transactions belonging to an account, sorted by account
|
||||
// nonce. The same type can be used both for storing contiguous transactions for
|
||||
// the executable/pending queue; and for storing gapped transactions for the non-
|
||||
@@ -252,7 +280,11 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
|
||||
// If there's an older better transaction, abort
|
||||
old := l.txs.Get(tx.Nonce())
|
||||
if old != nil {
|
||||
threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
|
||||
// threshold = oldGP * (100 + priceBump) / 100
|
||||
a := big.NewInt(100 + int64(priceBump))
|
||||
a = a.Mul(a, old.GasPrice())
|
||||
b := big.NewInt(100)
|
||||
threshold := a.Div(a, b)
|
||||
// Have to ensure that the new gas price is higher than the old gas
|
||||
// price as well as checking the percentage threshold to ensure that
|
||||
// this is accurate for low (Wei-level) gas price replacements
|
||||
@@ -296,20 +328,25 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions
|
||||
l.gascap = gasLimit
|
||||
|
||||
// Filter out all the transactions above the account's funds
|
||||
removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas() > gasLimit })
|
||||
removed := l.txs.Filter(func(tx *types.Transaction) bool {
|
||||
return tx.Gas() > gasLimit || tx.Cost().Cmp(costLimit) > 0
|
||||
})
|
||||
|
||||
// If the list was strict, filter anything above the lowest nonce
|
||||
if len(removed) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var invalids types.Transactions
|
||||
|
||||
if l.strict && len(removed) > 0 {
|
||||
// If the list was strict, filter anything above the lowest nonce
|
||||
if l.strict {
|
||||
lowest := uint64(math.MaxUint64)
|
||||
for _, tx := range removed {
|
||||
if nonce := tx.Nonce(); lowest > nonce {
|
||||
lowest = nonce
|
||||
}
|
||||
}
|
||||
invalids = l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
|
||||
invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
|
||||
}
|
||||
l.txs.reheap()
|
||||
return removed, invalids
|
||||
}
|
||||
|
||||
@@ -363,6 +400,12 @@ func (l *txList) Flatten() types.Transactions {
|
||||
return l.txs.Flatten()
|
||||
}
|
||||
|
||||
// LastElement returns the last element of a flattened list, thus, the
|
||||
// transaction with the highest nonce
|
||||
func (l *txList) LastElement() *types.Transaction {
|
||||
return l.txs.LastElement()
|
||||
}
|
||||
|
||||
// priceHeap is a heap.Interface implementation over transactions for retrieving
|
||||
// price-sorted transactions to discard when the pool fills up.
|
||||
type priceHeap []*types.Transaction
|
||||
@@ -495,8 +538,29 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo
|
||||
// Discard finds a number of most underpriced transactions, removes them from the
|
||||
// priced list and returns them for further removal from the entire pool.
|
||||
func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions {
|
||||
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
|
||||
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
|
||||
// If we have some local accountset, those will not be discarded
|
||||
if !local.empty() {
|
||||
// In case the list is filled to the brim with 'local' txs, we do this
|
||||
// little check to avoid unpacking / repacking the heap later on, which
|
||||
// is very expensive
|
||||
discardable := 0
|
||||
for _, tx := range *l.items {
|
||||
if !local.containsTx(tx) {
|
||||
discardable++
|
||||
}
|
||||
if discardable >= slots {
|
||||
break
|
||||
}
|
||||
}
|
||||
if slots > discardable {
|
||||
slots = discardable
|
||||
}
|
||||
}
|
||||
if slots == 0 {
|
||||
return nil
|
||||
}
|
||||
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
|
||||
save := make(types.Transactions, 0, len(*l.items)-slots) // Local underpriced transactions to keep
|
||||
|
||||
for len(*l.items) > 0 && slots > 0 {
|
||||
// Discard stale transactions if found during cleanup
|
||||
|
@@ -98,6 +98,7 @@ var (
|
||||
queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil)
|
||||
queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting
|
||||
queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds
|
||||
queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime
|
||||
|
||||
// General tx metrics
|
||||
knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil)
|
||||
@@ -362,9 +363,11 @@ func (pool *TxPool) loop() {
|
||||
}
|
||||
// Any non-locals old enough should be removed
|
||||
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
|
||||
for _, tx := range pool.queue[addr].Flatten() {
|
||||
list := pool.queue[addr].Flatten()
|
||||
for _, tx := range list {
|
||||
pool.removeTx(tx.Hash(), true)
|
||||
}
|
||||
queuedEvictionMeter.Mark(int64(len(list)))
|
||||
}
|
||||
}
|
||||
pool.mu.Unlock()
|
||||
@@ -614,6 +617,9 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
|
||||
pool.journalTx(from, tx)
|
||||
pool.queueTxEvent(tx)
|
||||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[from] = time.Now()
|
||||
return old != nil, nil
|
||||
}
|
||||
// New transaction isn't replacing a pending one, push into queue
|
||||
@@ -665,6 +671,10 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er
|
||||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
// If we never record the heartbeat, do it right now.
|
||||
if _, exist := pool.beats[from]; !exist {
|
||||
pool.beats[from] = time.Now()
|
||||
}
|
||||
return old != nil, nil
|
||||
}
|
||||
|
||||
@@ -696,7 +706,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
|
||||
// An older transaction was better, discard this
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed(1)
|
||||
|
||||
pendingDiscardMeter.Mark(1)
|
||||
return false
|
||||
}
|
||||
@@ -704,7 +713,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
|
||||
if old != nil {
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed(1)
|
||||
|
||||
pendingReplaceMeter.Mark(1)
|
||||
} else {
|
||||
// Nothing was replaced, bump the pending counter
|
||||
@@ -716,9 +724,10 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
||||
pool.beats[addr] = time.Now()
|
||||
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[addr] = time.Now()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -891,7 +900,6 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
|
||||
// If no more pending transactions are left, remove the list
|
||||
if pending.Empty() {
|
||||
delete(pool.pending, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
// Postpone any invalidated transactions
|
||||
for _, tx := range invalids {
|
||||
@@ -912,6 +920,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
|
||||
}
|
||||
if future.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1023,7 +1032,10 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
|
||||
defer close(done)
|
||||
|
||||
var promoteAddrs []common.Address
|
||||
if dirtyAccounts != nil {
|
||||
if dirtyAccounts != nil && reset == nil {
|
||||
// Only dirty accounts need to be promoted, unless we're resetting.
|
||||
// For resets, all addresses in the tx queue will be promoted and
|
||||
// the flatten operation can be avoided.
|
||||
promoteAddrs = dirtyAccounts.flatten()
|
||||
}
|
||||
pool.mu.Lock()
|
||||
@@ -1039,7 +1051,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
|
||||
}
|
||||
}
|
||||
// Reset needs promote for all addresses
|
||||
promoteAddrs = promoteAddrs[:0]
|
||||
promoteAddrs = make([]common.Address, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
promoteAddrs = append(promoteAddrs, addr)
|
||||
}
|
||||
@@ -1059,8 +1071,8 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
|
||||
|
||||
// Update all accounts to the latest known pending nonce
|
||||
for addr, list := range pool.pending {
|
||||
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
|
||||
pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1)
|
||||
highestPending := list.LastElement()
|
||||
pool.pendingNonces.set(addr, highestPending.Nonce()+1)
|
||||
}
|
||||
pool.mu.Unlock()
|
||||
|
||||
@@ -1229,6 +1241,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
|
||||
// Delete the entire queue entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
}
|
||||
return promoted
|
||||
@@ -1409,11 +1422,12 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
pool.enqueueTx(hash, tx)
|
||||
}
|
||||
pendingGauge.Dec(int64(len(gapped)))
|
||||
// This might happen in a reorg, so log it to the metering
|
||||
blockReorgInvalidatedTx.Mark(int64(len(gapped)))
|
||||
}
|
||||
// Delete the entire queue entry if it became empty.
|
||||
// Delete the entire pending entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.pending, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1457,6 +1471,10 @@ func (as *accountSet) contains(addr common.Address) bool {
|
||||
return exist
|
||||
}
|
||||
|
||||
func (as *accountSet) empty() bool {
|
||||
return len(as.accounts) == 0
|
||||
}
|
||||
|
||||
// containsTx checks if the sender of a given tx is within the set. If the sender
|
||||
// cannot be derived, this method returns false.
|
||||
func (as *accountSet) containsTx(tx *types.Transaction) bool {
|
||||
|
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// testTxPoolConfig is a transaction pool configuration without stateful disk
|
||||
@@ -54,7 +55,7 @@ type testBlockChain struct {
|
||||
func (bc *testBlockChain) CurrentBlock() *types.Block {
|
||||
return types.NewBlock(&types.Header{
|
||||
GasLimit: bc.gasLimit,
|
||||
}, nil, nil, nil)
|
||||
}, nil, nil, nil, new(trie.Trie))
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||
@@ -109,6 +110,7 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||
if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued {
|
||||
return fmt.Errorf("total priced transaction count %d != %d pending + %d queued", priced, pending, queued)
|
||||
}
|
||||
|
||||
// Ensure the next nonce to assign is the correct one
|
||||
for addr, txs := range pool.pending {
|
||||
// Find the last transaction
|
||||
@@ -868,7 +870,7 @@ func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) {
|
||||
func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
// Reduce the eviction interval to a testable amount
|
||||
defer func(old time.Duration) { evictionInterval = old }(evictionInterval)
|
||||
evictionInterval = time.Second
|
||||
evictionInterval = time.Millisecond * 100
|
||||
|
||||
// Create the pool to test the non-expiration enforcement
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
@@ -905,6 +907,22 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
// Allow the eviction interval to run
|
||||
time.Sleep(2 * evictionInterval)
|
||||
|
||||
// Transactions should not be evicted from the queue yet since lifetime duration has not passed
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 0 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0)
|
||||
}
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
// Wait a bit for eviction to run and clean up any leftovers, and ensure only the local remains
|
||||
time.Sleep(2 * config.Lifetime)
|
||||
|
||||
@@ -924,6 +942,72 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
// remove current transactions and increase nonce to prepare for a reset and cleanup
|
||||
statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2)
|
||||
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2)
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
||||
// make sure queue, pending are cleared
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 0 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0)
|
||||
}
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
// Queue gapped transactions
|
||||
if err := pool.AddLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil {
|
||||
t.Fatalf("failed to add remote transaction: %v", err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(1), remote)); err != nil {
|
||||
t.Fatalf("failed to add remote transaction: %v", err)
|
||||
}
|
||||
time.Sleep(5 * evictionInterval) // A half lifetime pass
|
||||
|
||||
// Queue executable transactions, the life cycle should be restarted.
|
||||
if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil {
|
||||
t.Fatalf("failed to add remote transaction: %v", err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), remote)); err != nil {
|
||||
t.Fatalf("failed to add remote transaction: %v", err)
|
||||
}
|
||||
time.Sleep(6 * evictionInterval)
|
||||
|
||||
// All gapped transactions shouldn't be kicked out
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 2 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
// The whole life time pass after last promotion, kick out stale transactions
|
||||
time.Sleep(2 * config.Lifetime)
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 2 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
if nolocals {
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
} else {
|
||||
if queued != 1 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that even if the transaction count belonging to a single account goes
|
||||
@@ -1890,11 +1974,15 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
|
||||
}
|
||||
|
||||
// Benchmarks the speed of batched transaction insertion.
|
||||
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100) }
|
||||
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000) }
|
||||
func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000) }
|
||||
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, false) }
|
||||
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, false) }
|
||||
func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, false) }
|
||||
|
||||
func benchmarkPoolBatchInsert(b *testing.B, size int) {
|
||||
func BenchmarkPoolBatchLocalInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, true) }
|
||||
func BenchmarkPoolBatchLocalInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, true) }
|
||||
func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, true) }
|
||||
|
||||
func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) {
|
||||
// Generate a batch of transactions to enqueue into the pool
|
||||
pool, key := setupTxPool()
|
||||
defer pool.Stop()
|
||||
@@ -1912,6 +2000,10 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) {
|
||||
// Benchmark importing the transactions into the queue
|
||||
b.ResetTimer()
|
||||
for _, batch := range batches {
|
||||
pool.AddRemotes(batch)
|
||||
if local {
|
||||
pool.AddLocals(batch)
|
||||
} else {
|
||||
pool.AddRemotes(batch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyRootHash = DeriveSha(Transactions{})
|
||||
EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
EmptyUncleHash = rlpHash([]*Header(nil))
|
||||
)
|
||||
|
||||
@@ -147,6 +147,17 @@ func rlpHash(x interface{}) (h common.Hash) {
|
||||
return h
|
||||
}
|
||||
|
||||
// EmptyBody returns true if there is no additional 'body' to complete the header
|
||||
// that is: no transactions and no uncles.
|
||||
func (h *Header) EmptyBody() bool {
|
||||
return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash
|
||||
}
|
||||
|
||||
// EmptyReceipts returns true if there are no receipts for this header/block.
|
||||
func (h *Header) EmptyReceipts() bool {
|
||||
return h.ReceiptHash == EmptyRootHash
|
||||
}
|
||||
|
||||
// Body is a simple (mutable, non-safe) data container for storing and moving
|
||||
// a block's data contents (transactions and uncles) together.
|
||||
type Body struct {
|
||||
@@ -210,14 +221,14 @@ type storageblock struct {
|
||||
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
|
||||
// are ignored and set to values derived from the given txs, uncles
|
||||
// and receipts.
|
||||
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt) *Block {
|
||||
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher Hasher) *Block {
|
||||
b := &Block{header: CopyHeader(header), td: new(big.Int)}
|
||||
|
||||
// TODO: panic if len(txs) != len(receipts)
|
||||
if len(txs) == 0 {
|
||||
b.header.TxHash = EmptyRootHash
|
||||
} else {
|
||||
b.header.TxHash = DeriveSha(Transactions(txs))
|
||||
b.header.TxHash = DeriveSha(Transactions(txs), hasher)
|
||||
b.transactions = make(Transactions, len(txs))
|
||||
copy(b.transactions, txs)
|
||||
}
|
||||
@@ -225,7 +236,7 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
|
||||
if len(receipts) == 0 {
|
||||
b.header.ReceiptHash = EmptyRootHash
|
||||
} else {
|
||||
b.header.ReceiptHash = DeriveSha(Receipts(receipts))
|
||||
b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher)
|
||||
b.header.Bloom = CreateBloom(receipts)
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// from bcValidBlockTest.json, "SimpleTx"
|
||||
@@ -90,6 +92,30 @@ func BenchmarkEncodeBlock(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
// testHasher is the helper tool for transaction/receipt list hashing.
|
||||
// The original hasher is trie, in order to get rid of import cycle,
|
||||
// use the testing hasher instead.
|
||||
type testHasher struct {
|
||||
hasher hash.Hash
|
||||
}
|
||||
|
||||
func newHasher() *testHasher {
|
||||
return &testHasher{hasher: sha3.NewLegacyKeccak256()}
|
||||
}
|
||||
|
||||
func (h *testHasher) Reset() {
|
||||
h.hasher.Reset()
|
||||
}
|
||||
|
||||
func (h *testHasher) Update(key, val []byte) {
|
||||
h.hasher.Write(key)
|
||||
h.hasher.Write(val)
|
||||
}
|
||||
|
||||
func (h *testHasher) Hash() common.Hash {
|
||||
return common.BytesToHash(h.hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func makeBenchBlock() *Block {
|
||||
var (
|
||||
key, _ = crypto.GenerateKey()
|
||||
@@ -128,5 +154,5 @@ func makeBenchBlock() *Block {
|
||||
Extra: []byte("benchmark uncle"),
|
||||
}
|
||||
}
|
||||
return NewBlock(header, txs, uncles, receipts)
|
||||
return NewBlock(header, txs, uncles, receipts, newHasher())
|
||||
}
|
||||
|
@@ -21,21 +21,28 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// DerivableList is the interface which can derive the hash.
|
||||
type DerivableList interface {
|
||||
Len() int
|
||||
GetRlp(i int) []byte
|
||||
}
|
||||
|
||||
func DeriveSha(list DerivableList) common.Hash {
|
||||
// Hasher is the tool used to calculate the hash of derivable list.
|
||||
type Hasher interface {
|
||||
Reset()
|
||||
Update([]byte, []byte)
|
||||
Hash() common.Hash
|
||||
}
|
||||
|
||||
func DeriveSha(list DerivableList, hasher Hasher) common.Hash {
|
||||
hasher.Reset()
|
||||
keybuf := new(bytes.Buffer)
|
||||
trie := new(trie.Trie)
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
keybuf.Reset()
|
||||
rlp.Encode(keybuf, uint(i))
|
||||
trie.Update(keybuf.Bytes(), list.GetRlp(i))
|
||||
hasher.Update(keybuf.Bytes(), list.GetRlp(i))
|
||||
}
|
||||
return trie.Hash()
|
||||
return hasher.Hash()
|
||||
}
|
||||
|
@@ -20,9 +20,9 @@ func (l Log) MarshalJSON() ([]byte, error) {
|
||||
Data hexutil.Bytes `json:"data" gencodec:"required"`
|
||||
BlockNumber hexutil.Uint64 `json:"blockNumber"`
|
||||
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
|
||||
TxIndex hexutil.Uint `json:"transactionIndex" gencodec:"required"`
|
||||
TxIndex hexutil.Uint `json:"transactionIndex"`
|
||||
BlockHash common.Hash `json:"blockHash"`
|
||||
Index hexutil.Uint `json:"logIndex" gencodec:"required"`
|
||||
Index hexutil.Uint `json:"logIndex"`
|
||||
Removed bool `json:"removed"`
|
||||
}
|
||||
var enc Log
|
||||
@@ -46,9 +46,9 @@ func (l *Log) UnmarshalJSON(input []byte) error {
|
||||
Data *hexutil.Bytes `json:"data" gencodec:"required"`
|
||||
BlockNumber *hexutil.Uint64 `json:"blockNumber"`
|
||||
TxHash *common.Hash `json:"transactionHash" gencodec:"required"`
|
||||
TxIndex *hexutil.Uint `json:"transactionIndex" gencodec:"required"`
|
||||
TxIndex *hexutil.Uint `json:"transactionIndex"`
|
||||
BlockHash *common.Hash `json:"blockHash"`
|
||||
Index *hexutil.Uint `json:"logIndex" gencodec:"required"`
|
||||
Index *hexutil.Uint `json:"logIndex"`
|
||||
Removed *bool `json:"removed"`
|
||||
}
|
||||
var dec Log
|
||||
@@ -74,17 +74,15 @@ func (l *Log) UnmarshalJSON(input []byte) error {
|
||||
return errors.New("missing required field 'transactionHash' for Log")
|
||||
}
|
||||
l.TxHash = *dec.TxHash
|
||||
if dec.TxIndex == nil {
|
||||
return errors.New("missing required field 'transactionIndex' for Log")
|
||||
if dec.TxIndex != nil {
|
||||
l.TxIndex = uint(*dec.TxIndex)
|
||||
}
|
||||
l.TxIndex = uint(*dec.TxIndex)
|
||||
if dec.BlockHash != nil {
|
||||
l.BlockHash = *dec.BlockHash
|
||||
}
|
||||
if dec.Index == nil {
|
||||
return errors.New("missing required field 'logIndex' for Log")
|
||||
if dec.Index != nil {
|
||||
l.Index = uint(*dec.Index)
|
||||
}
|
||||
l.Index = uint(*dec.Index)
|
||||
if dec.Removed != nil {
|
||||
l.Removed = *dec.Removed
|
||||
}
|
||||
|
@@ -44,11 +44,11 @@ type Log struct {
|
||||
// hash of the transaction
|
||||
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
|
||||
// index of the transaction in the block
|
||||
TxIndex uint `json:"transactionIndex" gencodec:"required"`
|
||||
TxIndex uint `json:"transactionIndex"`
|
||||
// hash of the block in which the transaction was included
|
||||
BlockHash common.Hash `json:"blockHash"`
|
||||
// index of the log in the block
|
||||
Index uint `json:"logIndex" gencodec:"required"`
|
||||
Index uint `json:"logIndex"`
|
||||
|
||||
// The Removed field is true if this log was reverted due to a chain reorganisation.
|
||||
// You must pay attention to this field if you receive logs through a filter query.
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"io"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -36,7 +37,9 @@ var (
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
data txdata
|
||||
data txdata // Consensus contents of a transaction
|
||||
time time.Time // Time first seen locally (spam avoidance)
|
||||
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
@@ -100,8 +103,10 @@ func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit
|
||||
if gasPrice != nil {
|
||||
d.Price.Set(gasPrice)
|
||||
}
|
||||
|
||||
return &Transaction{data: d}
|
||||
return &Transaction{
|
||||
data: d,
|
||||
time: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ChainId returns which chain id this transaction was signed for (if at all)
|
||||
@@ -134,8 +139,8 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||
err := s.Decode(&tx.data)
|
||||
if err == nil {
|
||||
tx.size.Store(common.StorageSize(rlp.ListSize(size)))
|
||||
tx.time = time.Now()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -153,7 +158,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||
if err := dec.UnmarshalJSON(input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
withSignature := dec.V.Sign() != 0 || dec.R.Sign() != 0 || dec.S.Sign() != 0
|
||||
if withSignature {
|
||||
var V byte
|
||||
@@ -167,8 +171,10 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||
return ErrInvalidSig
|
||||
}
|
||||
}
|
||||
|
||||
*tx = Transaction{data: dec}
|
||||
*tx = Transaction{
|
||||
data: dec,
|
||||
time: time.Now(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -246,7 +252,10 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpy := &Transaction{data: tx.data}
|
||||
cpy := &Transaction{
|
||||
data: tx.data,
|
||||
time: tx.time,
|
||||
}
|
||||
cpy.data.R, cpy.data.S, cpy.data.V = r, s, v
|
||||
return cpy, nil
|
||||
}
|
||||
@@ -306,19 +315,27 @@ func (s TxByNonce) Len() int { return len(s) }
|
||||
func (s TxByNonce) Less(i, j int) bool { return s[i].data.AccountNonce < s[j].data.AccountNonce }
|
||||
func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// TxByPrice implements both the sort and the heap interface, making it useful
|
||||
// TxByPriceAndTime implements both the sort and the heap interface, making it useful
|
||||
// for all at once sorting as well as individually adding and removing elements.
|
||||
type TxByPrice Transactions
|
||||
type TxByPriceAndTime Transactions
|
||||
|
||||
func (s TxByPrice) Len() int { return len(s) }
|
||||
func (s TxByPrice) Less(i, j int) bool { return s[i].data.Price.Cmp(s[j].data.Price) > 0 }
|
||||
func (s TxByPrice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s TxByPriceAndTime) Len() int { return len(s) }
|
||||
func (s TxByPriceAndTime) Less(i, j int) bool {
|
||||
// If the prices are equal, use the time the transaction was first seen for
|
||||
// deterministic sorting
|
||||
cmp := s[i].data.Price.Cmp(s[j].data.Price)
|
||||
if cmp == 0 {
|
||||
return s[i].time.Before(s[j].time)
|
||||
}
|
||||
return cmp > 0
|
||||
}
|
||||
func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func (s *TxByPrice) Push(x interface{}) {
|
||||
func (s *TxByPriceAndTime) Push(x interface{}) {
|
||||
*s = append(*s, x.(*Transaction))
|
||||
}
|
||||
|
||||
func (s *TxByPrice) Pop() interface{} {
|
||||
func (s *TxByPriceAndTime) Pop() interface{} {
|
||||
old := *s
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
@@ -331,7 +348,7 @@ func (s *TxByPrice) Pop() interface{} {
|
||||
// entire batches of transactions for non-executable accounts.
|
||||
type TransactionsByPriceAndNonce struct {
|
||||
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions
|
||||
heads TxByPrice // Next transaction for each unique account (price heap)
|
||||
heads TxByPriceAndTime // Next transaction for each unique account (price heap)
|
||||
signer Signer // Signer for the set of transactions
|
||||
}
|
||||
|
||||
@@ -341,8 +358,8 @@ type TransactionsByPriceAndNonce struct {
|
||||
// Note, the input map is reowned so the caller should not interact any more with
|
||||
// if after providing it to the constructor.
|
||||
func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce {
|
||||
// Initialize a price based heap with the head transactions
|
||||
heads := make(TxByPrice, 0, len(txs))
|
||||
// Initialize a price and received time based heap with the head transactions
|
||||
heads := make(TxByPriceAndTime, 0, len(txs))
|
||||
for from, accTxs := range txs {
|
||||
heads = append(heads, accTxs[0])
|
||||
// Ensure the sender address is from the signer
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -127,8 +128,8 @@ func TestTransactionPriceNonceSort(t *testing.T) {
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
|
||||
signer := HomesteadSigner{}
|
||||
|
||||
// Generate a batch of transactions with overlapping values, but shifted nonces
|
||||
groups := map[common.Address]Transactions{}
|
||||
for start, key := range keys {
|
||||
@@ -155,12 +156,10 @@ func TestTransactionPriceNonceSort(t *testing.T) {
|
||||
// Make sure the nonce order is valid
|
||||
for j, txj := range txs[i+1:] {
|
||||
fromj, _ := Sender(signer, txj)
|
||||
|
||||
if fromi == fromj && txi.Nonce() > txj.Nonce() {
|
||||
t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
|
||||
}
|
||||
}
|
||||
|
||||
// If the next tx has different from account, the price must be lower than the current one
|
||||
if i+1 < len(txs) {
|
||||
next := txs[i+1]
|
||||
@@ -172,6 +171,54 @@ func TestTransactionPriceNonceSort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if multiple transactions have the same price, the ones seen earlier
|
||||
// are prioritized to avoid network spam attacks aiming for a specific ordering.
|
||||
func TestTransactionTimeSort(t *testing.T) {
|
||||
// Generate a batch of accounts to start with
|
||||
keys := make([]*ecdsa.PrivateKey, 5)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
signer := HomesteadSigner{}
|
||||
|
||||
// Generate a batch of transactions with overlapping prices, but different creation times
|
||||
groups := map[common.Address]Transactions{}
|
||||
for start, key := range keys {
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
tx, _ := SignTx(NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key)
|
||||
tx.time = time.Unix(0, int64(len(keys)-start))
|
||||
|
||||
groups[addr] = append(groups[addr], tx)
|
||||
}
|
||||
// Sort the transactions and cross check the nonce ordering
|
||||
txset := NewTransactionsByPriceAndNonce(signer, groups)
|
||||
|
||||
txs := Transactions{}
|
||||
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
|
||||
txs = append(txs, tx)
|
||||
txset.Shift()
|
||||
}
|
||||
if len(txs) != len(keys) {
|
||||
t.Errorf("expected %d transactions, found %d", len(keys), len(txs))
|
||||
}
|
||||
for i, txi := range txs {
|
||||
fromi, _ := Sender(signer, txi)
|
||||
if i+1 < len(txs) {
|
||||
next := txs[i+1]
|
||||
fromNext, _ := Sender(signer, next)
|
||||
|
||||
if txi.GasPrice().Cmp(next.GasPrice()) < 0 {
|
||||
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
|
||||
}
|
||||
// Make sure time order is ascending if the txs have the same gas price
|
||||
if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.time.After(next.time) {
|
||||
t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.time, i+1, fromNext[:4], next.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransactionJSON tests serializing/de-serializing to/from JSON.
|
||||
func TestTransactionJSON(t *testing.T) {
|
||||
key, err := crypto.GenerateKey()
|
||||
|
@@ -112,7 +112,13 @@ func (c *Contract) validJumpSubdest(udest uint64) bool {
|
||||
// isCode returns true if the provided PC location is an actual opcode, as
|
||||
// opposed to a data-segment following a PUSHN operation.
|
||||
func (c *Contract) isCode(udest uint64) bool {
|
||||
// Do we already have an analysis laying around?
|
||||
if c.analysis != nil {
|
||||
return c.analysis.codeSegment(udest)
|
||||
}
|
||||
// Do we have a contract hash already?
|
||||
// If we do have a hash, that means it's a 'regular' contract. For regular
|
||||
// contracts ( not temporary initcode), we store the analysis in a map
|
||||
if c.CodeHash != (common.Hash{}) {
|
||||
// Does parent context have the analysis?
|
||||
analysis, exist := c.jumpdests[c.CodeHash]
|
||||
|
@@ -102,12 +102,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
|
||||
}
|
||||
|
||||
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
|
||||
gas := p.RequiredGas(input)
|
||||
if contract.UseGas(gas) {
|
||||
return p.Run(input)
|
||||
// It returns
|
||||
// - the returned bytes,
|
||||
// - the _remaining_ gas,
|
||||
// - any error that occurred
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
gasCost := p.RequiredGas(input)
|
||||
if suppliedGas < gasCost {
|
||||
return nil, 0, ErrOutOfGas
|
||||
}
|
||||
return nil, ErrOutOfGas
|
||||
suppliedGas -= gasCost
|
||||
output, err := p.Run(input)
|
||||
return output, suppliedGas, err
|
||||
}
|
||||
|
||||
// ECRECOVER implemented as a native contract.
|
||||
@@ -197,6 +203,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
|
||||
type bigModExp struct{}
|
||||
|
||||
var (
|
||||
big0 = big.NewInt(0)
|
||||
big1 = big.NewInt(1)
|
||||
big4 = big.NewInt(4)
|
||||
big8 = big.NewInt(8)
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -72,10 +71,9 @@ var blake2FMalformedInputTests = []precompiledFailureTest{
|
||||
func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
||||
nil, new(big.Int), p.RequiredGas(in))
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
|
||||
if res, err := RunPrecompiledContract(p, in, contract); err != nil {
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
|
||||
t.Error(err)
|
||||
} else if common.Bytes2Hex(res) != test.Expected {
|
||||
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
||||
@@ -91,10 +89,10 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
||||
func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
||||
nil, new(big.Int), p.RequiredGas(in)-1)
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
|
||||
_, err := RunPrecompiledContract(p, in, contract)
|
||||
gas := p.RequiredGas(in) - 1
|
||||
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
_, _, err := RunPrecompiledContract(p, in, gas)
|
||||
if err.Error() != "out of gas" {
|
||||
t.Errorf("Expected error [out of gas], got [%v]", err)
|
||||
}
|
||||
@@ -109,11 +107,9 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
||||
func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("31337")),
|
||||
nil, new(big.Int), p.RequiredGas(in))
|
||||
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
_, err := RunPrecompiledContract(p, in, contract)
|
||||
_, _, err := RunPrecompiledContract(p, in, gas)
|
||||
if err.Error() != test.ExpectedError {
|
||||
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
||||
}
|
||||
@@ -132,8 +128,6 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
reqGas := p.RequiredGas(in)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
||||
nil, new(big.Int), reqGas)
|
||||
|
||||
var (
|
||||
res []byte
|
||||
@@ -141,14 +135,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
||||
data = make([]byte, len(in))
|
||||
)
|
||||
|
||||
bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(bench *testing.B) {
|
||||
bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) {
|
||||
bench.ReportAllocs()
|
||||
start := time.Now().Nanosecond()
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
contract.Gas = reqGas
|
||||
copy(data, in)
|
||||
res, err = RunPrecompiledContract(p, data, contract)
|
||||
res, _, err = RunPrecompiledContract(p, data, reqGas)
|
||||
}
|
||||
bench.StopTimer()
|
||||
elapsed := float64(time.Now().Nanosecond() - start)
|
||||
|
@@ -68,12 +68,11 @@ func enable1884(jt *JumpTable) {
|
||||
jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884
|
||||
|
||||
// New opcode
|
||||
jt[SELFBALANCE] = operation{
|
||||
jt[SELFBALANCE] = &operation{
|
||||
execute: opSelfBalance,
|
||||
constantGas: GasFastStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,12 +86,11 @@ func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx
|
||||
// - Adds an opcode that returns the current chain’s EIP-155 unique identifier
|
||||
func enable1344(jt *JumpTable) {
|
||||
// New opcode
|
||||
jt[CHAINID] = operation{
|
||||
jt[CHAINID] = &operation{
|
||||
execute: opChainID,
|
||||
constantGas: GasQuickStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,29 +111,26 @@ func enable2200(jt *JumpTable) {
|
||||
// - Adds opcodes that jump to and return from subroutines
|
||||
func enable2315(jt *JumpTable) {
|
||||
// New opcode
|
||||
jt[BEGINSUB] = operation{
|
||||
jt[BEGINSUB] = &operation{
|
||||
execute: opBeginSub,
|
||||
constantGas: GasQuickStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
valid: true,
|
||||
}
|
||||
// New opcode
|
||||
jt[JUMPSUB] = operation{
|
||||
jt[JUMPSUB] = &operation{
|
||||
execute: opJumpSub,
|
||||
constantGas: GasSlowStep,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
jumps: true,
|
||||
valid: true,
|
||||
}
|
||||
// New opcode
|
||||
jt[RETURNSUB] = operation{
|
||||
jt[RETURNSUB] = &operation{
|
||||
execute: opReturnSub,
|
||||
constantGas: GasFastStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
valid: true,
|
||||
jumps: true,
|
||||
}
|
||||
}
|
||||
|
187
core/vm/evm.go
187
core/vm/evm.go
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// emptyCodeHash is used by create to ensure deployment is disallowed to already
|
||||
@@ -41,23 +42,24 @@ type (
|
||||
GetHashFunc func(uint64) common.Hash
|
||||
)
|
||||
|
||||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||
var precompiles map[common.Address]PrecompiledContract
|
||||
switch {
|
||||
case evm.chainRules.IsYoloV1:
|
||||
precompiles = PrecompiledContractsYoloV1
|
||||
case evm.chainRules.IsIstanbul:
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
case evm.chainRules.IsByzantium:
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
default:
|
||||
precompiles = PrecompiledContractsHomestead
|
||||
}
|
||||
p, ok := precompiles[addr]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
|
||||
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
|
||||
if contract.CodeAddr != nil {
|
||||
precompiles := PrecompiledContractsHomestead
|
||||
if evm.chainRules.IsByzantium {
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
}
|
||||
if evm.chainRules.IsIstanbul {
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
}
|
||||
if evm.chainRules.IsYoloV1 {
|
||||
precompiles = PrecompiledContractsYoloV1
|
||||
}
|
||||
if p := precompiles[*contract.CodeAddr]; p != nil {
|
||||
return RunPrecompiledContract(p, input, contract)
|
||||
}
|
||||
}
|
||||
for _, interpreter := range evm.interpreters {
|
||||
if interpreter.CanRun(contract.Code) {
|
||||
if evm.interpreter != interpreter {
|
||||
@@ -199,22 +201,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
// Fail if we're trying to transfer more than the available balance
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
var (
|
||||
to = AccountRef(addr)
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
)
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
precompiles := PrecompiledContractsHomestead
|
||||
if evm.chainRules.IsByzantium {
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
}
|
||||
if evm.chainRules.IsIstanbul {
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
}
|
||||
if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||
@@ -224,35 +218,47 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
}
|
||||
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, to, value, gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
|
||||
// Even if the account has no code, we need to continue because it might be a precompile
|
||||
start := time.Now()
|
||||
evm.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||
|
||||
// Capture the tracer start/end events in debug mode
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||
|
||||
defer func() { // Lazy evaluation of the parameters
|
||||
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
}()
|
||||
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
|
||||
evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
|
||||
}(gas, time.Now())
|
||||
}
|
||||
ret, err = run(evm, contract, input, false)
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
code := evm.StateDB.GetCode(addr)
|
||||
if len(code) == 0 {
|
||||
ret, err = nil, nil // gas is unchanged
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// If the account has no code, we can abort here
|
||||
// The depth-check is already done, and precompiles handled above
|
||||
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
|
||||
ret, err = run(evm, contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
}
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
// when we're in homestead this also counts for code storage gas errors.
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
// TODO: consider clearing up unused snapshots:
|
||||
//} else {
|
||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
// CallCode executes the contract associated with the addr with the given input
|
||||
@@ -277,23 +283,27 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
var (
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
to = AccountRef(caller.Address())
|
||||
)
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, to, value, gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
ret, err = run(evm, contract, input, false)
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = run(evm, contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
// DelegateCall executes the contract associated with the addr with the given input
|
||||
@@ -309,22 +319,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
var (
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
to = AccountRef(caller.Address())
|
||||
)
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
ret, err = run(evm, contract, input, false)
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = run(evm, contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
// StaticCall executes the contract associated with the addr with the given input
|
||||
@@ -339,32 +353,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
var (
|
||||
to = AccountRef(addr)
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
)
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, to, new(big.Int), gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
// We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
|
||||
// However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
|
||||
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
||||
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
||||
// We could change this, but for now it's left for legacy reasons
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
// We do an AddBalance of zero here, just in order to trigger a touch.
|
||||
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
||||
// but is the correct thing to do and matters on other networks, in tests, and potential
|
||||
// future scenarios
|
||||
evm.StateDB.AddBalance(addr, big.NewInt(0))
|
||||
evm.StateDB.AddBalance(addr, big0)
|
||||
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
// when we're in Homestead this also counts for code storage gas errors.
|
||||
ret, err = run(evm, contract, input, true)
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
// At this point, we use a copy of address. If we don't, the go compiler will
|
||||
// leak the 'contract' to the outer scope, and make allocation for 'contract'
|
||||
// even if the actual execution ends on RunPrecompiled above.
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
// when we're in Homestead this also counts for code storage gas errors.
|
||||
ret, err = run(evm, contract, input, true)
|
||||
gas = contract.Gas
|
||||
}
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
type codeAndHash struct {
|
||||
@@ -466,9 +491,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
//
|
||||
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
codeAndHash := &codeAndHash{code: code}
|
||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes())
|
||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes())
|
||||
return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user