Merge pull request #16 from expanse-project/rebase-1.4.16

Rebase 1.4.16
This commit is contained in:
Christopher Franko 2016-10-08 14:54:35 -04:00 committed by GitHub
commit 8ee40d3770
865 changed files with 202040 additions and 68245 deletions

5
.gitignore vendored
View file

@ -23,13 +23,10 @@ Godeps/_workspace/bin
.project .project
.settings .settings
deploy/osx/Mist.app
deploy/osx/Mist\ Installer.dmg
cmd/mist/assets/ext/expanse.js/
# used by the Makefile # used by the Makefile
/build/_workspace/ /build/_workspace/
/build/bin/ /build/bin/
/geth*.zip
# travis # travis
profile.tmp profile.tmp

6
Godeps/Godeps.json generated
View file

@ -20,8 +20,8 @@
}, },
{ {
"ImportPath": "github.com/expanse-org/ethash", "ImportPath": "github.com/expanse-org/ethash",
"Comment": "v23.1-245-g25b32de", "Comment": "v23.1-247-g2e80de5",
"Rev": "25b32de0c0271065c28c3719c2bfe86959d72f0c" "Rev": "2e80de5022370cfe632195b1720db52d07ff8a77"
}, },
{ {
"ImportPath": "github.com/fatih/color", "ImportPath": "github.com/fatih/color",
@ -117,7 +117,7 @@
}, },
{ {
"ImportPath": "github.com/rjeczalik/notify", "ImportPath": "github.com/rjeczalik/notify",
"Rev": "5dd6205716539662f8f14ab513552b41eab69d5d" "Rev": "f627deca7a510d96f0ef9388f2d0e8b16d21f87f"
}, },
{ {
"ImportPath": "github.com/robertkrimen/otto", "ImportPath": "github.com/robertkrimen/otto",

0
Godeps/_workspace/src/github.com/expanse-project/ethash/setup.py generated vendored Normal file → Executable file
View file

View file

@ -59,21 +59,20 @@
#define fix_endian32(dst_, src_) dst_ = ethash_swap_u32(src_) #define fix_endian32(dst_, src_) dst_ = ethash_swap_u32(src_)
#define fix_endian32_same(val_) val_ = ethash_swap_u32(val_) #define fix_endian32_same(val_) val_ = ethash_swap_u32(val_)
#define fix_endian64(dst_, src_) dst_ = ethash_swap_u64(src_ #define fix_endian64(dst_, src_) dst_ = ethash_swap_u64(src_)
#define fix_endian64_same(val_) val_ = ethash_swap_u64(val_) #define fix_endian64_same(val_) val_ = ethash_swap_u64(val_)
#define fix_endian_arr32(arr_, size_) \ #define fix_endian_arr32(arr_, size_) \
do { \ do { \
for (unsigned i_ = 0; i_ < (size_), ++i_) { \ for (unsigned i_ = 0; i_ < (size_); ++i_) { \
arr_[i_] = ethash_swap_u32(arr_[i_]); \ arr_[i_] = ethash_swap_u32(arr_[i_]); \
} \ } \
while (0) } while (0)
#define fix_endian_arr64(arr_, size_) \ #define fix_endian_arr64(arr_, size_) \
do { \ do { \
for (unsigned i_ = 0; i_ < (size_), ++i_) { \ for (unsigned i_ = 0; i_ < (size_); ++i_) { \
arr_[i_] = ethash_swap_u64(arr_[i_]); \ arr_[i_] = ethash_swap_u64(arr_[i_]); \
} \ } \
while (0) \ } while (0)
#else #else
# error "endian not supported" # error "endian not supported"
#endif // BYTE_ORDER #endif // BYTE_ORDER

View file

@ -257,7 +257,7 @@ static bool ethash_hash(
void ethash_quick_hash( void ethash_quick_hash(
ethash_h256_t* return_hash, ethash_h256_t* return_hash,
ethash_h256_t const* header_hash, ethash_h256_t const* header_hash,
uint64_t const nonce, uint64_t nonce,
ethash_h256_t const* mix_hash ethash_h256_t const* mix_hash
) )
{ {

View file

@ -21,10 +21,9 @@ env:
- PATH=$HOME/bin:$PATH - PATH=$HOME/bin:$PATH
install: install:
- go get golang.org/x/tools/cmd/vet
- go get -t -v ./... - go get -t -v ./...
script: script:
- go tool vet -all . - "(go version | grep -q 1.4) || go tool vet -all ."
- go install $GOFLAGS ./... - go install $GOFLAGS ./...
- go test -v -race $GOFLAGS ./... - go test -v -race $GOFLAGS ./...

View file

@ -11,7 +11,6 @@ environment:
install: install:
- go version - go version
- go get golang.org/x/tools/cmd/vet
- go get -v -t ./... - go get -v -t ./...
build_script: build_script:

View file

@ -133,7 +133,7 @@ func (w *watch) Dispatch(ev []FSEvent) {
ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev)) ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
if ev[i].Flags&failure != 0 { if ev[i].Flags&failure != 0 {
// TODO(rjeczalik): missing error handling // TODO(rjeczalik): missing error handling
panic("unhandled error: " + Event(ev[i].Flags).String()) continue
} }
if !strings.HasPrefix(ev[i].Path, w.path) { if !strings.HasPrefix(ev[i].Path, w.path) {
continue continue

105
Makefile
View file

@ -2,8 +2,8 @@
# with Go source code. If you know what GOPATH is then you probably # with Go source code. If you know what GOPATH is then you probably
# don't need to bother with make. # don't need to bother with make.
.PHONY: gexp gexp-cross evm all test travis-test-with-coverage xgo clean .PHONY: gexp gexp-cross evm all test clean
.PHONY: gexp-linux gexp-linux-386 gexp-linux-amd64 .PHONY: gexp-linux gexp-linux-386 gexp-linux-amd64 gexp-linux-mips64 gexp-linux-mips64le
.PHONY: gexp-linux-arm gexp-linux-arm-5 gexp-linux-arm-6 gexp-linux-arm-7 gexp-linux-arm64 .PHONY: gexp-linux-arm gexp-linux-arm-5 gexp-linux-arm-6 gexp-linux-arm-7 gexp-linux-arm64
.PHONY: gexp-darwin gexp-darwin-386 gexp-darwin-amd64 .PHONY: gexp-darwin gexp-darwin-386 gexp-darwin-amd64
.PHONY: gexp-windows gexp-windows-386 gexp-windows-amd64 .PHONY: gexp-windows gexp-windows-386 gexp-windows-amd64
@ -13,25 +13,41 @@ GOBIN = build/bin
GO ?= latest GO ?= latest
gexp: gexp:
build/env.sh go build -i -v $(shell build/flags.sh) -o $(GOBIN)/gexp ./cmd/gexp build/env.sh go run build/ci.go install ./cmd/gexp
@echo "Done building." @echo "Done building."
@echo "Run \"$(GOBIN)/gexp\" to launch gexp." @echo "Run \"$(GOBIN)/gexp\" to launch gexp."
evm:
build/env.sh go run build/ci.go install ./cmd/evm
@echo "Done building."
@echo "Run \"$(GOBIN)/evm to start the evm."
all:
build/env.sh go run build/ci.go install
test: all
build/env.sh go run build/ci.go test
clean:
rm -fr build/_workspace/pkg/ Godeps/_workspace/pkg $(GOBIN)/*
# Cross Compilation Targets (xgo)
gexp-cross: gexp-linux gexp-darwin gexp-windows gexp-android gexp-ios gexp-cross: gexp-linux gexp-darwin gexp-windows gexp-android gexp-ios
@echo "Full cross compilation done:" @echo "Full cross compilation done:"
@ls -ld $(GOBIN)/gexp-* @ls -ld $(GOBIN)/gexp-*
gexp-linux: gexp-linux-386 gexp-linux-amd64 gexp-linux-arm gexp-linux: gexp-linux-386 gexp-linux-amd64 gexp-linux-arm gexp-linux-mips64 gexp-linux-mips64le
@echo "Linux cross compilation done:" @echo "Linux cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* @ls -ld $(GOBIN)/gexp-linux-*
gexp-linux-386: xgo gexp-linux-386:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v ./cmd/gexp
@echo "Linux 386 cross compilation done:" @echo "Linux 386 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep 386 @ls -ld $(GOBIN)/gexp-linux-* | grep 386
gexp-linux-amd64: xgo gexp-linux-amd64:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v ./cmd/gexp
@echo "Linux amd64 cross compilation done:" @echo "Linux amd64 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep amd64 @ls -ld $(GOBIN)/gexp-linux-* | grep amd64
@ -39,37 +55,47 @@ gexp-linux-arm: gexp-linux-arm-5 gexp-linux-arm-6 gexp-linux-arm-7 gexp-linux-ar
@echo "Linux ARM cross compilation done:" @echo "Linux ARM cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep arm @ls -ld $(GOBIN)/gexp-linux-* | grep arm
gexp-linux-arm-5: xgo gexp-linux-arm-5:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v ./cmd/gexp
@echo "Linux ARMv5 cross compilation done:" @echo "Linux ARMv5 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep arm-5 @ls -ld $(GOBIN)/gexp-linux-* | grep arm-5
gexp-linux-arm-6: xgo gexp-linux-arm-6:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v ./cmd/gexp
@echo "Linux ARMv6 cross compilation done:" @echo "Linux ARMv6 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep arm-6 @ls -ld $(GOBIN)/gexp-linux-* | grep arm-6
gexp-linux-arm-7: xgo gexp-linux-arm-7:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v ./cmd/gexp
@echo "Linux ARMv7 cross compilation done:" @echo "Linux ARMv7 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep arm-7 @ls -ld $(GOBIN)/gexp-linux-* | grep arm-7
gexp-linux-arm64: xgo gexp-linux-arm64:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v ./cmd/gexp
@echo "Linux ARM64 cross compilation done:" @echo "Linux ARM64 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep arm64 @ls -ld $(GOBIN)/gexp-linux-* | grep arm64
gexp-linux-mips64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/mips64 -v ./cmd/gexp
@echo "Linux MIPS64 cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep mips64
gexp-linux-mips64le:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/mips64le -v ./cmd/gexp
@echo "Linux MIPS64le cross compilation done:"
@ls -ld $(GOBIN)/gexp-linux-* | grep mips64le
gexp-darwin: gexp-darwin-386 gexp-darwin-amd64 gexp-darwin: gexp-darwin-386 gexp-darwin-amd64
@echo "Darwin cross compilation done:" @echo "Darwin cross compilation done:"
@ls -ld $(GOBIN)/gexp-darwin-* @ls -ld $(GOBIN)/gexp-darwin-*
gexp-darwin-386: xgo gexp-darwin-386:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v ./cmd/gexp
@echo "Darwin 386 cross compilation done:" @echo "Darwin 386 cross compilation done:"
@ls -ld $(GOBIN)/gexp-darwin-* | grep 386 @ls -ld $(GOBIN)/gexp-darwin-* | grep 386
gexp-darwin-amd64: xgo gexp-darwin-amd64:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v ./cmd/gexp
@echo "Darwin amd64 cross compilation done:" @echo "Darwin amd64 cross compilation done:"
@ls -ld $(GOBIN)/gexp-darwin-* | grep amd64 @ls -ld $(GOBIN)/gexp-darwin-* | grep amd64
@ -77,45 +103,22 @@ gexp-windows: gexp-windows-386 gexp-windows-amd64
@echo "Windows cross compilation done:" @echo "Windows cross compilation done:"
@ls -ld $(GOBIN)/gexp-windows-* @ls -ld $(GOBIN)/gexp-windows-*
gexp-windows-386: xgo gexp-windows-386:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v ./cmd/gexp
@echo "Windows 386 cross compilation done:" @echo "Windows 386 cross compilation done:"
@ls -ld $(GOBIN)/gexp-windows-* | grep 386 @ls -ld $(GOBIN)/gexp-windows-* | grep 386
gexp-windows-amd64: xgo gexp-windows-amd64:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v ./cmd/gexp
@echo "Windows amd64 cross compilation done:" @echo "Windows amd64 cross compilation done:"
@ls -ld $(GOBIN)/gexp-windows-* | grep amd64 @ls -ld $(GOBIN)/gexp-windows-* | grep amd64
gexp-android: xgo gexp-android:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v ./cmd/gexp
@echo "Android cross compilation done:" @echo "Android cross compilation done:"
@ls -ld $(GOBIN)/gexp-android-* @ls -ld $(GOBIN)/gexp-android-*
gexp-ios: xgo gexp-ios:
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v $(shell build/flags.sh) ./cmd/gexp build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v ./cmd/gexp
@echo "iOS framework cross compilation done:" @echo "iOS framework cross compilation done:"
@ls -ld $(GOBIN)/gexp-ios-* @ls -ld $(GOBIN)/gexp-ios-*
evm:
build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm
@echo "Done building."
@echo "Run \"$(GOBIN)/evm to start the evm."
all:
for cmd in `ls ./cmd/`; do \
build/env.sh go build -i -v $(shell build/flags.sh) -o $(GOBIN)/$$cmd ./cmd/$$cmd; \
done
test: all
build/env.sh go test ./...
travis-test-with-coverage: all
build/env.sh go vet ./...
build/env.sh build/test-global-coverage.sh
xgo:
build/env.sh go get github.com/karalabe/xgo
clean:
rm -fr build/_workspace/pkg/ Godeps/_workspace/pkg $(GOBIN)/*

193
README.md
View file

@ -53,13 +53,204 @@ Go Expanse comes with several wrappers/executables found in
* `disasm` disassembles EVM code: `echo "6001" | disasm` * `disasm` disassembles EVM code: `echo "6001" | disasm`
* `rlpdump` prints RLP structures * `rlpdump` prints RLP structures
## Running geth
Going through all the possible command line flags is out of scope here (please consult our
[CLI Wiki page](https://github.com/expanse-project/go-expanse/wiki/Command-Line-Options)), but we've
enumerated a few common parameter combos to get you up to speed quickly on how you can run your
own instance.
### Full node on the main Ethereum network
By far the most common scenario is people wanting to simply interact with the Ethereum network:
create accounts; transfer funds; deploy and interact with contracts. For this particular use-case
the user doesn't care about years-old historical data, so we can fast-sync quickly to the current
state of the network. To do so:
```
$ geth --fast --cache=512 console
```
This command will:
* Start geth in fast sync mode (`--fast`), causing it to download more data in exchange for avoiding
processing the entire history of the Ethereum network, which is very CPU intensive.
* Bump the memory allowance of the database to 512MB (`--cache=512`), which can help significantly in
sync times especially for HDD users. This flag is optional and you can set it as high or as low as
you'd like, though we'd recommend the 512MB - 2GB range.
* Start up 's built-in interactive [JavaScript console](https://github.com/expanse-project/go-expanse/wiki/JavaScript-Console),
(via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API)
as well as 's own [management APIs](https://github.com/expanse-project/go-expanse/wiki/Management-APIs).
This too is optional and if you leave it out you can always attach to an already running instance
with `geth --attach`.
### Full node on the Ethereum test network
Transitioning towards developers, if you'd like to play around with creating Ethereum contracts, you
almost certainly would like to do that without any real money involved until you get the hang of the
entire system. In other words, instead of attaching to the main network, you want to join the **test**
network with your node, which is fully equivalent to the main network, but with play-Ether only.
```
$ geth --testnet --fast --cache=512 console
```
The `--fast`, `--cache` flags and `console` subcommand have the exact same meaning as above and they
are equially useful on the testnet too. Please see above for their explanations if you've skipped to
here.
Specifying the `--testnet` flag however will reconfigure your instance a bit:
* Instead of using the default data directory (`~/.ethereum` on Linux for example), will nest
itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on Linux).
* Instead of connecting the main Ethereum network, the client will connect to the test network,
which uses different P2P bootnodes, different network IDs and genesis states.
*Note: Although there are some internal protective measures to prevent transactions from crossing
over between the main network and test network (different starting nonces), you should make sure to
always use separate accounts for play-money and real-money. Unless you manually move accounts,
will by default correctly separate the two networks and will not make any accounts available between
them.*
### Programatically interfacing nodes
As a developer, sooner rather than later you'll want to start interacting with and the Ethereum
network via your own programs and not manually through the console. To aid this, has built in
support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) and
[ specific APIs](https://github.com/expanse-project/go-expanse/wiki/Management-APIs)). These can be
exposed via HTTP, WebSockets and IPC (unix sockets on unix based platroms, and named pipes on Windows).
The IPC interface is enabled by default and exposes all the APIs supported by , whereas the HTTP
and WS interfaces need to manually be enabled and only expose a subset of APIs due to security reasons.
These can be turned on/off and configured as you'd expect.
HTTP based JSON-RPC API options:
* `--rpc` Enable the HTTP-RPC server
* `--rpcaddr` HTTP-RPC server listening interface (default: "localhost")
* `--rpcport` HTTP-RPC server listening port (default: 8545)
* `--rpcapi` API's offered over the HTTP-RPC interface (default: "eth,net,web3")
* `--rpccorsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced)
* `--ws` Enable the WS-RPC server
* `--wsaddr` WS-RPC server listening interface (default: "localhost")
* `--wsport` WS-RPC server listening port (default: 8546)
* `--wsapi` API's offered over the WS-RPC interface (default: "eth,net,web3")
* `--wsorigins` Origins from which to accept websockets requests
* `--ipcdisable` Disable the IPC-RPC server
* `--ipcapi` API's offered over the IPC-RPC interface (default: "admin,debug,eth,miner,net,personal,shh,txpool,web3")
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
You'll need to use your own programming environments' capabilities (libraries, tools, etc) to connect
via HTTP, WS or IPC to a node configured with the above flags and you'll need to speak [JSON-RPC](http://www.jsonrpc.org/specification)
on all transports. You can reuse the same connection for multiple requests!
**Note: Please understand the security implications of opening up an HTTP/WS based transport before
doing so! Hackers on the internet are actively trying to subvert Ethereum nodes with exposed APIs!
Further, all browser tabs can access locally running webservers, so malicious webpages could try to
subvert locally available APIs!**
### Operating a private network
Maintaining your own private network is more involved as a lot of configurations taken for granted in
the official networks need to be manually set up.
#### Defining the private genesis state
First, you'll need to create the genesis state of your networks, which all nodes need to be aware of
and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
```json
{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}
```
The above fields should be fine for most purposes, although we'd recommend changing the `nonce` to
some random value so you prevent unknown remote nodes from being able to connect to you. If you'd
like to pre-fund some accounts for easier testing, you can populate the `alloc` field with account
configs:
```json
"alloc": {
"0x0000000000000000000000000000000000000001": {"balance": "111111111"},
"0x0000000000000000000000000000000000000002": {"balance": "222222222"}
}
```
With the genesis state defined in the above JSON file, you'll need to initialize **every** node
with it prior to starting it up to ensure all blockchain parameters are correctly set:
```
$ geth init path/to/genesis.json
```
#### Creating the rendezvous point
With all nodes that you want to run initialized to the desired genesis state, you'll need to start a
bootstrap node that others can use to find each other in your network and/or over the internet. The
clean way is to configure and run a dedicated bootnode:
```
$ bootnode --genkey=boot.key
$ bootnode --nodekey=boot.key
```
With the bootnode online, it will display an [`enode` URL](https://github.com/ethereum/wiki/wiki/enode-url-format)
that other nodes can use to connect to it and exchange peer information. Make sure to replace the
displayed IP address information (most probably `[::]`) with your externally accessible IP to get the
actual `enode` URL.
*Note: You could also use a full fledged node as a bootnode, but it's the less recommended way.*
#### Starting up your member nodes
With the bootnode operational and externally reachable (you can try `telnet <ip> <port>` to ensure
it's indeed reachable), start every subsequent node pointed to the bootnode for peer discovery
via the `--bootnodes` flag. It will probably also be desirable to keep the data directory of your
private network separated, so do also specify a custom `--datadir` flag.
```
$ geth --datadir=path/to/custom/data/folder --bootnodes=<bootnode-enode-url-from-above>
```
*Note: Since your network will be completely cut off from the main and test networks, you'll also
need to configure a miner to process transactions and create new blocks for you.*
#### Running a private miner
Mining on the public Ethereum network is a complex task as it's only feasible using GPUs, requiring
an OpenCL or CUDA enabled `ethminer` instance. For information on such a setup, please consult the
[EtherMining subreddit](https://www.reddit.com/r/EtherMining/) and the [Genoil miner](https://github.com/Genoil/cpp-ethereum)
repository.
In a private network setting however, a single CPU miner instance is more than enough for practical
purposes as it can produce a stable stream of blocks at the correct intervals without needing heavy
resources (consider running on a single thread, no need for multiple ones either). To start a
instance for mining, run it with all your usual flags, extended by:
```
$ geth <usual-flags> --mine --minerthreads=1 --etherbase=0x0000000000000000000000000000000000000000
```
Which will start mining bocks and transactions on a single CPU thread, crediting all proceedings to
the account specified by `--etherbase`. You can further tune the mining by changing the default gas
limit blocks converge to (`--targetgaslimit`) and the price transactions are accepted at (`--gasprice`).
## Contribution ## Contribution
`gexp` can be configured via command line options, environment variables and config files. `gexp` can be configured via command line options, environment variables and config files.
If you'd like to contribute to go-expanse, please fork, fix, commit and send a pull request If you'd like to contribute to go-expanse, please fork, fix, commit and send a pull request
for the maintainers to review and merge into the main code base. If you wish to submit more for the maintainers to review and merge into the main code base. If you wish to submit more
complex changes though, please check up with the core devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) complex changes though, please check up with the core devs first on [our gitter channel](https://gitter.im/expanse-project/go-expanse)
to ensure those changes are in line with the general philosophy of the project and/or get some to ensure those changes are in line with the general philosophy of the project and/or get some
early feedback which can make both your efforts much lighter as well as our review and merge early feedback which can make both your efforts much lighter as well as our review and merge
procedures quick and simple. procedures quick and simple.

View file

@ -1 +1 @@
1.4.7 1.4.16

View file

@ -72,7 +72,7 @@ func (b *SimulatedBackend) Commit() {
// Rollback aborts all pending transactions, reverting to the last committed state. // Rollback aborts all pending transactions, reverting to the last committed state.
func (b *SimulatedBackend) Rollback() { func (b *SimulatedBackend) Rollback() {
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {}) blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
b.pendingBlock = blocks[0] b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
@ -97,7 +97,8 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
statedb *state.StateDB statedb *state.StateDB
) )
if pending { if pending {
block, statedb = b.pendingBlock, b.pendingState.Copy() block, statedb = b.pendingBlock, b.pendingState
defer statedb.RevertToSnapshot(statedb.Snapshot())
} else { } else {
block = b.blockchain.CurrentBlock() block = b.blockchain.CurrentBlock()
statedb, _ = b.blockchain.State() statedb, _ = b.blockchain.State()
@ -119,6 +120,7 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
value: new(big.Int), value: new(big.Int),
data: data, data: data,
} }
// Execute the call and return // Execute the call and return
vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{})
gaspool := new(core.GasPool).AddGas(common.MaxBig) gaspool := new(core.GasPool).AddGas(common.MaxBig)
@ -146,8 +148,10 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
// Create a copy of the currently pending state db to screw around with // Create a copy of the currently pending state db to screw around with
var ( var (
block = b.pendingBlock block = b.pendingBlock
statedb = b.pendingState.Copy() statedb = b.pendingState
) )
defer statedb.RevertToSnapshot(statedb.Snapshot())
// If there's no code to interact with, respond with an appropriate error // If there's no code to interact with, respond with an appropriate error
if contract != nil { if contract != nil {
if code := statedb.GetCode(*contract); len(code) == 0 { if code := statedb.GetCode(*contract); len(code) == 0 {
@ -178,7 +182,7 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw // SendTransaction implements ContractTransactor.SendTransaction, delegating the raw
// transaction injection to the remote node. // transaction injection to the remote node.
func (b *SimulatedBackend) SendTransaction(tx *types.Transaction) error { func (b *SimulatedBackend) SendTransaction(tx *types.Transaction) error {
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() { for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx) block.AddTx(tx)
} }

33
appveyor.yml Normal file
View file

@ -0,0 +1,33 @@
os: Visual Studio 2015
# Clone directly into GOPATH.
clone_folder: c:\gopath\src\github.com\ethereum\go-ethereum
clone_depth: 5
version: "{branch}.{build}"
environment:
global:
# Go stuff
GOPATH: c:\gopath
GO: c:\go\bin\go
GOROOT: c:\go
CC: C:\msys64\mingw64\bin\gcc.exe
# MSYS2 stuff
MSYS2_ARCH: x86_64
MSYSTEM: MINGW64
PATH: C:\msys64\mingw64\bin\;%PATH%
install:
- "%GO% version"
- "%CC% --version"
build_script:
- "%GO% run build\\ci.go install"
test_script:
- "%GO% run build\\ci.go test -vet -coverage"
after_build:
- "%GO% run build\\ci.go archive -type zip"
artifacts:
- path: geth-*.zip

49
build/ci-notes.md Normal file
View file

@ -0,0 +1,49 @@
# Debian Packaging
Tagged releases and develop branch commits are available as installable Debian packages
for Ubuntu. Packages are built for the all Ubuntu versions which are supported by
Canonical:
- Trusty Tahr (14.04 LTS)
- Wily Werewolf (15.10)
- Xenial Xerus (16.04 LTS)
- Yakkety Yak (16.10)
Packages of develop branch commits have suffix -unstable and cannot be installed alongside
the stable version. Switching between release streams requires user intervention.
The packages are built and served by launchpad.net. We generate a Debian source package
for each distribution and upload it. Their builder picks up the source package, builds it
and installs the new version into the PPA repository. Launchpad requires a valid signature
by a team member for source package uploads. The signing key is stored in an environment
variable which Travis CI makes available to certain builds.
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
version that is available in the main Ubuntu repository. In order to make this possible,
our PPA depends on the ~gophers/ubuntu/archive PPA. Our source package build-depends on
golang-1.7, which is co-installable alongside the regular golang package. PPA dependencies
can be edited at https://launchpad.net/%7Elp-fjl/+archive/ubuntu/geth-ci-testing/+edit-dependencies
## Building Packages Locally (for testing)
You need to run Ubuntu to do test packaging.
Add the gophers PPA and install Go 1.7 and Debian packaging tools:
$ sudo apt-add-repository ppa:gophers/ubuntu/archive
$ sudo apt-get update
$ sudo apt-get install build-essential golang-1.7 devscripts debhelper
Create the source packages:
$ go run build/ci.go debsrc -workdir dist
Then go into the source package directory for your running distribution and build the package:
$ cd dist/ethereum-unstable-1.5.0+xenial
$ dpkg-buildpackage
Built packages are placed in the dist/ directory.
$ cd ..
$ dpkg-deb -c geth-unstable_1.5.0+xenial_amd64.deb

483
build/ci.go Normal file
View file

@ -0,0 +1,483 @@
// Copyright 2016 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/>.
// +build none
/*
The ci command is called from Continuous Integration scripts.
Usage: go run ci.go <command> <command flags/arguments>
Available commands are:
install [ packages... ] -- builds packages and executables
test [ -coverage ] [ -vet ] [ packages... ] -- runs the tests
archive [ -type zip|tar ] -- archives build artefacts
importkeys -- imports signing keys from env
debsrc [ -sign key-id ] [ -upload dest ] -- creates a debian source package
xgo [ options ] -- cross builds according to options
For all commands, -n prevents execution of external programs (dry run mode).
*/
package main
import (
"bytes"
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"../internal/build"
)
var (
// Files that end up in the geth*.zip archive.
gethArchiveFiles = []string{
"COPYING",
executablePath("geth"),
}
// Files that end up in the geth-alltools*.zip archive.
allToolsArchiveFiles = []string{
"COPYING",
executablePath("abigen"),
executablePath("evm"),
executablePath("geth"),
executablePath("rlpdump"),
}
// A debian package is created for all executables listed here.
debExecutables = []debExecutable{
{
Name: "geth",
Description: "Ethereum CLI client.",
},
{
Name: "rlpdump",
Description: "Developer utility tool that prints RLP structures.",
},
{
Name: "evm",
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
},
{
Name: "abigen",
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
},
}
// Distros for which packages are created.
// Note: vivid is unsupported because there is no golang-1.6 package for it.
debDistros = []string{"trusty", "wily", "xenial", "yakkety"}
)
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
func executablePath(name string) string {
if runtime.GOOS == "windows" {
name += ".exe"
}
return filepath.Join(GOBIN, name)
}
func main() {
log.SetFlags(log.Lshortfile)
if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) {
log.Fatal("this script must be run from the root of the repository")
}
if len(os.Args) < 2 {
log.Fatal("need subcommand as first argument")
}
switch os.Args[1] {
case "install":
doInstall(os.Args[2:])
case "test":
doTest(os.Args[2:])
case "archive":
doArchive(os.Args[2:])
case "debsrc":
doDebianSource(os.Args[2:])
case "xgo":
doXgo(os.Args[2:])
default:
log.Fatal("unknown command ", os.Args[1])
}
}
// Compiling
func doInstall(cmdline []string) {
flag.CommandLine.Parse(cmdline)
env := build.Env()
// Check Go version. People regularly open issues about compilation
// failure with outdated Go. This should save them the trouble.
if runtime.Version() < "go1.4" && !strings.HasPrefix(runtime.Version(), "devel") {
log.Println("You have Go version", runtime.Version())
log.Println("go-ethereum requires at least Go version 1.4 and cannot")
log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
os.Exit(1)
}
// Compile packages given as arguments, or everything if there are no arguments.
packages := []string{"./..."}
if flag.NArg() > 0 {
packages = flag.Args()
}
goinstall := goTool("install", buildFlags(env)...)
goinstall.Args = append(goinstall.Args, "-v")
goinstall.Args = append(goinstall.Args, packages...)
build.MustRun(goinstall)
}
func buildFlags(env build.Environment) (flags []string) {
if os.Getenv("GO_OPENCL") != "" {
flags = append(flags, "-tags", "opencl")
}
// Since Go 1.5, the separator char for link time assignments
// is '=' and using ' ' prints a warning. However, Go < 1.5 does
// not support using '='.
sep := " "
if runtime.Version() > "go1.5" || strings.Contains(runtime.Version(), "devel") {
sep = "="
}
// Set gitCommit constant via link-time assignment.
if env.Commit != "" {
flags = append(flags, "-ldflags", "-X main.gitCommit"+sep+env.Commit)
}
return flags
}
func goTool(subcmd string, args ...string) *exec.Cmd {
gocmd := filepath.Join(runtime.GOROOT(), "bin", "go")
cmd := exec.Command(gocmd, subcmd)
cmd.Args = append(cmd.Args, args...)
cmd.Env = []string{
"GOPATH=" + build.GOPATH(),
"GOBIN=" + GOBIN,
}
for _, e := range os.Environ() {
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
continue
}
cmd.Env = append(cmd.Env, e)
}
return cmd
}
// Running The Tests
//
// "tests" also includes static analysis tools such as vet.
func doTest(cmdline []string) {
var (
vet = flag.Bool("vet", false, "Whether to run go vet")
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
)
flag.CommandLine.Parse(cmdline)
packages := []string{"./..."}
if len(flag.CommandLine.Args()) > 0 {
packages = flag.CommandLine.Args()
}
// Run analysis tools before the tests.
if *vet {
build.MustRun(goTool("vet", packages...))
}
// Run the actual tests.
gotest := goTool("test")
// Test a single package at a time. CI builders are slow
// and some tests run into timeouts under load.
gotest.Args = append(gotest.Args, "-p", "1")
if *coverage {
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
}
gotest.Args = append(gotest.Args, packages...)
build.MustRun(gotest)
}
// Release Packaging
func doArchive(cmdline []string) {
var (
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
ext string
)
flag.CommandLine.Parse(cmdline)
switch *atype {
case "zip":
ext = ".zip"
case "tar":
ext = ".tar.gz"
default:
log.Fatal("unknown archive type: ", atype)
}
env := build.Env()
maybeSkipArchive(env)
base := archiveBasename(env)
if err := build.WriteArchive("geth-"+base, ext, gethArchiveFiles); err != nil {
log.Fatal(err)
}
if err := build.WriteArchive("geth-alltools-"+base, ext, allToolsArchiveFiles); err != nil {
log.Fatal(err)
}
}
func archiveBasename(env build.Environment) string {
// date := time.Now().UTC().Format("200601021504")
platform := runtime.GOOS + "-" + runtime.GOARCH
archive := platform + "-" + build.VERSION()
if env.Commit != "" {
archive += "-" + env.Commit[:8]
}
return archive
}
// skips archiving for some build configurations.
func maybeSkipArchive(env build.Environment) {
if env.IsPullRequest {
log.Printf("skipping because this is a PR build")
os.Exit(0)
}
if env.Branch != "develop" && !strings.HasPrefix(env.Tag, "v1.") {
log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag)
os.Exit(0)
}
}
// Debian Packaging
func doDebianSource(cmdline []string) {
var (
signer = flag.String("signer", "", `Signing key name, also used as package author`)
upload = flag.String("upload", "", `Where to upload the source package (usually "ppa:ethereum/ethereum")`)
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
now = time.Now()
)
flag.CommandLine.Parse(cmdline)
*workdir = makeWorkdir(*workdir)
env := build.Env()
maybeSkipArchive(env)
// Import the signing key.
if b64key := os.Getenv("PPA_SIGNING_KEY"); b64key != "" {
key, err := base64.StdEncoding.DecodeString(b64key)
if err != nil {
log.Fatal("invalid base64 PPA_SIGNING_KEY")
}
gpg := exec.Command("gpg", "--import")
gpg.Stdin = bytes.NewReader(key)
build.MustRun(gpg)
}
// Create the packages.
for _, distro := range debDistros {
meta := newDebMetadata(distro, *signer, env, now)
pkgdir := stageDebianSource(*workdir, meta)
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
debuild.Dir = pkgdir
build.MustRun(debuild)
changes := fmt.Sprintf("%s_%s_source.changes", meta.Name(), meta.VersionString())
changes = filepath.Join(*workdir, changes)
if *signer != "" {
build.MustRunCommand("debsign", changes)
}
if *upload != "" {
build.MustRunCommand("dput", *upload, changes)
}
}
}
func makeWorkdir(wdflag string) string {
var err error
if wdflag != "" {
err = os.MkdirAll(wdflag, 0744)
} else {
wdflag, err = ioutil.TempDir("", "eth-deb-build-")
}
if err != nil {
log.Fatal(err)
}
return wdflag
}
func isUnstableBuild(env build.Environment) bool {
if env.Branch != "develop" && env.Tag != "" {
return false
}
return true
}
type debMetadata struct {
Env build.Environment
// go-ethereum version being built. Note that this
// is not the debian package version. The package version
// is constructed by VersionString.
Version string
Author string // "name <email>", also selects signing key
Distro, Time string
Executables []debExecutable
}
type debExecutable struct {
Name, Description string
}
func newDebMetadata(distro, author string, env build.Environment, t time.Time) debMetadata {
if author == "" {
// No signing key, use default author.
author = "Ethereum Builds <fjl@ethereum.org>"
}
return debMetadata{
Env: env,
Author: author,
Distro: distro,
Version: build.VERSION(),
Time: t.Format(time.RFC1123Z),
Executables: debExecutables,
}
}
// Name returns the name of the metapackage that depends
// on all executable packages.
func (meta debMetadata) Name() string {
if isUnstableBuild(meta.Env) {
return "ethereum-unstable"
}
return "ethereum"
}
// VersionString returns the debian version of the packages.
func (meta debMetadata) VersionString() string {
vsn := meta.Version
if meta.Env.Buildnum != "" {
vsn += "+build" + meta.Env.Buildnum
}
if meta.Distro != "" {
vsn += "+" + meta.Distro
}
return vsn
}
// ExeList returns the list of all executable packages.
func (meta debMetadata) ExeList() string {
names := make([]string, len(meta.Executables))
for i, e := range meta.Executables {
names[i] = meta.ExeName(e)
}
return strings.Join(names, ", ")
}
// ExeName returns the package name of an executable package.
func (meta debMetadata) ExeName(exe debExecutable) string {
if isUnstableBuild(meta.Env) {
return exe.Name + "-unstable"
}
return exe.Name
}
// ExeConflicts returns the content of the Conflicts field
// for executable packages.
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
if isUnstableBuild(meta.Env) {
// Set up the conflicts list so that the *-unstable packages
// cannot be installed alongside the regular version.
//
// https://www.debian.org/doc/debian-policy/ch-relationships.html
// is very explicit about Conflicts: and says that Breaks: should
// be preferred and the conflicting files should be handled via
// alternates. We might do this eventually but using a conflict is
// easier now.
return "ethereum, " + exe.Name
}
return ""
}
func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
pkg := meta.Name() + "-" + meta.VersionString()
pkgdir = filepath.Join(tmpdir, pkg)
if err := os.Mkdir(pkgdir, 0755); err != nil {
log.Fatal(err)
}
// Copy the source code.
build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator))
// Put the debian build files in place.
debian := filepath.Join(pkgdir, "debian")
build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
build.Render("build/deb.control", filepath.Join(debian, "control"), 0644, meta)
build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
for _, exe := range meta.Executables {
install := filepath.Join(debian, meta.ExeName(exe)+".install")
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
build.Render("build/deb.install", install, 0644, exe)
build.Render("build/deb.docs", docs, 0644, exe)
}
return pkgdir
}
// Cross compilation
func doXgo(cmdline []string) {
flag.CommandLine.Parse(cmdline)
env := build.Env()
// Make sure xgo is available for cross compilation
gogetxgo := goTool("get", "github.com/karalabe/xgo")
build.MustRun(gogetxgo)
// Execute the actual cross compilation
xgo := xgoTool(append(buildFlags(env), flag.Args()...))
build.MustRun(xgo)
}
func xgoTool(args []string) *exec.Cmd {
cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...)
cmd.Env = []string{
"GOPATH=" + build.GOPATH(),
"GOBIN=" + GOBIN,
}
for _, e := range os.Environ() {
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
continue
}
cmd.Env = append(cmd.Env, e)
}
return cmd
}

5
build/deb.changelog Normal file
View file

@ -0,0 +1,5 @@
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
* git build of {{.Env.Commit}}
-- {{.Author}} {{.Time}}

25
build/deb.control Normal file
View file

@ -0,0 +1,25 @@
Source: {{.Name}}
Section: science
Priority: extra
Maintainer: {{.Author}}
Build-Depends: debhelper (>= 8.0.0), golang-1.7
Standards-Version: 3.9.5
Homepage: https://ethereum.org
Vcs-Git: git://github.com/expanse-project/go-expanse.git
Vcs-Browser: https://github.com/expanse-project/go-expanse
Package: {{.Name}}
Architecture: any
Depends: ${misc:Depends}, {{.ExeList}}
Description: Meta-package to install geth and other tools
Meta-package to install geth and other tools
{{range .Executables}}
Package: {{$.ExeName .}}
Conflicts: {{$.ExeConflicts .}}
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Built-Using: ${misc:Built-Using}
Description: {{.Description}}
{{.Description}}
{{end}}

14
build/deb.copyright Normal file
View file

@ -0,0 +1,14 @@
Copyright 2016 The go-ethereum Authors
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/>.

1
build/deb.docs Normal file
View file

@ -0,0 +1 @@
AUTHORS

1
build/deb.install Normal file
View file

@ -0,0 +1 @@
build/bin/{{.Name}} usr/bin

13
build/deb.rules Normal file
View file

@ -0,0 +1,13 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
override_dh_auto_build:
build/env.sh /usr/lib/go-1.7/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
override_dh_auto_test:
%:
dh $@

View file

@ -20,9 +20,8 @@ fi
# Set up the environment to use the workspace. # Set up the environment to use the workspace.
# Also add Godeps workspace so we build using canned dependencies. # Also add Godeps workspace so we build using canned dependencies.
GOPATH="$ethdir/go-expanse/Godeps/_workspace:$workspace" GOPATH="$workspace"
GOBIN="$PWD/build/bin" export GOPATH
export GOPATH GOBIN
# Run the command inside the workspace. # Run the command inside the workspace.
cd "$ethdir/go-expanse" cd "$ethdir/go-expanse"

View file

@ -1,22 +0,0 @@
#!/bin/sh
set -e
if [ ! -f "build/env.sh" ]; then
echo "$0 must be run from the root of the repository."
exit 2
fi
# Since Go 1.5, the separator char for link time assignments
# is '=' and using ' ' prints a warning. However, Go < 1.5 does
# not support using '='.
sep=$(go version | awk '{ if ($3 >= "go1.5" || index($3, "devel")) print "="; else print " "; }' -)
# set gitCommit when running from a Git checkout.
if [ -f ".git/HEAD" ]; then
echo "-ldflags '-X main.gitCommit$sep$(git rev-parse HEAD)'"
fi
if [ ! -z "$GO_OPENCL" ]; then
echo "-tags opencl"
fi

View file

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(find ./* -maxdepth 10 -type d -not -path "./build" -not -path "./Godeps/*" ); do
if ls $d/*.go &> /dev/null; then
go test -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
echo '<<<<<< EOF' >> coverage.txt
rm profile.out
fi
fi
done

View file

@ -49,7 +49,6 @@ var (
// don't relicense vendored sources // don't relicense vendored sources
"crypto/sha3/", "crypto/ecies/", "logger/glog/", "crypto/sha3/", "crypto/ecies/", "logger/glog/",
"crypto/secp256k1/curve.go", "crypto/secp256k1/curve.go",
"trie/arc.go",
} }
// paths with this prefix are licensed as GPL. all other files are LGPL. // paths with this prefix are licensed as GPL. all other files are LGPL.

View file

@ -1,26 +0,0 @@
@echo off
if not exist .\build\win-ci-compile.bat (
echo This script must be run from the root of the repository.
exit /b
)
if not defined GOPATH (
echo GOPATH is not set.
exit /b
)
set GOPATH=%GOPATH%;%cd%\Godeps\_workspace
set GOBIN=%cd%\build\bin
rem set gitCommit when running from a Git checkout.
set goLinkFlags=""
if exist ".git\HEAD" (
where /q git
if not errorlevel 1 (
for /f %%h in ('git rev-parse HEAD') do (
set goLinkFlags="-X main.gitCommit=%%h"
)
)
)
@echo on
go install -v -ldflags %goLinkFlags% ./...

View file

@ -1,15 +0,0 @@
@echo off
if not exist .\build\win-ci-test.bat (
echo This script must be run from the root of the repository.
exit /b
)
if not defined GOPATH (
echo GOPATH is not set.
exit /b
)
set GOPATH=%GOPATH%;%cd%\Godeps\_workspace
set GOBIN=%cd%\build\bin
@echo on
go test ./...

32
circle.yml Normal file
View file

@ -0,0 +1,32 @@
machine:
services:
- docker
dependencies:
cache_directories:
- "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds
- "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds
override:
# Restore all previously cached docker images
- mkdir -p ~/.docker
- for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done
# Pull in and hive, restore cached ethash DAGs and do a dry run
- go get -u github.com/karalabe/hive
- (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash)
- (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/)
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6)
# Cache all the docker images and the ethash DAGs
- for img in `docker images | grep -v "^<none>" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash
test:
override:
# Build and move into a known folder
- make geth
- cp ./build/bin/geth $HOME/geth
# Run hive and move all generated logs into the public artifacts folder
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.)
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS

View file

@ -75,9 +75,9 @@ func runTestWithReader(test string, r io.Reader) error {
var err error var err error
switch strings.ToLower(test) { switch strings.ToLower(test) {
case "bk", "block", "blocktest", "blockchaintest", "blocktests", "blockchaintests": case "bk", "block", "blocktest", "blockchaintest", "blocktests", "blockchaintests":
err = tests.RunBlockTestWithReader(params.MainNetHomesteadBlock, r, skipTests) err = tests.RunBlockTestWithReader(params.MainNetHomesteadBlock, params.MainNetDAOForkBlock, r, skipTests)
case "st", "state", "statetest", "statetests": case "st", "state", "statetest", "statetests":
rs := tests.RuleSet{HomesteadBlock: params.MainNetHomesteadBlock} rs := tests.RuleSet{HomesteadBlock: params.MainNetHomesteadBlock, DAOForkBlock: params.MainNetDAOForkBlock, DAOForkSupport: true}
err = tests.RunStateTestWithReader(rs, r, skipTests) err = tests.RunStateTestWithReader(rs, r, skipTests)
case "tx", "transactiontest", "transactiontests": case "tx", "transactiontest", "transactiontests":
err = tests.RunTransactionTestsWithReader(r, skipTests) err = tests.RunTransactionTestsWithReader(r, skipTests)

View file

@ -30,6 +30,7 @@ import (
"github.com/expanse-project/go-expanse/core/state" "github.com/expanse-project/go-expanse/core/state"
"github.com/expanse-project/go-expanse/core/types" "github.com/expanse-project/go-expanse/core/types"
"github.com/expanse-project/go-expanse/core/vm" "github.com/expanse-project/go-expanse/core/vm"
"github.com/expanse-project/go-expanse/crypto"
"github.com/expanse-project/go-expanse/ethdb" "github.com/expanse-project/go-expanse/ethdb"
"github.com/expanse-project/go-expanse/logger/glog" "github.com/expanse-project/go-expanse/logger/glog"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
@ -141,7 +142,9 @@ func run(ctx *cli.Context) error {
) )
} else { } else {
receiver := statedb.CreateAccount(common.StringToAddress("receiver")) receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)))
code := common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name))
receiver.SetCode(crypto.Keccak256Hash(code), code)
ret, err = vmenv.Call( ret, err = vmenv.Call(
sender, sender,
receiver.Address(), receiver.Address(),
@ -223,8 +226,8 @@ func (ruleSet) IsHomestead(*big.Int) bool { return true }
func (self *VMEnv) RuleSet() vm.RuleSet { return ruleSet{} } func (self *VMEnv) RuleSet() vm.RuleSet { return ruleSet{} }
func (self *VMEnv) Vm() vm.Vm { return self.evm } func (self *VMEnv) Vm() vm.Vm { return self.evm }
func (self *VMEnv) Db() vm.Database { return self.state } func (self *VMEnv) Db() vm.Database { return self.state }
func (self *VMEnv) MakeSnapshot() vm.Database { return self.state.Copy() } func (self *VMEnv) SnapshotDatabase() int { return self.state.Snapshot() }
func (self *VMEnv) SetSnapshot(db vm.Database) { self.state.Set(db.(*state.StateDB)) } func (self *VMEnv) RevertToSnapshot(snap int) { self.state.RevertToSnapshot(snap) }
func (self *VMEnv) Origin() common.Address { return *self.transactor } func (self *VMEnv) Origin() common.Address { return *self.transactor }
func (self *VMEnv) BlockNumber() *big.Int { return common.Big0 } func (self *VMEnv) BlockNumber() *big.Int { return common.Big0 }
func (self *VMEnv) Coinbase() common.Address { return *self.transactor } func (self *VMEnv) Coinbase() common.Address { return *self.transactor }

232
cmd/geth/dao_test.go Normal file
View file

@ -0,0 +1,232 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"io/ioutil"
"math/big"
"os"
"path/filepath"
"testing"
"github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/core"
"github.com/expanse-project/go-expanse/ethdb"
"github.com/expanse-project/go-expanse/params"
)
// Genesis block for nodes which don't care about the DAO fork (i.e. not configured)
var daoOldGenesis = `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {}
}`
// Genesis block for nodes which actively oppose the DAO fork
var daoNoForkGenesis = `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"daoForkBlock" : 314,
"daoForkSupport" : false
}
}`
// Genesis block for nodes which actively support the DAO fork
var daoProForkGenesis = `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"daoForkBlock" : 314,
"daoForkSupport" : true
}
}`
var daoGenesisHash = common.HexToHash("5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0")
var daoGenesisForkBlock = big.NewInt(314)
// Tests that the DAO hard-fork number and the nodes support/opposition is correctly
// set in the database after various initialization procedures and invocations.
func TestDAODefaultMainnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, "", [][2]bool{{false, false}}, params.MainNetDAOForkBlock, true)
}
func TestDAOSupportMainnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, "", [][2]bool{{true, false}}, params.MainNetDAOForkBlock, true)
}
func TestDAOOpposeMainnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, "", [][2]bool{{false, true}}, params.MainNetDAOForkBlock, false)
}
func TestDAOSwitchToSupportMainnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, "", [][2]bool{{false, true}, {true, false}}, params.MainNetDAOForkBlock, true)
}
func TestDAOSwitchToOpposeMainnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, "", [][2]bool{{true, false}, {false, true}}, params.MainNetDAOForkBlock, false)
}
func TestDAODefaultTestnet(t *testing.T) {
testDAOForkBlockNewChain(t, true, "", [][2]bool{{false, false}}, params.TestNetDAOForkBlock, true)
}
func TestDAOSupportTestnet(t *testing.T) {
testDAOForkBlockNewChain(t, true, "", [][2]bool{{true, false}}, params.TestNetDAOForkBlock, true)
}
func TestDAOOpposeTestnet(t *testing.T) {
testDAOForkBlockNewChain(t, true, "", [][2]bool{{false, true}}, params.TestNetDAOForkBlock, false)
}
func TestDAOSwitchToSupportTestnet(t *testing.T) {
testDAOForkBlockNewChain(t, true, "", [][2]bool{{false, true}, {true, false}}, params.TestNetDAOForkBlock, true)
}
func TestDAOSwitchToOpposeTestnet(t *testing.T) {
testDAOForkBlockNewChain(t, true, "", [][2]bool{{true, false}, {false, true}}, params.TestNetDAOForkBlock, false)
}
func TestDAOInitOldPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{}, nil, false)
}
func TestDAODefaultOldPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{false, false}}, params.MainNetDAOForkBlock, true)
}
func TestDAOSupportOldPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{true, false}}, params.MainNetDAOForkBlock, true)
}
func TestDAOOpposeOldPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{false, true}}, params.MainNetDAOForkBlock, false)
}
func TestDAOSwitchToSupportOldPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{false, true}, {true, false}}, params.MainNetDAOForkBlock, true)
}
func TestDAOSwitchToOpposeOldPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{true, false}, {false, true}}, params.MainNetDAOForkBlock, false)
}
func TestDAOInitNoForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{}, daoGenesisForkBlock, false)
}
func TestDAODefaultNoForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{false, false}}, daoGenesisForkBlock, false)
}
func TestDAOSupportNoForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{true, false}}, daoGenesisForkBlock, true)
}
func TestDAOOpposeNoForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{false, true}}, daoGenesisForkBlock, false)
}
func TestDAOSwitchToSupportNoForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{false, true}, {true, false}}, daoGenesisForkBlock, true)
}
func TestDAOSwitchToOpposeNoForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{true, false}, {false, true}}, daoGenesisForkBlock, false)
}
func TestDAOInitProForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{}, daoGenesisForkBlock, true)
}
func TestDAODefaultProForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{false, false}}, daoGenesisForkBlock, true)
}
func TestDAOSupportProForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{true, false}}, daoGenesisForkBlock, true)
}
func TestDAOOpposeProForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{false, true}}, daoGenesisForkBlock, false)
}
func TestDAOSwitchToSupportProForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{false, true}, {true, false}}, daoGenesisForkBlock, true)
}
func TestDAOSwitchToOpposeProForkPrivnet(t *testing.T) {
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{true, false}, {false, true}}, daoGenesisForkBlock, false)
}
func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes [][2]bool, expectBlock *big.Int, expectVote bool) {
// Create a temporary data directory to use and inspect later
datadir := tmpdir(t)
defer os.RemoveAll(datadir)
// Start a instance with the requested flags set and immediately terminate
if genesis != "" {
json := filepath.Join(datadir, "genesis.json")
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
t.Fatalf("failed to write genesis file: %v", err)
}
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
}
for _, vote := range votes {
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
if testnet {
args = append(args, "--testnet")
}
if vote[0] {
args = append(args, "--support-dao-fork")
}
if vote[1] {
args = append(args, "--oppose-dao-fork")
}
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
geth.cmd.Wait()
}
// Retrieve the DAO config flag from the database
path := filepath.Join(datadir, "chaindata")
if testnet && genesis == "" {
path = filepath.Join(datadir, "testnet", "chaindata")
}
db, err := ethdb.NewLDBDatabase(path, 0, 0)
if err != nil {
t.Fatalf("failed to open test database: %v", err)
}
defer db.Close()
genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
if testnet {
genesisHash = common.HexToHash("0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303")
}
if genesis != "" {
genesisHash = daoGenesisHash
}
config, err := core.GetChainConfig(db, genesisHash)
if err != nil {
t.Fatalf("failed to retrieve chain config: %v", err)
}
// Validate the DAO hard-fork block number against the expected value
if config.DAOForkBlock == nil {
if expectBlock != nil {
t.Errorf("dao hard-fork block mismatch: have nil, want %v", expectBlock)
}
} else if expectBlock == nil {
t.Errorf("dao hard-fork block mismatch: have %v, want nil", config.DAOForkBlock)
} else if config.DAOForkBlock.Cmp(expectBlock) != 0 {
t.Errorf("dao hard-fork block mismatch: have %v, want %v", config.DAOForkBlock, expectBlock)
}
if config.DAOForkSupport != expectVote {
t.Errorf("dao hard-fork support mismatch: have %v, want %v", config.DAOForkSupport, expectVote)
}
}

107
cmd/geth/genesis_test.go Normal file
View file

@ -0,0 +1,107 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
var customGenesisTests = []struct {
genesis string
query string
result string
}{
// Plain genesis file without anything extra
{
genesis: `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}`,
query: "eth.getBlock(0).nonce",
result: "0x0000000000000042",
},
// Genesis file with an empty chain configuration (ensure missing fields work)
{
genesis: `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {}
}`,
query: "eth.getBlock(0).nonce",
result: "0x0000000000000042",
},
// Genesis file with specific chain configurations
{
genesis: `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"homesteadBlock" : 314,
"daoForkBlock" : 141,
"daoForkSupport" : true
},
}`,
query: "eth.getBlock(0).nonce",
result: "0x0000000000000042",
},
}
// Tests that initializing with a custom genesis block and chain definitions
// work properly.
func TestCustomGenesis(t *testing.T) {
for i, tt := range customGenesisTests {
// Create a temporary data directory to use and inspect later
datadir := tmpdir(t)
defer os.RemoveAll(datadir)
// Initialize the data directory with the custom genesis block
json := filepath.Join(datadir, "genesis.json")
if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil {
t.Fatalf("test %d: failed to write genesis file: %v", i, err)
}
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
// Query the custom genesis block
geth := runGeth(t, "--datadir", datadir, "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console")
geth.expectRegexp(tt.result)
geth.expectExit()
}
}

View file

@ -50,7 +50,7 @@ const (
clientIdentifier = "Gexp" // Client identifier to advertise over the network clientIdentifier = "Gexp" // Client identifier to advertise over the network
versionMajor = 1 // Major version component of the current release versionMajor = 1 // Major version component of the current release
versionMinor = 4 // Minor version component of the current release versionMinor = 4 // Minor version component of the current release
versionPatch = 7 // Patch version component of the current release versionPatch = 16 // Patch version component of the current release
versionMeta = "stable" // Version metadata to append to the version string versionMeta = "stable" // Version metadata to append to the version string
versionOracle = "0x926d69cc3bbf81d52cba6886d788df007a15a3cd" // Expanse address of the Gexp release oracle versionOracle = "0x926d69cc3bbf81d52cba6886d788df007a15a3cd" // Expanse address of the Gexp release oracle
) )
@ -148,7 +148,6 @@ participating.
utils.IdentityFlag, utils.IdentityFlag,
utils.UnlockedAccountFlag, utils.UnlockedAccountFlag,
utils.PasswordFileFlag, utils.PasswordFileFlag,
utils.GenesisFileFlag,
utils.BootnodesFlag, utils.BootnodesFlag,
utils.DataDirFlag, utils.DataDirFlag,
utils.KeyStoreDirFlag, utils.KeyStoreDirFlag,
@ -163,6 +162,8 @@ participating.
utils.MaxPendingPeersFlag, utils.MaxPendingPeersFlag,
utils.EtherbaseFlag, utils.EtherbaseFlag,
utils.GasPriceFlag, utils.GasPriceFlag,
utils.SupportDAOFork,
utils.OpposeDAOFork,
utils.MinerThreadsFlag, utils.MinerThreadsFlag,
utils.MiningEnabledFlag, utils.MiningEnabledFlag,
utils.MiningGPUFlag, utils.MiningGPUFlag,
@ -223,12 +224,6 @@ participating.
exp.EnableBadBlockReporting = true exp.EnableBadBlockReporting = true
utils.SetupNetwork(ctx) utils.SetupNetwork(ctx)
// Deprecation warning.
if ctx.GlobalIsSet(utils.GenesisFileFlag.Name) {
common.PrintDepricationWarning("--genesis is deprecated. Switch to use 'gexp init /path/to/file'")
}
return nil return nil
} }

View file

@ -68,7 +68,6 @@ var AppHelpFlagGroups = []flagGroup{
utils.OlympicFlag, utils.OlympicFlag,
utils.TestNetFlag, utils.TestNetFlag,
utils.DevModeFlag, utils.DevModeFlag,
utils.GenesisFileFlag,
utils.IdentityFlag, utils.IdentityFlag,
utils.FastSyncFlag, utils.FastSyncFlag,
utils.LightKDFFlag, utils.LightKDFFlag,

View file

@ -23,6 +23,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"regexp" "regexp"
"runtime"
"github.com/expanse-project/go-expanse/common" "github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/core" "github.com/expanse-project/go-expanse/core"
@ -53,11 +54,17 @@ func openLogFile(Datadir string, filename string) *os.File {
// is redirected to a different file. // is redirected to a different file.
func Fatalf(format string, args ...interface{}) { func Fatalf(format string, args ...interface{}) {
w := io.MultiWriter(os.Stdout, os.Stderr) w := io.MultiWriter(os.Stdout, os.Stderr)
if runtime.GOOS == "windows" {
// The SameFile check below doesn't work on Windows.
// stdout is unlikely to get redirected though, so just print there.
w = os.Stdout
} else {
outf, _ := os.Stdout.Stat() outf, _ := os.Stdout.Stat()
errf, _ := os.Stderr.Stat() errf, _ := os.Stderr.Stat()
if outf != nil && errf != nil && os.SameFile(outf, errf) { if outf != nil && errf != nil && os.SameFile(outf, errf) {
w = os.Stderr w = os.Stderr
} }
}
fmt.Fprintf(w, "Fatal: "+format+"\n", args...) fmt.Fprintf(w, "Fatal: "+format+"\n", args...)
logger.Flush() logger.Flush()
os.Exit(1) os.Exit(1)

View file

@ -22,13 +22,11 @@ import (
"io/ioutil" "io/ioutil"
"math" "math"
"math/big" "math/big"
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/expanse-project/ethash" "github.com/expanse-project/ethash"
"github.com/expanse-project/go-expanse/accounts" "github.com/expanse-project/go-expanse/accounts"
@ -126,10 +124,6 @@ var (
Name: "dev", Name: "dev",
Usage: "Developer mode: pre-configured private network with several debugging flags", Usage: "Developer mode: pre-configured private network with several debugging flags",
} }
GenesisFileFlag = cli.StringFlag{
Name: "genesis",
Usage: "Insert/overwrite the genesis block (JSON format)",
}
IdentityFlag = cli.StringFlag{ IdentityFlag = cli.StringFlag{
Name: "identity", Name: "identity",
Usage: "Custom node name", Usage: "Custom node name",
@ -161,6 +155,15 @@ var (
Name: "lightkdf", Name: "lightkdf",
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength", Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
} }
// Fork settings
SupportDAOFork = cli.BoolFlag{
Name: "support-dao-fork",
Usage: "Updates the chain rules to support the DAO hard-fork",
}
OpposeDAOFork = cli.BoolFlag{
Name: "oppose-dao-fork",
Usage: "Updates the chain rules to oppose the DAO hard-fork",
}
// Miner settings // Miner settings
// TODO: refactor CPU vs GPU mining flags // TODO: refactor CPU vs GPU mining flags
MiningEnabledFlag = cli.BoolFlag{ MiningEnabledFlag = cli.BoolFlag{
@ -534,20 +537,6 @@ func MakeWSRpcHost(ctx *cli.Context) string {
return ctx.GlobalString(WSListenAddrFlag.Name) return ctx.GlobalString(WSListenAddrFlag.Name)
} }
// MakeGenesisBlock loads up a genesis block from an input file specified in the
// command line, or returns the empty string if none set.
func MakeGenesisBlock(ctx *cli.Context) string {
genesis := ctx.GlobalString(GenesisFileFlag.Name)
if genesis == "" {
return ""
}
data, err := ioutil.ReadFile(genesis)
if err != nil {
Fatalf("Failed to load custom genesis file: %v", err)
}
return string(data)
}
// MakeDatabaseHandles raises out the number of allowed file handles per process // MakeDatabaseHandles raises out the number of allowed file handles per process
// for Gexp and returns half of the allowance to assign to the database. // for Gexp and returns half of the allowance to assign to the database.
func MakeDatabaseHandles() int { func MakeDatabaseHandles() int {
@ -676,20 +665,9 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
} }
// Configure the Expanse service // Configure the Expanse service
accman := MakeAccountManager(ctx) accman := MakeAccountManager(ctx)
// initialise new random number generator
rand := rand.New(rand.NewSource(time.Now().UnixNano()))
// get enabled jit flag
jitEnabled := ctx.GlobalBool(VMEnableJitFlag.Name) jitEnabled := ctx.GlobalBool(VMEnableJitFlag.Name)
// if the jit is not enabled enable it for 10 pct of the people
if !jitEnabled && rand.Float64() < 0.1 {
jitEnabled = true
glog.V(logger.Info).Infoln("You're one of the lucky few that will try out the JIT VM (random). If you get a consensus failure please be so kind to report this incident with the block hash that failed. You can switch to the regular VM by setting --jitvm=false")
}
ethConf := &exp.Config{ ethConf := &exp.Config{
ChainConfig: MustMakeChainConfig(ctx), ChainConfig: MustMakeChainConfig(ctx),
Genesis: MakeGenesisBlock(ctx),
FastSync: ctx.GlobalBool(FastSyncFlag.Name), FastSync: ctx.GlobalBool(FastSyncFlag.Name),
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name), BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name), DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
@ -722,17 +700,13 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
ethConf.NetworkId = 1 ethConf.NetworkId = 1
} }
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.OlympicGenesisBlock() ethConf.Genesis = core.OlympicGenesisBlock()
}
case ctx.GlobalBool(TestNetFlag.Name): case ctx.GlobalBool(TestNetFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
ethConf.NetworkId = 2 ethConf.NetworkId = 2
} }
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.TestNetGenesisBlock() ethConf.Genesis = core.TestNetGenesisBlock()
}
state.StartingNonce = 1048576 // (2**20) state.StartingNonce = 1048576 // (2**20)
case ctx.GlobalBool(DevModeFlag.Name): case ctx.GlobalBool(DevModeFlag.Name):
@ -746,10 +720,8 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
if !ctx.GlobalIsSet(ListenPortFlag.Name) { if !ctx.GlobalIsSet(ListenPortFlag.Name) {
stackConf.ListenAddr = ":0" stackConf.ListenAddr = ":0"
} }
// Override the Expanse protocol configs // Override the Ethereum protocol configs
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.OlympicGenesisBlock() ethConf.Genesis = core.OlympicGenesisBlock()
}
if !ctx.GlobalIsSet(GasPriceFlag.Name) { if !ctx.GlobalIsSet(GasPriceFlag.Name) {
ethConf.GasPrice = new(big.Int) ethConf.GasPrice = new(big.Int)
} }
@ -806,24 +778,62 @@ func MustMakeChainConfig(ctx *cli.Context) *core.ChainConfig {
// MustMakeChainConfigFromDb reads the chain configuration from the given database. // MustMakeChainConfigFromDb reads the chain configuration from the given database.
func MustMakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig { func MustMakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig {
genesis := core.GetBlock(db, core.GetCanonicalHash(db, 0)) // If the chain is already initialized, use any existing chain configs
config := new(core.ChainConfig)
genesis := core.GetBlock(db, core.GetCanonicalHash(db, 0))
if genesis != nil { if genesis != nil {
// Existing genesis block, use stored config if available.
storedConfig, err := core.GetChainConfig(db, genesis.Hash()) storedConfig, err := core.GetChainConfig(db, genesis.Hash())
if err == nil { switch err {
return storedConfig case nil:
} else if err != core.ChainConfigNotFoundErr { config = storedConfig
case core.ChainConfigNotFoundErr:
// No configs found, use empty, will populate below
default:
Fatalf("Could not make chain configuration: %v", err) Fatalf("Could not make chain configuration: %v", err)
} }
} }
var homesteadBlockNo *big.Int // Set any missing fields due to them being unset or system upgrade
if config.HomesteadBlock == nil {
if ctx.GlobalBool(TestNetFlag.Name) { if ctx.GlobalBool(TestNetFlag.Name) {
homesteadBlockNo = params.TestNetHomesteadBlock config.HomesteadBlock = params.TestNetHomesteadBlock
} else { } else {
homesteadBlockNo = params.MainNetHomesteadBlock config.HomesteadBlock = params.MainNetHomesteadBlock
} }
return &core.ChainConfig{HomesteadBlock: homesteadBlockNo} }
if config.DAOForkBlock == nil {
if ctx.GlobalBool(TestNetFlag.Name) {
config.DAOForkBlock = params.TestNetDAOForkBlock
} else {
config.DAOForkBlock = params.MainNetDAOForkBlock
}
config.DAOForkSupport = true
}
// Force override any existing configs if explicitly requested
switch {
case ctx.GlobalBool(SupportDAOFork.Name):
config.DAOForkSupport = true
case ctx.GlobalBool(OpposeDAOFork.Name):
config.DAOForkSupport = false
}
// Temporarilly display a proper message so the user knows which fork its on
if !ctx.GlobalBool(TestNetFlag.Name) && (genesis == nil || genesis.Hash() == common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")) {
choice := "SUPPORT"
if !config.DAOForkSupport {
choice = "OPPOSE"
}
current := fmt.Sprintf(" is currently configured to %s the DAO hard-fork!", choice)
howtoswap := fmt.Sprintf("You can change your choice prior to block #%v with --support-dao-fork or --oppose-dao-fork.", config.DAOForkBlock)
howtosync := fmt.Sprintf("After the hard-fork block #%v passed, changing chains requires a resync from scratch!", config.DAOForkBlock)
separator := strings.Repeat("-", len(howtoswap))
glog.V(logger.Warn).Info(separator)
glog.V(logger.Warn).Info(current)
glog.V(logger.Warn).Info(howtoswap)
glog.V(logger.Warn).Info(howtosync)
glog.V(logger.Warn).Info(separator)
}
return config
} }
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.

View file

@ -241,11 +241,11 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str
return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:]
} }
// Welcome show summary of current Geth instance and some metadata about the // Welcome show summary of current instance and some metadata about the
// console's available modules. // console's available modules.
func (c *Console) Welcome() { func (c *Console) Welcome() {
// Print some generic Geth metadata // Print some generic metadata
fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n") fmt.Fprintf(c.printer, "Welcome to the JavaScript console!\n\n")
c.jsre.Run(` c.jsre.Run(`
console.log("instance: " + web3.version.node); console.log("instance: " + web3.version.node);
console.log("coinbase: " + eth.coinbase); console.log("coinbase: " + eth.coinbase);

View file

@ -2,7 +2,7 @@ FROM alpine:3.3
RUN \ RUN \
apk add --update go git make gcc musl-dev && \ apk add --update go git make gcc musl-dev && \
git clone https://github.com/ethereum/go-ethereum && \ git clone https://github.com/expanse-project/go-expanse && \
(cd go-ethereum && git checkout develop) && \ (cd go-ethereum && git checkout develop) && \
(cd go-ethereum && make gexp) && \ (cd go-ethereum && make gexp) && \
cp go-ethereum/build/bin/gexp /gexp && \ cp go-ethereum/build/bin/gexp /gexp && \

View file

@ -1,14 +1,14 @@
FROM alpine:3.3 FROM alpine:3.3
RUN \ RUN \
apk add --update go git make gcc musl-dev gmp-dev gmp && \ apk add --update go git make gcc musl-dev && \
git clone https://github.com/ethereum/go-ethereum && \ git clone https://github.com/expanse-project/go-expanse && \
(cd go-ethereum && make gexp) && \ (cd go-expanse && make geth) && \
cp go-ethereum/build/bin/gexp /gexp && \ cp go-expanse/build/bin/gexp /gexp && \
apk del go git make gcc musl-dev gmp-dev && \ apk del go git make gcc musl-dev && \
rm -rf /go-ethereum && rm -rf /var/cache/apk/* rm -rf /go-expanse && rm -rf /var/cache/apk/*
EXPOSE 8545 EXPOSE 9656
EXPOSE 30303 EXPOSE 42786
ENTRYPOINT ["/gexp"] ENTRYPOINT ["/gexp"]

View file

@ -8,7 +8,7 @@ Vagrant.configure(2) do |config|
vb.memory = "2048" vb.memory = "2048"
end end
config.vm.synced_folder "../../", "/home/vagrant/go/src/github.com/ethereum/go-ethereum" config.vm.synced_folder "../../", "/home/vagrant/go/src/github.com/expanse-project/go-expanse"
config.vm.synced_folder ".", "/vagrant", disabled: true config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.provision "shell", inline: <<-SHELL config.vm.provision "shell", inline: <<-SHELL

View file

@ -163,7 +163,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
// Generate a chain of b.N blocks using the supplied block // Generate a chain of b.N blocks using the supplied block
// generator function. // generator function.
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{benchRootAddr, benchRootFunds}) genesis := WriteGenesisBlockForTesting(db, GenesisAccount{benchRootAddr, benchRootFunds})
chain, _ := GenerateChain(genesis, db, b.N, gen) chain, _ := GenerateChain(nil, genesis, db, b.N, gen)
// Time the insertion of the new chain. // Time the insertion of the new chain.
// State and blocks are stored in the same DB. // State and blocks are stored in the same DB.

View file

@ -247,7 +247,8 @@ func ValidateHeader(config *ChainConfig, pow pow.PoW, header *types.Header, pare
return &BlockNonceErr{header.Number, header.Hash(), header.Nonce.Uint64()} return &BlockNonceErr{header.Number, header.Hash(), header.Nonce.Uint64()}
} }
} }
return nil // If all checks passed, validate the extra-data field for hard forks
return ValidateDAOHeaderExtraData(config, header)
} }
// CalcDifficulty is the difficulty adjustment algorithm. It returns // CalcDifficulty is the difficulty adjustment algorithm. It returns

View file

@ -95,6 +95,7 @@ type BlockChain struct {
currentBlock *types.Block // Current head of the block chain currentBlock *types.Block // Current head of the block chain
currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!) currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!)
stateCache *state.StateDB // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
blockCache *lru.Cache // Cache for the most recent entire blocks blockCache *lru.Cache // Cache for the most recent entire blocks
@ -198,11 +199,18 @@ func (self *BlockChain) loadLastState() error {
self.currentFastBlock = block self.currentFastBlock = block
} }
} }
// Initialize a statedb cache to ensure singleton account bloom filter generation
statedb, err := state.New(self.currentBlock.Root(), self.chainDb)
if err != nil {
return err
}
self.stateCache = statedb
self.stateCache.GetAccount(common.Address{})
// Issue a status log and return // Issue a status log and return
headerTd := self.GetTd(self.hc.CurrentHeader().Hash()) headerTd := self.GetTd(self.hc.CurrentHeader().Hash())
blockTd := self.GetTd(self.currentBlock.Hash()) blockTd := self.GetTd(self.currentBlock.Hash())
fastTd := self.GetTd(self.currentFastBlock.Hash()) fastTd := self.GetTd(self.currentFastBlock.Hash())
glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.hc.CurrentHeader().Number, self.hc.CurrentHeader().Hash().Bytes()[:4], headerTd) glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.hc.CurrentHeader().Number, self.hc.CurrentHeader().Hash().Bytes()[:4], headerTd)
glog.V(logger.Info).Infof("Last block: #%d [%x…] TD=%v", self.currentBlock.Number(), self.currentBlock.Hash().Bytes()[:4], blockTd) glog.V(logger.Info).Infof("Last block: #%d [%x…] TD=%v", self.currentBlock.Number(), self.currentBlock.Hash().Bytes()[:4], blockTd)
glog.V(logger.Info).Infof("Fast block: #%d [%x…] TD=%v", self.currentFastBlock.Number(), self.currentFastBlock.Hash().Bytes()[:4], fastTd) glog.V(logger.Info).Infof("Fast block: #%d [%x…] TD=%v", self.currentFastBlock.Number(), self.currentFastBlock.Hash().Bytes()[:4], fastTd)
@ -349,7 +357,12 @@ func (self *BlockChain) AuxValidator() pow.PoW { return self.pow }
// State returns a new mutable state based on the current HEAD block. // State returns a new mutable state based on the current HEAD block.
func (self *BlockChain) State() (*state.StateDB, error) { func (self *BlockChain) State() (*state.StateDB, error) {
return state.New(self.CurrentBlock().Root(), self.chainDb) return self.StateAt(self.CurrentBlock().Root())
}
// StateAt returns a new mutable state based on a particular point in time.
func (self *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
return self.stateCache.New(root)
} }
// Reset purges the entire blockchain, restoring it to its genesis state. // Reset purges the entire blockchain, restoring it to its genesis state.
@ -763,13 +776,20 @@ func (self *BlockChain) WriteBlock(block *types.Block) (status WriteStatus, err
if ptd == nil { if ptd == nil {
return NonStatTy, ParentError(block.ParentHash()) return NonStatTy, ParentError(block.ParentHash())
} }
// Make sure no inconsistent state is leaked during insertion
self.mu.Lock()
defer self.mu.Unlock()
localTd := self.GetTd(self.currentBlock.Hash()) localTd := self.GetTd(self.currentBlock.Hash())
externTd := new(big.Int).Add(block.Difficulty(), ptd) externTd := new(big.Int).Add(block.Difficulty(), ptd)
// Make sure no inconsistent state is leaked during insertion // Irrelevant of the canonical status, write the block itself to the database
self.mu.Lock() if err := self.hc.WriteTd(block.Hash(), externTd); err != nil {
defer self.mu.Unlock() glog.Fatalf("failed to write block total difficulty: %v", err)
}
if err := WriteBlock(self.chainDb, block); err != nil {
glog.Fatalf("failed to write block contents: %v", err)
}
// If the total difficulty is higher than our known, add it to the canonical chain // If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining. // Second clause in the if statement reduces the vulnerability to selfish mining.
@ -782,20 +802,11 @@ func (self *BlockChain) WriteBlock(block *types.Block) (status WriteStatus, err
return NonStatTy, err return NonStatTy, err
} }
} }
// Insert the block as the new head of the chain self.insert(block) // Insert the block as the new head of the chain
self.insert(block)
status = CanonStatTy status = CanonStatTy
} else { } else {
status = SideStatTy status = SideStatTy
} }
// Irrelevant of the canonical status, write the block itself to the database
if err := self.hc.WriteTd(block.Hash(), externTd); err != nil {
glog.Fatalf("failed to write block total difficulty: %v", err)
}
if err := WriteBlock(self.chainDb, block); err != nil {
glog.Fatalf("failed to write block contents: %v", err)
}
self.futureBlocks.Remove(block.Hash()) self.futureBlocks.Remove(block.Hash())
return return
@ -820,7 +831,6 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
tstart = time.Now() tstart = time.Now()
nonceChecked = make([]bool, len(chain)) nonceChecked = make([]bool, len(chain))
statedb *state.StateDB
) )
// Start the parallel nonce verifier. // Start the parallel nonce verifier.
@ -887,29 +897,30 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// Create a new statedb using the parent block and report an // Create a new statedb using the parent block and report an
// error if it fails. // error if it fails.
if statedb == nil { switch {
statedb, err = state.New(self.GetBlock(block.ParentHash()).Root(), self.chainDb) case i == 0:
} else { err = self.stateCache.Reset(self.GetBlock(block.ParentHash()).Root())
err = statedb.Reset(chain[i-1].Root()) default:
err = self.stateCache.Reset(chain[i-1].Root())
} }
if err != nil { if err != nil {
reportBlock(block, err) reportBlock(block, err)
return i, err return i, err
} }
// Process block using the parent state as reference point. // Process block using the parent state as reference point.
receipts, logs, usedGas, err := self.processor.Process(block, statedb, self.config.VmConfig) receipts, logs, usedGas, err := self.processor.Process(block, self.stateCache, self.config.VmConfig)
if err != nil { if err != nil {
reportBlock(block, err) reportBlock(block, err)
return i, err return i, err
} }
// Validate the state using the default validator // Validate the state using the default validator
err = self.Validator().ValidateState(block, self.GetBlock(block.ParentHash()), statedb, receipts, usedGas) err = self.Validator().ValidateState(block, self.GetBlock(block.ParentHash()), self.stateCache, receipts, usedGas)
if err != nil { if err != nil {
reportBlock(block, err) reportBlock(block, err)
return i, err return i, err
} }
// Write state changes to database // Write state changes to database
_, err = statedb.Commit() _, err = self.stateCache.Commit()
if err != nil { if err != nil {
return i, err return i, err
} }

View file

@ -712,7 +712,7 @@ func TestFastVsFullChains(t *testing.T) {
funds = big.NewInt(1000000000) funds = big.NewInt(1000000000)
genesis = GenesisBlockForTesting(gendb, address, funds) genesis = GenesisBlockForTesting(gendb, address, funds)
) )
blocks, receipts := GenerateChain(genesis, gendb, 1024, func(i int, block *BlockGen) { blocks, receipts := GenerateChain(nil, genesis, gendb, 1024, func(i int, block *BlockGen) {
block.SetCoinbase(common.Address{0x00}) block.SetCoinbase(common.Address{0x00})
// If the block number is multiple of 3, send a few bonus transactions to the miner // If the block number is multiple of 3, send a few bonus transactions to the miner
@ -795,7 +795,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
genesis = GenesisBlockForTesting(gendb, address, funds) genesis = GenesisBlockForTesting(gendb, address, funds)
) )
height := uint64(1024) height := uint64(1024)
blocks, receipts := GenerateChain(genesis, gendb, int(height), nil) blocks, receipts := GenerateChain(nil, genesis, gendb, int(height), nil)
// Configure a subchain to roll back // Configure a subchain to roll back
remove := []common.Hash{} remove := []common.Hash{}
@ -895,7 +895,7 @@ func TestChainTxReorgs(t *testing.T) {
// - futureAdd: transaction added after the reorg has already finished // - futureAdd: transaction added after the reorg has already finished
var pastAdd, freshAdd, futureAdd *types.Transaction var pastAdd, freshAdd, futureAdd *types.Transaction
chain, _ := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) { chain, _ := GenerateChain(nil, genesis, db, 3, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2) pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
@ -920,7 +920,7 @@ func TestChainTxReorgs(t *testing.T) {
} }
// overwrite the old chain // overwrite the old chain
chain, _ = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) { chain, _ = GenerateChain(nil, genesis, db, 5, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
@ -990,7 +990,7 @@ func TestLogReorgs(t *testing.T) {
blockchain, _ := NewBlockChain(db, testChainConfig(), FakePow{}, evmux) blockchain, _ := NewBlockChain(db, testChainConfig(), FakePow{}, evmux)
subs := evmux.Subscribe(RemovedLogsEvent{}) subs := evmux.Subscribe(RemovedLogsEvent{})
chain, _ := GenerateChain(genesis, db, 2, func(i int, gen *BlockGen) { chain, _ := GenerateChain(nil, genesis, db, 2, func(i int, gen *BlockGen) {
if i == 1 { if i == 1 {
tx, err := types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code).SignECDSA(key1) tx, err := types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code).SignECDSA(key1)
if err != nil { if err != nil {
@ -1003,7 +1003,7 @@ func TestLogReorgs(t *testing.T) {
t.Fatalf("failed to insert chain: %v", err) t.Fatalf("failed to insert chain: %v", err)
} }
chain, _ = GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {}) chain, _ = GenerateChain(nil, genesis, db, 3, func(i int, gen *BlockGen) {})
if _, err := blockchain.InsertChain(chain); err != nil { if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
@ -1025,12 +1025,12 @@ func TestReorgSideEvent(t *testing.T) {
evmux := &event.TypeMux{} evmux := &event.TypeMux{}
blockchain, _ := NewBlockChain(db, testChainConfig(), FakePow{}, evmux) blockchain, _ := NewBlockChain(db, testChainConfig(), FakePow{}, evmux)
chain, _ := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {}) chain, _ := GenerateChain(nil, genesis, db, 3, func(i int, gen *BlockGen) {})
if _, err := blockchain.InsertChain(chain); err != nil { if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert chain: %v", err) t.Fatalf("failed to insert chain: %v", err)
} }
replacementBlocks, _ := GenerateChain(genesis, db, 4, func(i int, gen *BlockGen) { replacementBlocks, _ := GenerateChain(nil, genesis, db, 4, func(i int, gen *BlockGen) {
tx, err := types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil).SignECDSA(key1) tx, err := types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil).SignECDSA(key1)
if i == 2 { if i == 2 {
gen.OffsetTime(-1) gen.OffsetTime(-1)
@ -1090,3 +1090,41 @@ done:
} }
} }
// Tests if the canonical block can be fetched from the database during chain insertion.
func TestCanonicalBlockRetrieval(t *testing.T) {
var (
db, _ = ethdb.NewMemDatabase()
genesis = WriteGenesisBlockForTesting(db)
)
evmux := &event.TypeMux{}
blockchain, _ := NewBlockChain(db, testChainConfig(), FakePow{}, evmux)
chain, _ := GenerateChain(nil, genesis, db, 10, func(i int, gen *BlockGen) {})
for i, _ := range chain {
go func(block *types.Block) {
// try to retrieve a block by its canonical hash and see if the block data can be retrieved.
for {
ch := GetCanonicalHash(db, block.NumberU64())
if ch == (common.Hash{}) {
continue // busy wait for canonical hash to be written
}
if ch != block.Hash() {
t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex())
}
fb := GetBlock(db, ch)
if fb == nil {
t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex())
}
if fb.Hash() != block.Hash() {
t.Fatalf("invalid block hash for block %d, want %s, got %s", block.NumberU64(), block.Hash().Hex(), fb.Hash().Hex())
}
return
}
}(chain[i])
blockchain.InsertChain(types.Blocks{chain[i]})
}
}

View file

@ -19,13 +19,13 @@ package core
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/expanse-project/go-expanse/common" "github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/core/state" "github.com/expanse-project/go-expanse/core/state"
"github.com/expanse-project/go-expanse/core/types" "github.com/expanse-project/go-expanse/core/types"
"github.com/expanse-project/go-expanse/core/vm" "github.com/expanse-project/go-expanse/core/vm"
"github.com/expanse-project/go-expanse/ethdb" "github.com/expanse-project/go-expanse/ethdb"
"github.com/expanse-project/go-expanse/event" "github.com/expanse-project/go-expanse/event"
"github.com/expanse-project/go-expanse/params"
"github.com/expanse-project/go-expanse/pow" "github.com/expanse-project/go-expanse/pow"
) )
@ -35,7 +35,11 @@ import (
// MakeChainConfig returns a new ChainConfig with the expanse default chain settings. // MakeChainConfig returns a new ChainConfig with the expanse default chain settings.
func MakeChainConfig() *ChainConfig { func MakeChainConfig() *ChainConfig {
return &ChainConfig{HomesteadBlock: big.NewInt(0)} return &ChainConfig{
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
}
} }
// FakePow is a non-validating proof of work implementation. // FakePow is a non-validating proof of work implementation.
@ -126,7 +130,7 @@ func (b *BlockGen) AddUncheckedReceipt(receipt *types.Receipt) {
// TxNonce returns the next valid transaction nonce for the // TxNonce returns the next valid transaction nonce for the
// account at addr. It panics if the account does not exist. // account at addr. It panics if the account does not exist.
func (b *BlockGen) TxNonce(addr common.Address) uint64 { func (b *BlockGen) TxNonce(addr common.Address) uint64 {
if !b.statedb.HasAccount(addr) { if !b.statedb.Exist(addr) {
panic("account does not exist") panic("account does not exist")
} }
return b.statedb.GetNonce(addr) return b.statedb.GetNonce(addr)
@ -173,10 +177,27 @@ func (b *BlockGen) OffsetTime(seconds int64) {
// Blocks created by GenerateChain do not contain valid proof of work // Blocks created by GenerateChain do not contain valid proof of work
// values. Inserting them into BlockChain requires use of FakePow or // values. Inserting them into BlockChain requires use of FakePow or
// a similar non-validating proof of work implementation. // a similar non-validating proof of work implementation.
func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { func GenerateChain(config *ChainConfig, parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
genblock := func(i int, h *types.Header, statedb *state.StateDB) (*types.Block, types.Receipts) { genblock := func(i int, h *types.Header, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb} b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb}
// Mutate the state and block according to any hard-fork specs
if config == nil {
config = MakeChainConfig()
}
if daoBlock := config.DAOForkBlock; daoBlock != nil {
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
if h.Number.Cmp(daoBlock) >= 0 && h.Number.Cmp(limit) < 0 {
if config.DAOForkSupport {
h.Extra = common.CopyBytes(params.DAOForkBlockExtra)
}
}
}
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(h.Number) == 0 {
ApplyDAOHardFork(statedb)
}
// Execute any user modifications to the block and finalize it
if gen != nil { if gen != nil {
gen(i, b) gen(i, b)
} }
@ -261,7 +282,7 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [
// makeBlockChain creates a deterministic chain of blocks rooted at parent. // makeBlockChain creates a deterministic chain of blocks rooted at parent.
func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block { func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block {
blocks, _ := GenerateChain(parent, db, n, func(i int, b *BlockGen) { blocks, _ := GenerateChain(nil, parent, db, n, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
}) })
return blocks return blocks

View file

@ -47,7 +47,7 @@ func ExampleGenerateChain() {
// This call generates a chain of 5 blocks. The function runs for // This call generates a chain of 5 blocks. The function runs for
// each block and adds different features to gen based on the // each block and adds different features to gen based on the
// block index. // block index.
chain, _ := GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) { chain, _ := GenerateChain(nil, genesis, db, 5, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
// In block 1, addr1 sends addr2 some expanse. // In block 1, addr1 sends addr2 some expanse.
@ -79,7 +79,7 @@ func ExampleGenerateChain() {
evmux := &event.TypeMux{} evmux := &event.TypeMux{}
blockchain, _ := NewBlockChain(db, MakeChainConfig(), FakePow{}, evmux) blockchain, _ := NewBlockChain(db, MakeChainConfig(), FakePow{}, evmux)
if i, err := blockchain.InsertChain(chain); err != nil { if i, err := blockchain.InsertChain(chain); err != nil {
fmt.Printf("insert error (block %d): %v\n", i, err) fmt.Printf("insert error (block %d): %v\n", chain[i].NumberU64(), err)
return return
} }

View file

@ -60,7 +60,7 @@ func TestPowVerification(t *testing.T) {
var ( var (
testdb, _ = ethdb.NewMemDatabase() testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int)) genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
blocks, _ = GenerateChain(genesis, testdb, 8, nil) blocks, _ = GenerateChain(nil, genesis, testdb, 8, nil)
) )
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
for i, block := range blocks { for i, block := range blocks {
@ -115,7 +115,7 @@ func testPowConcurrentVerification(t *testing.T, threads int) {
var ( var (
testdb, _ = ethdb.NewMemDatabase() testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int)) genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
blocks, _ = GenerateChain(genesis, testdb, 8, nil) blocks, _ = GenerateChain(nil, genesis, testdb, 8, nil)
) )
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
for i, block := range blocks { for i, block := range blocks {
@ -186,7 +186,7 @@ func testPowConcurrentAbortion(t *testing.T, threads int) {
var ( var (
testdb, _ = ethdb.NewMemDatabase() testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int)) genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
blocks, _ = GenerateChain(genesis, testdb, 1024, nil) blocks, _ = GenerateChain(nil, genesis, testdb, 1024, nil)
) )
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
for i, block := range blocks { for i, block := range blocks {

View file

@ -31,16 +31,17 @@ var ChainConfigNotFoundErr = errors.New("ChainConfig not found") // general conf
// that any network, identified by its genesis block, can have its own // that any network, identified by its genesis block, can have its own
// set of configuration options. // set of configuration options.
type ChainConfig struct { type ChainConfig struct {
HomesteadBlock *big.Int // homestead switch block HomesteadBlock *big.Int `json:"homesteadBlock"` // Homestead switch block (nil = no fork, 0 = already homestead)
DAOForkBlock *big.Int `json:"daoForkBlock"` // TheDAO hard-fork switch block (nil = no fork)
DAOForkSupport bool `json:"daoForkSupport"` // Whether the nodes supports or opposes the DAO hard-fork
VmConfig vm.Config `json:"-"` VmConfig vm.Config `json:"-"`
} }
// IsHomestead returns whether num is either equal to the homestead block or greater. // IsHomestead returns whether num is either equal to the homestead block or greater.
func (c *ChainConfig) IsHomestead(num *big.Int) bool { func (c *ChainConfig) IsHomestead(num *big.Int) bool {
if num == nil { if c.HomesteadBlock == nil || num == nil {
return false return false
} }
return num.Cmp(c.HomesteadBlock) >= 0 return num.Cmp(c.HomesteadBlock) >= 0
} }

74
core/dao.go Normal file
View file

@ -0,0 +1,74 @@
// Copyright 2016 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 core
import (
"bytes"
"math/big"
"github.com/expanse-project/go-expanse/core/state"
"github.com/expanse-project/go-expanse/core/types"
"github.com/expanse-project/go-expanse/params"
)
// ValidateDAOHeaderExtraData validates the extra-data field of a block header to
// ensure it conforms to DAO hard-fork rules.
//
// DAO hard-fork extension to the header validity:
// a) if the node is no-fork, do not accept blocks in the [fork, fork+10) range
// with the fork specific extra-data set
// b) if the node is pro-fork, require blocks in the specific range to have the
// unique extra-data set.
func ValidateDAOHeaderExtraData(config *ChainConfig, header *types.Header) error {
// Short circuit validation if the node doesn't care about the DAO fork
if config.DAOForkBlock == nil {
return nil
}
// Make sure the block is within the fork's modified extra-data range
limit := new(big.Int).Add(config.DAOForkBlock, params.DAOForkExtraRange)
if header.Number.Cmp(config.DAOForkBlock) < 0 || header.Number.Cmp(limit) >= 0 {
return nil
}
// Depending whether we support or oppose the fork, validate the extra-data contents
if config.DAOForkSupport {
if bytes.Compare(header.Extra, params.DAOForkBlockExtra) != 0 {
return ValidationError("DAO pro-fork bad block extra-data: 0x%x", header.Extra)
}
} else {
if bytes.Compare(header.Extra, params.DAOForkBlockExtra) == 0 {
return ValidationError("DAO no-fork bad block extra-data: 0x%x", header.Extra)
}
}
// All ok, header has the same extra-data we expect
return nil
}
// ApplyDAOHardFork modifies the state database according to the DAO hard-fork
// rules, transferring all balances of a set of DAO accounts to a single refund
// contract.
func ApplyDAOHardFork(statedb *state.StateDB) {
// Retrieve the contract to refund balances into
refund := statedb.GetOrNewStateObject(params.DAORefundContract)
// Move every DAO account and extra-balance account funds into the refund contract
for _, addr := range params.DAODrainList {
if account := statedb.GetStateObject(addr); account != nil {
refund.AddBalance(account.Balance())
account.SetBalance(new(big.Int))
}
}
}

132
core/dao_test.go Normal file
View file

@ -0,0 +1,132 @@
// Copyright 2016 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 core
import (
"math/big"
"testing"
"github.com/expanse-project/go-expanse/ethdb"
"github.com/expanse-project/go-expanse/event"
"github.com/expanse-project/go-expanse/params"
)
// Tests that DAO-fork enabled clients can properly filter out fork-commencing
// blocks based on their extradata fields.
func TestDAOForkRangeExtradata(t *testing.T) {
forkBlock := big.NewInt(32)
// Generate a common prefix for both pro-forkers and non-forkers
db, _ := ethdb.NewMemDatabase()
genesis := WriteGenesisBlockForTesting(db)
prefix, _ := GenerateChain(nil, genesis, db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
// Create the concurrent, conflicting two nodes
proDb, _ := ethdb.NewMemDatabase()
WriteGenesisBlockForTesting(proDb)
proConf := &ChainConfig{HomesteadBlock: big.NewInt(0), DAOForkBlock: forkBlock, DAOForkSupport: true}
proBc, _ := NewBlockChain(proDb, proConf, new(FakePow), new(event.TypeMux))
conDb, _ := ethdb.NewMemDatabase()
WriteGenesisBlockForTesting(conDb)
conConf := &ChainConfig{HomesteadBlock: big.NewInt(0), DAOForkBlock: forkBlock, DAOForkSupport: false}
conBc, _ := NewBlockChain(conDb, conConf, new(FakePow), new(event.TypeMux))
if _, err := proBc.InsertChain(prefix); err != nil {
t.Fatalf("pro-fork: failed to import chain prefix: %v", err)
}
if _, err := conBc.InsertChain(prefix); err != nil {
t.Fatalf("con-fork: failed to import chain prefix: %v", err)
}
// Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks
for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ {
// Create a pro-fork block, and try to feed into the no-fork chain
db, _ = ethdb.NewMemDatabase()
WriteGenesisBlockForTesting(db)
bc, _ := NewBlockChain(db, conConf, new(FakePow), new(event.TypeMux))
blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64()+1))
for j := 0; j < len(blocks)/2; j++ {
blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j]
}
if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
}
blocks, _ = GenerateChain(proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
if _, err := conBc.InsertChain(blocks); err == nil {
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
}
// Create a proper no-fork block for the contra-forker
blocks, _ = GenerateChain(conConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
if _, err := conBc.InsertChain(blocks); err != nil {
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
}
// Create a no-fork block, and try to feed into the pro-fork chain
db, _ = ethdb.NewMemDatabase()
WriteGenesisBlockForTesting(db)
bc, _ = NewBlockChain(db, proConf, new(FakePow), new(event.TypeMux))
blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64()+1))
for j := 0; j < len(blocks)/2; j++ {
blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j]
}
if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
}
blocks, _ = GenerateChain(conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
if _, err := proBc.InsertChain(blocks); err == nil {
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
}
// Create a proper pro-fork block for the pro-forker
blocks, _ = GenerateChain(proConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
if _, err := proBc.InsertChain(blocks); err != nil {
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
}
}
// Verify that contra-forkers accept pro-fork extra-datas after forking finishes
db, _ = ethdb.NewMemDatabase()
WriteGenesisBlockForTesting(db)
bc, _ := NewBlockChain(db, conConf, new(FakePow), new(event.TypeMux))
blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64()+1))
for j := 0; j < len(blocks)/2; j++ {
blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j]
}
if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
}
blocks, _ = GenerateChain(proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
if _, err := conBc.InsertChain(blocks); err != nil {
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
}
// Verify that pro-forkers accept contra-fork extra-datas after forking finishes
db, _ = ethdb.NewMemDatabase()
WriteGenesisBlockForTesting(db)
bc, _ = NewBlockChain(db, proConf, new(FakePow), new(event.TypeMux))
blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64()+1))
for j := 0; j < len(blocks)/2; j++ {
blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j]
}
if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
}
blocks, _ = GenerateChain(conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
if _, err := proBc.InsertChain(blocks); err != nil {
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
}
}

View file

@ -157,7 +157,11 @@ func GetTd(db ethdb.Database, hash common.Hash) *big.Int {
} }
// GetBlock retrieves an entire block corresponding to the hash, assembling it // GetBlock retrieves an entire block corresponding to the hash, assembling it
// back from the stored header and body. // back from the stored header and body. If either the header or body could not
// be retrieved nil is returned.
//
// Note, due to concurrent download of header and block body the header and thus
// canonical hash can be stored in the database but the body data not (yet).
func GetBlock(db ethdb.Database, hash common.Hash) *types.Block { func GetBlock(db ethdb.Database, hash common.Hash) *types.Block {
// Retrieve the block header and body contents // Retrieve the block header and body contents
header := GetHeader(db, hash) header := GetHeader(db, hash)

View file

@ -62,7 +62,7 @@ func (d *diffTest) UnmarshalJSON(b []byte) (err error) {
return nil return nil
} }
func TestDifficultyFrontier(t *testing.T) { func TestCalcDifficulty(t *testing.T) {
file, err := os.Open("../tests/files/BasicTests/difficulty.json") file, err := os.Open("../tests/files/BasicTests/difficulty.json")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -75,9 +75,10 @@ func TestDifficultyFrontier(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
config := &ChainConfig{HomesteadBlock: big.NewInt(1150000)}
for name, test := range tests { for name, test := range tests {
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
diff := calcDifficultyFrontier(test.CurrentTimestamp, test.ParentTimestamp, number, test.ParentDifficulty) diff := CalcDifficulty(config, test.CurrentTimestamp, test.ParentTimestamp, number, test.ParentDifficulty)
if diff.Cmp(test.CurrentDifficulty) != 0 { if diff.Cmp(test.CurrentDifficulty) != 0 {
t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff) t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff)
} }
@ -561,7 +562,7 @@ func TestMipmapChain(t *testing.T) {
defer db.Close() defer db.Close()
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{addr, big.NewInt(1000000)}) genesis := WriteGenesisBlockForTesting(db, GenesisAccount{addr, big.NewInt(1000000)})
chain, receipts := GenerateChain(genesis, db, 1010, func(i int, gen *BlockGen) { chain, receipts := GenerateChain(nil, genesis, db, 1010, func(i int, gen *BlockGen) {
var receipts types.Receipts var receipts types.Receipts
switch i { switch i {
case 1: case 1:

View file

@ -28,14 +28,14 @@ import (
// Call executes within the given contract // Call executes within the given contract
func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
ret, _, err = exec(env, caller, &addr, &addr, input, env.Db().GetCode(addr), gas, gasPrice, value) ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value)
return ret, err return ret, err
} }
// CallCode executes the given address' code as the given contract address // CallCode executes the given address' code as the given contract address
func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
callerAddr := caller.Address() callerAddr := caller.Address()
ret, _, err = exec(env, caller, &callerAddr, &addr, input, env.Db().GetCode(addr), gas, gasPrice, value) ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value)
return ret, err return ret, err
} }
@ -44,13 +44,13 @@ func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address
callerAddr := caller.Address() callerAddr := caller.Address()
originAddr := env.Origin() originAddr := env.Origin()
callerValue := caller.Value() callerValue := caller.Value()
ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, input, env.Db().GetCode(addr), gas, gasPrice, callerValue) ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, callerValue)
return ret, err return ret, err
} }
// Create creates a new contract with the given code // Create creates a new contract with the given code
func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) { func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) {
ret, address, err = exec(env, caller, nil, nil, nil, code, gas, gasPrice, value) ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, gasPrice, value)
// Here we get an error if we run into maximum stack depth, // Here we get an error if we run into maximum stack depth,
// See: https://github.com/expanse-project/yellowpaper/pull/131 // See: https://github.com/expanse-project/yellowpaper/pull/131
// and YP definitions for CREATE instruction // and YP definitions for CREATE instruction
@ -60,7 +60,7 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPric
return ret, address, err return ret, address, err
} }
func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) {
evm := env.Vm() evm := env.Vm()
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
@ -86,7 +86,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
createAccount = true createAccount = true
} }
snapshotPreTransfer := env.MakeSnapshot() snapshotPreTransfer := env.SnapshotDatabase()
var ( var (
from = env.Db().GetAccount(caller.Address()) from = env.Db().GetAccount(caller.Address())
to vm.Account to vm.Account
@ -106,7 +106,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
// EVM. The contract is a scoped environment for this execution context // EVM. The contract is a scoped environment for this execution context
// only. // only.
contract := vm.NewContract(caller, to, value, gas, gasPrice) contract := vm.NewContract(caller, to, value, gas, gasPrice)
contract.SetCallCode(codeAddr, code) contract.SetCallCode(codeAddr, codeHash, code)
defer contract.Finalise() defer contract.Finalise()
ret, err = evm.Run(contract, input) ret, err = evm.Run(contract, input)
@ -130,13 +130,13 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) { if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
env.SetSnapshot(snapshotPreTransfer) env.RevertToSnapshot(snapshotPreTransfer)
} }
return ret, addr, err return ret, addr, err
} }
func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) {
evm := env.Vm() evm := env.Vm()
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
@ -145,7 +145,7 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA
return nil, common.Address{}, vm.DepthError return nil, common.Address{}, vm.DepthError
} }
snapshot := env.MakeSnapshot() snapshot := env.SnapshotDatabase()
var to vm.Account var to vm.Account
if !env.Db().Exist(*toAddr) { if !env.Db().Exist(*toAddr) {
@ -156,14 +156,14 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA
// Iinitialise a new contract and make initialise the delegate values // Iinitialise a new contract and make initialise the delegate values
contract := vm.NewContract(caller, to, value, gas, gasPrice).AsDelegate() contract := vm.NewContract(caller, to, value, gas, gasPrice).AsDelegate()
contract.SetCallCode(codeAddr, code) contract.SetCallCode(codeAddr, codeHash, code)
defer contract.Finalise() defer contract.Finalise()
ret, err = evm.Run(contract, input) ret, err = evm.Run(contract, input)
if err != nil { if err != nil {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
env.SetSnapshot(snapshot) env.RevertToSnapshot(snapshot)
} }
return ret, addr, err return ret, addr, err

View file

@ -129,6 +129,14 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
localTd := hc.GetTd(hc.currentHeaderHash) localTd := hc.GetTd(hc.currentHeaderHash)
externTd := new(big.Int).Add(header.Difficulty, ptd) externTd := new(big.Int).Add(header.Difficulty, ptd)
// Irrelevant of the canonical status, write the td and header to the database
if err := hc.WriteTd(hash, externTd); err != nil {
glog.Fatalf("failed to write header total difficulty: %v", err)
}
if err := WriteHeader(hc.chainDb, header); err != nil {
glog.Fatalf("failed to write header contents: %v", err)
}
// If the total difficulty is higher than our known, add it to the canonical chain // If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining. // Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
@ -150,6 +158,7 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
headHeader = hc.GetHeader(headHash) headHeader = hc.GetHeader(headHash)
headNumber = headHeader.Number.Uint64() headNumber = headHeader.Number.Uint64()
} }
// Extend the canonical chain with the new header // Extend the canonical chain with the new header
if err := WriteCanonicalHash(hc.chainDb, hash, number); err != nil { if err := WriteCanonicalHash(hc.chainDb, hash, number); err != nil {
glog.Fatalf("failed to insert header number: %v", err) glog.Fatalf("failed to insert header number: %v", err)
@ -157,19 +166,13 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
if err := WriteHeadHeaderHash(hc.chainDb, hash); err != nil { if err := WriteHeadHeaderHash(hc.chainDb, hash); err != nil {
glog.Fatalf("failed to insert head header hash: %v", err) glog.Fatalf("failed to insert head header hash: %v", err)
} }
hc.currentHeaderHash, hc.currentHeader = hash, types.CopyHeader(header) hc.currentHeaderHash, hc.currentHeader = hash, types.CopyHeader(header)
status = CanonStatTy status = CanonStatTy
} else { } else {
status = SideStatTy status = SideStatTy
} }
// Irrelevant of the canonical status, write the header itself to the database
if err := hc.WriteTd(hash, externTd); err != nil {
glog.Fatalf("failed to write header total difficulty: %v", err)
}
if err := WriteHeader(hc.chainDb, header); err != nil {
glog.Fatalf("failed to write header contents: %v", err)
}
hc.headerCache.Add(hash, header) hc.headerCache.Add(hash, header)
return return

View file

@ -21,9 +21,10 @@ import (
"fmt" "fmt"
"github.com/expanse-project/go-expanse/common" "github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/rlp"
) )
type Account struct { type DumpAccount struct {
Balance string `json:"balance"` Balance string `json:"balance"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce"`
Root string `json:"root"` Root string `json:"root"`
@ -32,40 +33,41 @@ type Account struct {
Storage map[string]string `json:"storage"` Storage map[string]string `json:"storage"`
} }
type World struct { type Dump struct {
Root string `json:"root"` Root string `json:"root"`
Accounts map[string]Account `json:"accounts"` Accounts map[string]DumpAccount `json:"accounts"`
} }
func (self *StateDB) RawDump() World { func (self *StateDB) RawDump() Dump {
world := World{ dump := Dump{
Root: common.Bytes2Hex(self.trie.Root()), Root: common.Bytes2Hex(self.trie.Root()),
Accounts: make(map[string]Account), Accounts: make(map[string]DumpAccount),
} }
it := self.trie.Iterator() it := self.trie.Iterator()
for it.Next() { for it.Next() {
addr := self.trie.GetKey(it.Key) addr := self.trie.GetKey(it.Key)
stateObject, err := DecodeObject(common.BytesToAddress(addr), self.db, it.Value) var data Account
if err != nil { if err := rlp.DecodeBytes(it.Value, &data); err != nil {
panic(err) panic(err)
} }
account := Account{ obj := newObject(nil, common.BytesToAddress(addr), data, nil)
Balance: stateObject.balance.String(), account := DumpAccount{
Nonce: stateObject.nonce, Balance: data.Balance.String(),
Root: common.Bytes2Hex(stateObject.Root()), Nonce: data.Nonce,
CodeHash: common.Bytes2Hex(stateObject.codeHash), Root: common.Bytes2Hex(data.Root[:]),
Code: common.Bytes2Hex(stateObject.Code()), CodeHash: common.Bytes2Hex(data.CodeHash),
Code: common.Bytes2Hex(obj.Code(self.db)),
Storage: make(map[string]string), Storage: make(map[string]string),
} }
storageIt := stateObject.trie.Iterator() storageIt := obj.getTrie(self.db).Iterator()
for storageIt.Next() { for storageIt.Next() {
account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value)
} }
world.Accounts[common.Bytes2Hex(addr)] = account dump.Accounts[common.Bytes2Hex(addr)] = account
} }
return world return dump
} }
func (self *StateDB) Dump() []byte { func (self *StateDB) Dump() []byte {
@ -76,12 +78,3 @@ func (self *StateDB) Dump() []byte {
return json return json
} }
// Debug stuff
func (self *StateObject) CreateOutputForDiff() {
fmt.Printf("%x %x %x %x\n", self.Address(), self.Root(), self.balance.Bytes(), self.nonce)
it := self.trie.Iterator()
for it.Next() {
fmt.Printf("%x %x\n", it.Key, it.Value)
}
}

View file

@ -76,7 +76,7 @@ func (it *NodeIterator) step() error {
} }
// Initialize the iterator if we've just started // Initialize the iterator if we've just started
if it.stateIt == nil { if it.stateIt == nil {
it.stateIt = trie.NewNodeIterator(it.state.trie.Trie) it.stateIt = it.state.trie.NodeIterator()
} }
// If we had data nodes previously, we surely have at least state nodes // If we had data nodes previously, we surely have at least state nodes
if it.dataIt != nil { if it.dataIt != nil {

117
core/state/journal.go Normal file
View file

@ -0,0 +1,117 @@
// Copyright 2016 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 state
import (
"math/big"
"github.com/expanse-project/go-expanse/common"
)
type journalEntry interface {
undo(*StateDB)
}
type journal []journalEntry
type (
// Changes to the account trie.
createObjectChange struct {
account *common.Address
}
resetObjectChange struct {
prev *StateObject
}
suicideChange struct {
account *common.Address
prev bool // whether account had already suicided
prevbalance *big.Int
}
// Changes to individual accounts.
balanceChange struct {
account *common.Address
prev *big.Int
}
nonceChange struct {
account *common.Address
prev uint64
}
storageChange struct {
account *common.Address
key, prevalue common.Hash
}
codeChange struct {
account *common.Address
prevcode, prevhash []byte
}
// Changes to other state values.
refundChange struct {
prev *big.Int
}
addLogChange struct {
txhash common.Hash
}
)
func (ch createObjectChange) undo(s *StateDB) {
s.GetStateObject(*ch.account).deleted = true
delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account)
}
func (ch resetObjectChange) undo(s *StateDB) {
s.setStateObject(ch.prev)
}
func (ch suicideChange) undo(s *StateDB) {
obj := s.GetStateObject(*ch.account)
if obj != nil {
obj.suicided = ch.prev
obj.setBalance(ch.prevbalance)
}
}
func (ch balanceChange) undo(s *StateDB) {
s.GetStateObject(*ch.account).setBalance(ch.prev)
}
func (ch nonceChange) undo(s *StateDB) {
s.GetStateObject(*ch.account).setNonce(ch.prev)
}
func (ch codeChange) undo(s *StateDB) {
s.GetStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
}
func (ch storageChange) undo(s *StateDB) {
s.GetStateObject(*ch.account).setState(ch.key, ch.prevalue)
}
func (ch refundChange) undo(s *StateDB) {
s.refund = ch.prev
}
func (ch addLogChange) undo(s *StateDB) {
logs := s.logs[ch.txhash]
if len(logs) == 1 {
delete(s.logs, ch.txhash)
} else {
s.logs[ch.txhash] = logs[:len(logs)-1]
}
}

View file

@ -33,14 +33,14 @@ type ManagedState struct {
mu sync.RWMutex mu sync.RWMutex
accounts map[string]*account accounts map[common.Address]*account
} }
// ManagedState returns a new managed state with the statedb as it's backing layer // ManagedState returns a new managed state with the statedb as it's backing layer
func ManageState(statedb *StateDB) *ManagedState { func ManageState(statedb *StateDB) *ManagedState {
return &ManagedState{ return &ManagedState{
StateDB: statedb.Copy(), StateDB: statedb.Copy(),
accounts: make(map[string]*account), accounts: make(map[common.Address]*account),
} }
} }
@ -103,7 +103,7 @@ func (ms *ManagedState) SetNonce(addr common.Address, nonce uint64) {
so := ms.GetOrNewStateObject(addr) so := ms.GetOrNewStateObject(addr)
so.SetNonce(nonce) so.SetNonce(nonce)
ms.accounts[addr.Str()] = newAccount(so) ms.accounts[addr] = newAccount(so)
} }
// HasAccount returns whether the given address is managed or not // HasAccount returns whether the given address is managed or not
@ -114,29 +114,28 @@ func (ms *ManagedState) HasAccount(addr common.Address) bool {
} }
func (ms *ManagedState) hasAccount(addr common.Address) bool { func (ms *ManagedState) hasAccount(addr common.Address) bool {
_, ok := ms.accounts[addr.Str()] _, ok := ms.accounts[addr]
return ok return ok
} }
// populate the managed state // populate the managed state
func (ms *ManagedState) getAccount(addr common.Address) *account { func (ms *ManagedState) getAccount(addr common.Address) *account {
straddr := addr.Str() if account, ok := ms.accounts[addr]; !ok {
if account, ok := ms.accounts[straddr]; !ok {
so := ms.GetOrNewStateObject(addr) so := ms.GetOrNewStateObject(addr)
ms.accounts[straddr] = newAccount(so) ms.accounts[addr] = newAccount(so)
} else { } else {
// Always make sure the state account nonce isn't actually higher // Always make sure the state account nonce isn't actually higher
// than the tracked one. // than the tracked one.
so := ms.StateDB.GetStateObject(addr) so := ms.StateDB.GetStateObject(addr)
if so != nil && uint64(len(account.nonces))+account.nstart < so.nonce { if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
ms.accounts[straddr] = newAccount(so) ms.accounts[addr] = newAccount(so)
} }
} }
return ms.accounts[straddr] return ms.accounts[addr]
} }
func newAccount(so *StateObject) *account { func newAccount(so *StateObject) *account {
return &account{so, so.nonce, nil} return &account{so, so.Nonce(), nil}
} }

View file

@ -29,11 +29,9 @@ func create() (*ManagedState, *account) {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := New(common.Hash{}, db) statedb, _ := New(common.Hash{}, db)
ms := ManageState(statedb) ms := ManageState(statedb)
so := &StateObject{address: addr, nonce: 100} ms.StateDB.SetNonce(addr, 100)
ms.StateDB.stateObjects[addr.Str()] = so ms.accounts[addr] = newAccount(ms.StateDB.GetStateObject(addr))
ms.accounts[addr.Str()] = newAccount(so) return ms, ms.accounts[addr]
return ms, ms.accounts[addr.Str()]
} }
func TestNewNonce(t *testing.T) { func TestNewNonce(t *testing.T) {
@ -92,7 +90,7 @@ func TestRemoteNonceChange(t *testing.T) {
account.nonces = append(account.nonces, nn...) account.nonces = append(account.nonces, nn...)
nonce := ms.NewNonce(addr) nonce := ms.NewNonce(addr)
ms.StateDB.stateObjects[addr.Str()].nonce = 200 ms.StateDB.stateObjects[addr].data.Nonce = 200
nonce = ms.NewNonce(addr) nonce = ms.NewNonce(addr)
if nonce != 200 { if nonce != 200 {
t.Error("expected nonce after remote update to be", 201, "got", nonce) t.Error("expected nonce after remote update to be", 201, "got", nonce)
@ -100,7 +98,7 @@ func TestRemoteNonceChange(t *testing.T) {
ms.NewNonce(addr) ms.NewNonce(addr)
ms.NewNonce(addr) ms.NewNonce(addr)
ms.NewNonce(addr) ms.NewNonce(addr)
ms.StateDB.stateObjects[addr.Str()].nonce = 200 ms.StateDB.stateObjects[addr].data.Nonce = 200
nonce = ms.NewNonce(addr) nonce = ms.NewNonce(addr)
if nonce != 204 { if nonce != 204 {
t.Error("expected nonce after remote update to be", 201, "got", nonce) t.Error("expected nonce after remote update to be", 201, "got", nonce)

View file

@ -57,143 +57,220 @@ func (self Storage) Copy() Storage {
return cpy return cpy
} }
// StateObject represents an Ethereum account which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// Account values can be accessed and modified through the object.
// Finally, call CommitTrie to write the modified storage trie into a database.
type StateObject struct { type StateObject struct {
db trie.Database // State database for storing state changes address common.Address // Ethereum address of this account
trie *trie.SecureTrie data Account
db *StateDB
// Address belonging to this account // DB error.
address common.Address // State objects are used by the consensus core and VM which are
// The balance of the account // unable to deal with database-level errors. Any error that occurs
balance *big.Int // during a database read is memoized here and will eventually be returned
// The nonce of the account // by StateDB.Commit.
nonce uint64 dbErr error
// The code hash if code is present (i.e. a contract)
codeHash []byte
// The code for this account
code Code
// Temporarily initialisation code
initCode Code
// Cached storage (flushed when updated)
storage Storage
// Mark for deletion // Write caches.
// When an object is marked for deletion it will be delete from the trie trie *trie.SecureTrie // storage trie, which becomes non-nil on first access
// during the "update" phase of the state transition code Code // contract bytecode, which gets set when code is loaded
remove bool
cachedStorage Storage // Storage entry cache to avoid duplicate reads
dirtyStorage Storage // Storage entries that need to be flushed to disk
// Cache flags.
// When an object is marked suicided it will be delete from the trie
// during the "update" phase of the state transition.
dirtyCode bool // true if the code was updated
suicided bool
deleted bool deleted bool
dirty bool onDirty func(addr common.Address) // Callback method to mark a state object newly dirty
} }
func NewStateObject(address common.Address, db trie.Database) *StateObject { // Account is the Ethereum consensus representation of accounts.
object := &StateObject{ // These objects are stored in the main account trie.
db: db, type Account struct {
address: address, Nonce uint64
balance: new(big.Int), Balance *big.Int
dirty: true, Root common.Hash // merkle root of the storage trie
codeHash: emptyCodeHash, CodeHash []byte
storage: make(Storage), }
// newObject creates a state object.
func newObject(db *StateDB, address common.Address, data Account, onDirty func(addr common.Address)) *StateObject {
if data.Balance == nil {
data.Balance = new(big.Int)
} }
object.trie, _ = trie.NewSecure(common.Hash{}, db) if data.CodeHash == nil {
return object data.CodeHash = emptyCodeHash
}
return &StateObject{db: db, address: address, data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), onDirty: onDirty}
} }
func (self *StateObject) MarkForDeletion() { // EncodeRLP implements rlp.Encoder.
self.remove = true func (c *StateObject) EncodeRLP(w io.Writer) error {
self.dirty = true return rlp.Encode(w, c.data)
}
// setError remembers the first non-nil error it is called with.
func (self *StateObject) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (self *StateObject) markSuicided() {
self.suicided = true
if self.onDirty != nil {
self.onDirty(self.Address())
self.onDirty = nil
}
if glog.V(logger.Core) { if glog.V(logger.Core) {
glog.Infof("%x: #%d %v X\n", self.Address(), self.nonce, self.balance) glog.Infof("%x: #%d %v X\n", self.Address(), self.Nonce(), self.Balance())
} }
} }
func (c *StateObject) getAddr(addr common.Hash) common.Hash { func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie {
var ret []byte if c.trie == nil {
rlp.DecodeBytes(c.trie.Get(addr[:]), &ret) var err error
return common.BytesToHash(ret) c.trie, err = trie.NewSecure(c.data.Root, db)
}
func (c *StateObject) setAddr(addr, value common.Hash) {
v, err := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
if err != nil { if err != nil {
// if RLPing failed we better panic and not fail silently. This would be considered a consensus issue c.trie, _ = trie.NewSecure(common.Hash{}, db)
panic(err) c.setError(fmt.Errorf("can't create storage trie: %v", err))
} }
c.trie.Update(addr[:], v) }
return c.trie
} }
func (self *StateObject) Storage() Storage { // GetState returns a value in account storage.
return self.storage func (self *StateObject) GetState(db trie.Database, key common.Hash) common.Hash {
} value, exists := self.cachedStorage[key]
if exists {
func (self *StateObject) GetState(key common.Hash) common.Hash { return value
value, exists := self.storage[key] }
if !exists { // Load from DB in case it is missing.
value = self.getAddr(key) if enc := self.getTrie(db).Get(key[:]); len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
}
value.SetBytes(content)
}
if (value != common.Hash{}) { if (value != common.Hash{}) {
self.storage[key] = value self.cachedStorage[key] = value
} }
}
return value return value
} }
func (self *StateObject) SetState(key, value common.Hash) { // SetState updates a value in account storage.
self.storage[key] = value func (self *StateObject) SetState(db trie.Database, key, value common.Hash) {
self.dirty = true self.db.journal = append(self.db.journal, storageChange{
account: &self.address,
key: key,
prevalue: self.GetState(db, key),
})
self.setState(key, value)
} }
// Update updates the current cached storage to the trie func (self *StateObject) setState(key, value common.Hash) {
func (self *StateObject) Update() { self.cachedStorage[key] = value
for key, value := range self.storage { self.dirtyStorage[key] = value
if self.onDirty != nil {
self.onDirty(self.Address())
self.onDirty = nil
}
}
// updateTrie writes cached storage modifications into the object's storage trie.
func (self *StateObject) updateTrie(db trie.Database) {
tr := self.getTrie(db)
for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key)
if (value == common.Hash{}) { if (value == common.Hash{}) {
self.trie.Delete(key[:]) tr.Delete(key[:])
continue continue
} }
self.setAddr(key, value) // Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
tr.Update(key[:], v)
} }
} }
// UpdateRoot sets the trie root to the current root hash of
func (self *StateObject) updateRoot(db trie.Database) {
self.updateTrie(db)
self.data.Root = self.trie.Hash()
}
// CommitTrie the storage trie of the object to dwb.
// This updates the trie root.
func (self *StateObject) CommitTrie(db trie.Database, dbw trie.DatabaseWriter) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.trie.CommitTo(dbw)
if err == nil {
self.data.Root = root
}
return err
}
func (c *StateObject) AddBalance(amount *big.Int) { func (c *StateObject) AddBalance(amount *big.Int) {
c.SetBalance(new(big.Int).Add(c.balance, amount)) if amount.Cmp(common.Big0) == 0 {
return
}
c.SetBalance(new(big.Int).Add(c.Balance(), amount))
if glog.V(logger.Core) { if glog.V(logger.Core) {
glog.Infof("%x: #%d %v (+ %v)\n", c.Address(), c.nonce, c.balance, amount) glog.Infof("%x: #%d %v (+ %v)\n", c.Address(), c.Nonce(), c.Balance(), amount)
} }
} }
func (c *StateObject) SubBalance(amount *big.Int) { func (c *StateObject) SubBalance(amount *big.Int) {
c.SetBalance(new(big.Int).Sub(c.balance, amount)) if amount.Cmp(common.Big0) == 0 {
return
}
c.SetBalance(new(big.Int).Sub(c.Balance(), amount))
if glog.V(logger.Core) { if glog.V(logger.Core) {
glog.Infof("%x: #%d %v (- %v)\n", c.Address(), c.nonce, c.balance, amount) glog.Infof("%x: #%d %v (- %v)\n", c.Address(), c.Nonce(), c.Balance(), amount)
} }
} }
func (c *StateObject) SetBalance(amount *big.Int) { func (self *StateObject) SetBalance(amount *big.Int) {
c.balance = amount self.db.journal = append(self.db.journal, balanceChange{
c.dirty = true account: &self.address,
prev: new(big.Int).Set(self.data.Balance),
})
self.setBalance(amount)
} }
func (c *StateObject) St() Storage { func (self *StateObject) setBalance(amount *big.Int) {
return c.storage self.data.Balance = amount
if self.onDirty != nil {
self.onDirty(self.Address())
self.onDirty = nil
}
} }
// Return the gas back to the origin. Used by the Virtual machine or Closures // Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *StateObject) ReturnGas(gas, price *big.Int) {} func (c *StateObject) ReturnGas(gas, price *big.Int) {}
func (self *StateObject) Copy() *StateObject { func (self *StateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *StateObject {
stateObject := NewStateObject(self.Address(), self.db) stateObject := newObject(db, self.address, self.data, onDirty)
stateObject.balance.Set(self.balance)
stateObject.codeHash = common.CopyBytes(self.codeHash)
stateObject.nonce = self.nonce
stateObject.trie = self.trie stateObject.trie = self.trie
stateObject.code = common.CopyBytes(self.code) stateObject.code = self.code
stateObject.initCode = common.CopyBytes(self.initCode) stateObject.dirtyStorage = self.dirtyStorage.Copy()
stateObject.storage = self.storage.Copy() stateObject.cachedStorage = self.dirtyStorage.Copy()
stateObject.remove = self.remove stateObject.suicided = self.suicided
stateObject.dirty = self.dirty stateObject.dirtyCode = self.dirtyCode
stateObject.deleted = self.deleted stateObject.deleted = self.deleted
return stateObject return stateObject
} }
@ -201,40 +278,73 @@ func (self *StateObject) Copy() *StateObject {
// Attribute accessors // Attribute accessors
// //
func (self *StateObject) Balance() *big.Int {
return self.balance
}
// Returns the address of the contract/account // Returns the address of the contract/account
func (c *StateObject) Address() common.Address { func (c *StateObject) Address() common.Address {
return c.address return c.address
} }
func (self *StateObject) Trie() *trie.SecureTrie { // Code returns the contract code associated with this object, if any.
return self.trie func (self *StateObject) Code(db trie.Database) []byte {
} if self.code != nil {
func (self *StateObject) Root() []byte {
return self.trie.Root()
}
func (self *StateObject) Code() []byte {
return self.code return self.code
}
if bytes.Equal(self.CodeHash(), emptyCodeHash) {
return nil
}
code, err := db.Get(self.CodeHash())
if err != nil {
self.setError(fmt.Errorf("can't load code hash %x: %v", self.CodeHash(), err))
}
self.code = code
return code
} }
func (self *StateObject) SetCode(code []byte) { func (self *StateObject) SetCode(codeHash common.Hash, code []byte) {
prevcode := self.Code(self.db.db)
self.db.journal = append(self.db.journal, codeChange{
account: &self.address,
prevhash: self.CodeHash(),
prevcode: prevcode,
})
self.setCode(codeHash, code)
}
func (self *StateObject) setCode(codeHash common.Hash, code []byte) {
self.code = code self.code = code
self.codeHash = crypto.Keccak256(code) self.data.CodeHash = codeHash[:]
self.dirty = true self.dirtyCode = true
if self.onDirty != nil {
self.onDirty(self.Address())
self.onDirty = nil
}
} }
func (self *StateObject) SetNonce(nonce uint64) { func (self *StateObject) SetNonce(nonce uint64) {
self.nonce = nonce self.db.journal = append(self.db.journal, nonceChange{
self.dirty = true account: &self.address,
prev: self.data.Nonce,
})
self.setNonce(nonce)
}
func (self *StateObject) setNonce(nonce uint64) {
self.data.Nonce = nonce
if self.onDirty != nil {
self.onDirty(self.Address())
self.onDirty = nil
}
}
func (self *StateObject) CodeHash() []byte {
return self.data.CodeHash
}
func (self *StateObject) Balance() *big.Int {
return self.data.Balance
} }
func (self *StateObject) Nonce() uint64 { func (self *StateObject) Nonce() uint64 {
return self.nonce return self.data.Nonce
} }
// Never called, but must be present to allow StateObject to be used // Never called, but must be present to allow StateObject to be used
@ -246,52 +356,16 @@ func (self *StateObject) Value() *big.Int {
func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) {
// When iterating over the storage check the cache first // When iterating over the storage check the cache first
for h, value := range self.storage { for h, value := range self.cachedStorage {
cb(h, value) cb(h, value)
} }
it := self.trie.Iterator() it := self.getTrie(self.db.db).Iterator()
for it.Next() { for it.Next() {
// ignore cached values // ignore cached values
key := common.BytesToHash(self.trie.GetKey(it.Key)) key := common.BytesToHash(self.trie.GetKey(it.Key))
if _, ok := self.storage[key]; !ok { if _, ok := self.cachedStorage[key]; !ok {
cb(key, common.BytesToHash(it.Value)) cb(key, common.BytesToHash(it.Value))
} }
} }
} }
type extStateObject struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}
// EncodeRLP implements rlp.Encoder.
func (c *StateObject) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{c.nonce, c.balance, c.Root(), c.codeHash})
}
// DecodeObject decodes an RLP-encoded state object.
func DecodeObject(address common.Address, db trie.Database, data []byte) (*StateObject, error) {
var (
obj = &StateObject{address: address, db: db, storage: make(Storage)}
ext extStateObject
err error
)
if err = rlp.DecodeBytes(data, &ext); err != nil {
return nil, err
}
if obj.trie, err = trie.NewSecure(ext.Root, db); err != nil {
return nil, err
}
if !bytes.Equal(ext.CodeHash, emptyCodeHash) {
if obj.code, err = db.Get(ext.CodeHash); err != nil {
return nil, fmt.Errorf("can't get code for hash %x: %v", ext.CodeHash, err)
}
}
obj.nonce = ext.Nonce
obj.balance = ext.Balance
obj.codeHash = ext.CodeHash
return obj, nil
}

View file

@ -24,6 +24,7 @@ import (
checker "gopkg.in/check.v1" checker "gopkg.in/check.v1"
"github.com/expanse-project/go-expanse/common" "github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/crypto"
"github.com/expanse-project/go-expanse/ethdb" "github.com/expanse-project/go-expanse/ethdb"
) )
@ -40,13 +41,13 @@ func (s *StateSuite) TestDump(c *checker.C) {
obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01}))
obj1.AddBalance(big.NewInt(22)) obj1.AddBalance(big.NewInt(22))
obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02})) obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02}))
obj2.SetCode([]byte{3, 3, 3, 3, 3, 3, 3}) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02})) obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02}))
obj3.SetBalance(big.NewInt(44)) obj3.SetBalance(big.NewInt(44))
// write some of them to the trie // write some of them to the trie
s.state.UpdateStateObject(obj1) s.state.updateStateObject(obj1)
s.state.UpdateStateObject(obj2) s.state.updateStateObject(obj2)
s.state.Commit() s.state.Commit()
// check that dump contains the state objects that are in trie // check that dump contains the state objects that are in trie
@ -115,12 +116,12 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
// set initial state object value // set initial state object value
s.state.SetState(stateobjaddr, storageaddr, data1) s.state.SetState(stateobjaddr, storageaddr, data1)
// get snapshot of current state // get snapshot of current state
snapshot := s.state.Copy() snapshot := s.state.Snapshot()
// set new state object value // set new state object value
s.state.SetState(stateobjaddr, storageaddr, data2) s.state.SetState(stateobjaddr, storageaddr, data2)
// restore snapshot // restore snapshot
s.state.Set(snapshot) s.state.RevertToSnapshot(snapshot)
// get state storage value // get state storage value
res := s.state.GetState(stateobjaddr, storageaddr) res := s.state.GetState(stateobjaddr, storageaddr)
@ -128,6 +129,12 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
c.Assert(data1, checker.DeepEquals, res) c.Assert(data1, checker.DeepEquals, res)
} }
func TestSnapshotEmpty(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
state, _ := New(common.Hash{}, db)
state.RevertToSnapshot(state.Snapshot())
}
// use testing instead of checker because checker does not support // use testing instead of checker because checker does not support
// printing/logging in tests (-check.vv does not work) // printing/logging in tests (-check.vv does not work)
func TestSnapshot2(t *testing.T) { func TestSnapshot2(t *testing.T) {
@ -146,80 +153,85 @@ func TestSnapshot2(t *testing.T) {
// db, trie are already non-empty values // db, trie are already non-empty values
so0 := state.GetStateObject(stateobjaddr0) so0 := state.GetStateObject(stateobjaddr0)
so0.balance = big.NewInt(42) so0.SetBalance(big.NewInt(42))
so0.nonce = 43 so0.SetNonce(43)
so0.SetCode([]byte{'c', 'a', 'f', 'e'}) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.remove = true so0.suicided = false
so0.deleted = false so0.deleted = false
so0.dirty = false state.setStateObject(so0)
state.SetStateObject(so0)
root, _ := state.Commit()
state.Reset(root)
// and one with deleted == true // and one with deleted == true
so1 := state.GetStateObject(stateobjaddr1) so1 := state.GetStateObject(stateobjaddr1)
so1.balance = big.NewInt(52) so1.SetBalance(big.NewInt(52))
so1.nonce = 53 so1.SetNonce(53)
so1.SetCode([]byte{'c', 'a', 'f', 'e', '2'}) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.remove = true so1.suicided = true
so1.deleted = true so1.deleted = true
so1.dirty = true state.setStateObject(so1)
state.SetStateObject(so1)
so1 = state.GetStateObject(stateobjaddr1) so1 = state.GetStateObject(stateobjaddr1)
if so1 != nil { if so1 != nil {
t.Fatalf("deleted object not nil when getting") t.Fatalf("deleted object not nil when getting")
} }
snapshot := state.Copy() snapshot := state.Snapshot()
state.Set(snapshot) state.RevertToSnapshot(snapshot)
so0Restored := state.GetStateObject(stateobjaddr0) so0Restored := state.GetStateObject(stateobjaddr0)
so1Restored := state.GetStateObject(stateobjaddr1) // Update lazily-loaded values before comparing.
so0Restored.GetState(db, storageaddr)
so0Restored.Code(db)
// non-deleted is equal (restored) // non-deleted is equal (restored)
compareStateObjects(so0Restored, so0, t) compareStateObjects(so0Restored, so0, t)
// deleted should be nil, both before and after restore of state copy // deleted should be nil, both before and after restore of state copy
so1Restored := state.GetStateObject(stateobjaddr1)
if so1Restored != nil { if so1Restored != nil {
t.Fatalf("deleted object not nil after restoring snapshot") t.Fatalf("deleted object not nil after restoring snapshot: %+v", so1Restored)
} }
} }
func compareStateObjects(so0, so1 *StateObject, t *testing.T) { func compareStateObjects(so0, so1 *StateObject, t *testing.T) {
if so0.address != so1.address { if so0.Address() != so1.Address() {
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address) t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
} }
if so0.balance.Cmp(so1.balance) != 0 { if so0.Balance().Cmp(so1.Balance()) != 0 {
t.Fatalf("Balance mismatch: have %v, want %v", so0.balance, so1.balance) t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance())
} }
if so0.nonce != so1.nonce { if so0.Nonce() != so1.Nonce() {
t.Fatalf("Nonce mismatch: have %v, want %v", so0.nonce, so1.nonce) t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce())
} }
if !bytes.Equal(so0.codeHash, so1.codeHash) { if so0.data.Root != so1.data.Root {
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.codeHash, so1.codeHash) t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:])
}
if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) {
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash())
} }
if !bytes.Equal(so0.code, so1.code) { if !bytes.Equal(so0.code, so1.code) {
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code) t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
} }
if !bytes.Equal(so0.initCode, so1.initCode) {
t.Fatalf("InitCode mismatch: have %v, want %v", so0.initCode, so1.initCode)
}
for k, v := range so1.storage { if len(so1.cachedStorage) != len(so0.cachedStorage) {
if so0.storage[k] != v { t.Errorf("Storage size mismatch: have %d, want %d", len(so1.cachedStorage), len(so0.cachedStorage))
t.Fatalf("Storage key %s mismatch: have %v, want %v", k, so0.storage[k], v) }
for k, v := range so1.cachedStorage {
if so0.cachedStorage[k] != v {
t.Errorf("Storage key %x mismatch: have %v, want %v", k, so0.cachedStorage[k], v)
} }
} }
for k, v := range so0.storage { for k, v := range so0.cachedStorage {
if so1.storage[k] != v { if so1.cachedStorage[k] != v {
t.Fatalf("Storage key %s mismatch: have %v, want none.", k, v) t.Errorf("Storage key %x mismatch: have %v, want none.", k, v)
} }
} }
if so0.remove != so1.remove { if so0.suicided != so1.suicided {
t.Fatalf("Remove mismatch: have %v, want %v", so0.remove, so1.remove) t.Fatalf("suicided mismatch: have %v, want %v", so0.suicided, so1.suicided)
} }
if so0.deleted != so1.deleted { if so0.deleted != so1.deleted {
t.Fatalf("Deleted mismatch: have %v, want %v", so0.deleted, so1.deleted) t.Fatalf("Deleted mismatch: have %v, want %v", so0.deleted, so1.deleted)
} }
if so0.dirty != so1.dirty {
t.Fatalf("Dirty mismatch: have %v, want %v", so0.dirty, so1.dirty)
}
} }

View file

@ -20,22 +20,39 @@ package state
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"sort"
"sync"
"github.com/expanse-project/go-expanse/common" "github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/core/vm" "github.com/expanse-project/go-expanse/core/vm"
"github.com/expanse-project/go-expanse/crypto"
"github.com/expanse-project/go-expanse/ethdb" "github.com/expanse-project/go-expanse/ethdb"
"github.com/expanse-project/go-expanse/logger" "github.com/expanse-project/go-expanse/logger"
"github.com/expanse-project/go-expanse/logger/glog" "github.com/expanse-project/go-expanse/logger/glog"
"github.com/expanse-project/go-expanse/rlp" "github.com/expanse-project/go-expanse/rlp"
"github.com/expanse-project/go-expanse/trie" "github.com/expanse-project/go-expanse/trie"
lru "github.com/hashicorp/golang-lru"
) )
// The starting nonce determines the default nonce when new accounts are being // The starting nonce determines the default nonce when new accounts are being
// created. // created.
var StartingNonce uint64 var StartingNonce uint64
// StateDBs within the expanse protocol are used to store anything const (
// Number of past tries to keep. The arbitrarily chosen value here
// is max uncle depth + 1.
maxTrieCacheLength = 8
// Number of codehash->size associations to keep.
codeSizeCacheSize = 100000
)
type revision struct {
id int
journalIndex int
}
// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing // within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve: // nested states. It's the general query interface to retrieve:
// * Contracts // * Contracts
@ -43,15 +60,28 @@ var StartingNonce uint64
type StateDB struct { type StateDB struct {
db ethdb.Database db ethdb.Database
trie *trie.SecureTrie trie *trie.SecureTrie
pastTries []*trie.SecureTrie
codeSizeCache *lru.Cache
stateObjects map[string]*StateObject // This map holds 'live' objects, which will get modified while processing a state transition.
stateObjects map[common.Address]*StateObject
stateObjectsDirty map[common.Address]struct{}
// The refund counter, also used by state transitioning.
refund *big.Int refund *big.Int
thash, bhash common.Hash thash, bhash common.Hash
txIndex int txIndex int
logs map[common.Hash]vm.Logs logs map[common.Hash]vm.Logs
logSize uint logSize uint
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal journal
validRevisions []revision
nextRevisionId int
lock sync.Mutex
} }
// Create a new state from a given trie // Create a new state from a given trie
@ -60,10 +90,34 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
csc, _ := lru.New(codeSizeCacheSize)
return &StateDB{ return &StateDB{
db: db, db: db,
trie: tr, trie: tr,
stateObjects: make(map[string]*StateObject), codeSizeCache: csc,
stateObjects: make(map[common.Address]*StateObject),
stateObjectsDirty: make(map[common.Address]struct{}),
refund: new(big.Int),
logs: make(map[common.Hash]vm.Logs),
}, nil
}
// New creates a new statedb by reusing any journalled tries to avoid costly
// disk io.
func (self *StateDB) New(root common.Hash) (*StateDB, error) {
self.lock.Lock()
defer self.lock.Unlock()
tr, err := self.openTrie(root)
if err != nil {
return nil, err
}
return &StateDB{
db: self.db,
trie: tr,
codeSizeCache: self.codeSizeCache,
stateObjects: make(map[common.Address]*StateObject),
stateObjectsDirty: make(map[common.Address]struct{}),
refund: new(big.Int), refund: new(big.Int),
logs: make(map[common.Hash]vm.Logs), logs: make(map[common.Hash]vm.Logs),
}, nil }, nil
@ -72,25 +126,50 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
// Reset clears out all emphemeral state objects from the state db, but keeps // Reset clears out all emphemeral state objects from the state db, but keeps
// the underlying state trie to avoid reloading data for the next operations. // the underlying state trie to avoid reloading data for the next operations.
func (self *StateDB) Reset(root common.Hash) error { func (self *StateDB) Reset(root common.Hash) error {
var ( self.lock.Lock()
err error defer self.lock.Unlock()
tr = self.trie
) tr, err := self.openTrie(root)
if self.trie.Hash() != root { if err != nil {
if tr, err = trie.NewSecure(root, self.db); err != nil {
return err return err
} }
} self.trie = tr
*self = StateDB{ self.stateObjects = make(map[common.Address]*StateObject)
db: self.db, self.stateObjectsDirty = make(map[common.Address]struct{})
trie: tr, self.thash = common.Hash{}
stateObjects: make(map[string]*StateObject), self.bhash = common.Hash{}
refund: new(big.Int), self.txIndex = 0
logs: make(map[common.Hash]vm.Logs), self.logs = make(map[common.Hash]vm.Logs)
} self.logSize = 0
self.clearJournalAndRefund()
return nil return nil
} }
// openTrie creates a trie. It uses an existing trie if one is available
// from the journal if available.
func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) {
for i := len(self.pastTries) - 1; i >= 0; i-- {
if self.pastTries[i].Hash() == root {
tr := *self.pastTries[i]
return &tr, nil
}
}
return trie.NewSecure(root, self.db)
}
func (self *StateDB) pushTrie(t *trie.SecureTrie) {
self.lock.Lock()
defer self.lock.Unlock()
if len(self.pastTries) >= maxTrieCacheLength {
copy(self.pastTries, self.pastTries[1:])
self.pastTries[len(self.pastTries)-1] = t
} else {
self.pastTries = append(self.pastTries, t)
}
}
func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) { func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) {
self.thash = thash self.thash = thash
self.bhash = bhash self.bhash = bhash
@ -98,6 +177,8 @@ func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) {
} }
func (self *StateDB) AddLog(log *vm.Log) { func (self *StateDB) AddLog(log *vm.Log) {
self.journal = append(self.journal, addLogChange{txhash: self.thash})
log.TxHash = self.thash log.TxHash = self.thash
log.BlockHash = self.bhash log.BlockHash = self.bhash
log.TxIndex = uint(self.txIndex) log.TxIndex = uint(self.txIndex)
@ -119,13 +200,12 @@ func (self *StateDB) Logs() vm.Logs {
} }
func (self *StateDB) AddRefund(gas *big.Int) { func (self *StateDB) AddRefund(gas *big.Int) {
self.journal = append(self.journal, refundChange{prev: new(big.Int).Set(self.refund)})
self.refund.Add(self.refund, gas) self.refund.Add(self.refund, gas)
} }
func (self *StateDB) HasAccount(addr common.Address) bool { // Exist reports whether the given account address exists in the state.
return self.GetStateObject(addr) != nil // Notably this also returns true for suicided accounts.
}
func (self *StateDB) Exist(addr common.Address) bool { func (self *StateDB) Exist(addr common.Address) bool {
return self.GetStateObject(addr) != nil return self.GetStateObject(addr) != nil
} }
@ -138,16 +218,15 @@ func (self *StateDB) GetAccount(addr common.Address) vm.Account {
func (self *StateDB) GetBalance(addr common.Address) *big.Int { func (self *StateDB) GetBalance(addr common.Address) *big.Int {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject != nil { if stateObject != nil {
return stateObject.balance return stateObject.Balance()
} }
return common.Big0 return common.Big0
} }
func (self *StateDB) GetNonce(addr common.Address) uint64 { func (self *StateDB) GetNonce(addr common.Address) uint64 {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject != nil { if stateObject != nil {
return stateObject.nonce return stateObject.Nonce()
} }
return StartingNonce return StartingNonce
@ -156,25 +235,50 @@ func (self *StateDB) GetNonce(addr common.Address) uint64 {
func (self *StateDB) GetCode(addr common.Address) []byte { func (self *StateDB) GetCode(addr common.Address) []byte {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject != nil { if stateObject != nil {
return stateObject.code code := stateObject.Code(self.db)
key := common.BytesToHash(stateObject.CodeHash())
self.codeSizeCache.Add(key, len(code))
return code
} }
return nil return nil
} }
func (self *StateDB) GetCodeSize(addr common.Address) int {
stateObject := self.GetStateObject(addr)
if stateObject == nil {
return 0
}
key := common.BytesToHash(stateObject.CodeHash())
if cached, ok := self.codeSizeCache.Get(key); ok {
return cached.(int)
}
size := len(stateObject.Code(self.db))
if stateObject.dbErr == nil {
self.codeSizeCache.Add(key, size)
}
return size
}
func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
stateObject := self.GetStateObject(addr)
if stateObject == nil {
return common.Hash{}
}
return common.BytesToHash(stateObject.CodeHash())
}
func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash { func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash {
stateObject := self.GetStateObject(a) stateObject := self.GetStateObject(a)
if stateObject != nil { if stateObject != nil {
return stateObject.GetState(b) return stateObject.GetState(self.db, b)
} }
return common.Hash{} return common.Hash{}
} }
func (self *StateDB) IsDeleted(addr common.Address) bool { func (self *StateDB) HasSuicided(addr common.Address) bool {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject != nil { if stateObject != nil {
return stateObject.remove return stateObject.suicided
} }
return false return false
} }
@ -190,6 +294,13 @@ func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
} }
} }
func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) {
stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetBalance(amount)
}
}
func (self *StateDB) SetNonce(addr common.Address, nonce uint64) { func (self *StateDB) SetNonce(addr common.Address, nonce uint64) {
stateObject := self.GetOrNewStateObject(addr) stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -200,35 +311,43 @@ func (self *StateDB) SetNonce(addr common.Address, nonce uint64) {
func (self *StateDB) SetCode(addr common.Address, code []byte) { func (self *StateDB) SetCode(addr common.Address, code []byte) {
stateObject := self.GetOrNewStateObject(addr) stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.SetCode(code) stateObject.SetCode(crypto.Keccak256Hash(code), code)
} }
} }
func (self *StateDB) SetState(addr common.Address, key common.Hash, value common.Hash) { func (self *StateDB) SetState(addr common.Address, key common.Hash, value common.Hash) {
stateObject := self.GetOrNewStateObject(addr) stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.SetState(key, value) stateObject.SetState(self.db, key, value)
} }
} }
func (self *StateDB) Delete(addr common.Address) bool { // Suicide marks the given account as suicided.
// This clears the account balance.
//
// The account's state object is still available until the state is committed,
// GetStateObject will return a non-nil account after Suicide.
func (self *StateDB) Suicide(addr common.Address) bool {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject != nil { if stateObject == nil {
stateObject.MarkForDeletion()
stateObject.balance = new(big.Int)
return true
}
return false return false
}
self.journal = append(self.journal, suicideChange{
account: &addr,
prev: stateObject.suicided,
prevbalance: new(big.Int).Set(stateObject.Balance()),
})
stateObject.markSuicided()
stateObject.data.Balance = new(big.Int)
return true
} }
// //
// Setting, updating & deleting state object methods // Setting, updating & deleting state object methods
// //
// Update the given state object and apply it to state trie // updateStateObject writes the given object to the trie.
func (self *StateDB) UpdateStateObject(stateObject *StateObject) { func (self *StateDB) updateStateObject(stateObject *StateObject) {
addr := stateObject.Address() addr := stateObject.Address()
data, err := rlp.EncodeToBytes(stateObject) data, err := rlp.EncodeToBytes(stateObject)
if err != nil { if err != nil {
@ -237,117 +356,156 @@ func (self *StateDB) UpdateStateObject(stateObject *StateObject) {
self.trie.Update(addr[:], data) self.trie.Update(addr[:], data)
} }
// Delete the given state object and delete it from the state trie // deleteStateObject removes the given object from the state trie.
func (self *StateDB) DeleteStateObject(stateObject *StateObject) { func (self *StateDB) deleteStateObject(stateObject *StateObject) {
stateObject.deleted = true stateObject.deleted = true
addr := stateObject.Address() addr := stateObject.Address()
self.trie.Delete(addr[:]) self.trie.Delete(addr[:])
//delete(self.stateObjects, addr.Str())
} }
// Retrieve a state object given my the address. Nil if not found // Retrieve a state object given my the address. Returns nil if not found.
func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObject) { func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObject) {
stateObject = self.stateObjects[addr.Str()] // Prefer 'live' objects.
if stateObject != nil { if obj := self.stateObjects[addr]; obj != nil {
if stateObject.deleted { if obj.deleted {
stateObject = nil
}
return stateObject
}
data := self.trie.Get(addr[:])
if len(data) == 0 {
return nil return nil
} }
stateObject, err := DecodeObject(addr, self.db, data) return obj
if err != nil { }
// Load the object from the database.
enc := self.trie.Get(addr[:])
if len(enc) == 0 {
return nil
}
var data Account
if err := rlp.DecodeBytes(enc, &data); err != nil {
glog.Errorf("can't decode object at %x: %v", addr[:], err) glog.Errorf("can't decode object at %x: %v", addr[:], err)
return nil return nil
} }
self.SetStateObject(stateObject) // Insert into the live set.
return stateObject obj := newObject(self, addr, data, self.MarkStateObjectDirty)
self.setStateObject(obj)
return obj
} }
func (self *StateDB) SetStateObject(object *StateObject) { func (self *StateDB) setStateObject(object *StateObject) {
self.stateObjects[object.Address().Str()] = object self.stateObjects[object.Address()] = object
} }
// Retrieve a state object or create a new state object if nil // Retrieve a state object or create a new state object if nil
func (self *StateDB) GetOrNewStateObject(addr common.Address) *StateObject { func (self *StateDB) GetOrNewStateObject(addr common.Address) *StateObject {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject == nil || stateObject.deleted { if stateObject == nil || stateObject.deleted {
stateObject = self.CreateStateObject(addr) stateObject, _ = self.createObject(addr)
} }
return stateObject return stateObject
} }
// NewStateObject create a state object whether it exist in the trie or not // MarkStateObjectDirty adds the specified object to the dirty map to avoid costly
func (self *StateDB) newStateObject(addr common.Address) *StateObject { // state object cache iteration to find a handful of modified ones.
func (self *StateDB) MarkStateObjectDirty(addr common.Address) {
self.stateObjectsDirty[addr] = struct{}{}
}
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
func (self *StateDB) createObject(addr common.Address) (newobj, prev *StateObject) {
prev = self.GetStateObject(addr)
newobj = newObject(self, addr, Account{}, self.MarkStateObjectDirty)
newobj.setNonce(StartingNonce) // sets the object to dirty
if prev == nil {
if glog.V(logger.Core) { if glog.V(logger.Core) {
glog.Infof("(+) %x\n", addr) glog.Infof("(+) %x\n", addr)
} }
self.journal = append(self.journal, createObjectChange{account: &addr})
stateObject := NewStateObject(addr, self.db) } else {
stateObject.SetNonce(StartingNonce) self.journal = append(self.journal, resetObjectChange{prev: prev})
self.stateObjects[addr.Str()] = stateObject
return stateObject
}
// Creates creates a new state object and takes ownership. This is different from "NewStateObject"
func (self *StateDB) CreateStateObject(addr common.Address) *StateObject {
// Get previous (if any)
so := self.GetStateObject(addr)
// Create a new one
newSo := self.newStateObject(addr)
// If it existed set the balance to the new account
if so != nil {
newSo.balance = so.balance
} }
self.setStateObject(newobj)
return newSo return newobj, prev
} }
// CreateAccount explicitly creates a state object. If a state object with the address
// already exists the balance is carried over to the new account.
//
// CreateAccount is called during the EVM CREATE operation. The situation might arise that
// a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (self *StateDB) CreateAccount(addr common.Address) vm.Account { func (self *StateDB) CreateAccount(addr common.Address) vm.Account {
return self.CreateStateObject(addr) new, prev := self.createObject(addr)
if prev != nil {
new.setBalance(prev.data.Balance)
}
return new
} }
// // Copy creates a deep, independent copy of the state.
// Setting, copying of the state methods // Snapshots of the copied state cannot be applied to the copy.
//
func (self *StateDB) Copy() *StateDB { func (self *StateDB) Copy() *StateDB {
// ignore error - we assume state-to-be-copied always exists self.lock.Lock()
state, _ := New(common.Hash{}, self.db) defer self.lock.Unlock()
state.trie = self.trie
for k, stateObject := range self.stateObjects { // Copy all the basic fields, initialize the memory ones
state.stateObjects[k] = stateObject.Copy() state := &StateDB{
db: self.db,
trie: self.trie,
pastTries: self.pastTries,
codeSizeCache: self.codeSizeCache,
stateObjects: make(map[common.Address]*StateObject, len(self.stateObjectsDirty)),
stateObjectsDirty: make(map[common.Address]struct{}, len(self.stateObjectsDirty)),
refund: new(big.Int).Set(self.refund),
logs: make(map[common.Hash]vm.Logs, len(self.logs)),
logSize: self.logSize,
}
// Copy the dirty states and logs
for addr, _ := range self.stateObjectsDirty {
state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state, state.MarkStateObjectDirty)
state.stateObjectsDirty[addr] = struct{}{}
} }
state.refund.Set(self.refund)
for hash, logs := range self.logs { for hash, logs := range self.logs {
state.logs[hash] = make(vm.Logs, len(logs)) state.logs[hash] = make(vm.Logs, len(logs))
copy(state.logs[hash], logs) copy(state.logs[hash], logs)
} }
state.logSize = self.logSize
return state return state
} }
func (self *StateDB) Set(state *StateDB) { // Snapshot returns an identifier for the current revision of the state.
self.trie = state.trie func (self *StateDB) Snapshot() int {
self.stateObjects = state.stateObjects id := self.nextRevisionId
self.nextRevisionId++
self.refund = state.refund self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)})
self.logs = state.logs return id
self.logSize = state.logSize
} }
// RevertToSnapshot reverts all state changes made since the given revision.
func (self *StateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots.
idx := sort.Search(len(self.validRevisions), func(i int) bool {
return self.validRevisions[i].id >= revid
})
if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
snapshot := self.validRevisions[idx].journalIndex
// Replay the journal to undo changes.
for i := len(self.journal) - 1; i >= snapshot; i-- {
self.journal[i].undo(self)
}
self.journal = self.journal[:snapshot]
// Remove invalidated snapshots from the stack.
self.validRevisions = self.validRevisions[:idx]
}
// GetRefund returns the current value of the refund counter.
// The return value must not be modified by the caller and will become
// invalid at the next call to AddRefund.
func (self *StateDB) GetRefund() *big.Int { func (self *StateDB) GetRefund() *big.Int {
return self.refund return self.refund
} }
@ -356,18 +514,17 @@ func (self *StateDB) GetRefund() *big.Int {
// It is called in between transactions to get the root hash that // It is called in between transactions to get the root hash that
// goes into transaction receipts. // goes into transaction receipts.
func (s *StateDB) IntermediateRoot() common.Hash { func (s *StateDB) IntermediateRoot() common.Hash {
s.refund = new(big.Int) for addr, _ := range s.stateObjectsDirty {
for _, stateObject := range s.stateObjects { stateObject := s.stateObjects[addr]
if stateObject.dirty { if stateObject.suicided {
if stateObject.remove { s.deleteStateObject(stateObject)
s.DeleteStateObject(stateObject)
} else { } else {
stateObject.Update() stateObject.updateRoot(s.db)
s.UpdateStateObject(stateObject) s.updateStateObject(stateObject)
}
stateObject.dirty = false
} }
} }
// Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund()
return s.trie.Hash() return s.trie.Hash()
} }
@ -377,18 +534,18 @@ func (s *StateDB) IntermediateRoot() common.Hash {
// DeleteSuicides should not be used for consensus related updates // DeleteSuicides should not be used for consensus related updates
// under any circumstances. // under any circumstances.
func (s *StateDB) DeleteSuicides() { func (s *StateDB) DeleteSuicides() {
// Reset refund so that any used-gas calculations can use // Reset refund so that any used-gas calculations can use this method.
// this method. s.clearJournalAndRefund()
s.refund = new(big.Int)
for _, stateObject := range s.stateObjects { for addr, _ := range s.stateObjectsDirty {
if stateObject.dirty { stateObject := s.stateObjects[addr]
// If the object has been removed by a suicide // If the object has been removed by a suicide
// flag the object as deleted. // flag the object as deleted.
if stateObject.remove { if stateObject.suicided {
stateObject.deleted = true stateObject.deleted = true
} }
stateObject.dirty = false delete(s.stateObjectsDirty, addr)
}
} }
} }
@ -407,46 +564,42 @@ func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) {
return root, batch return root, batch
} }
func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) { func (s *StateDB) clearJournalAndRefund() {
s.journal = nil
s.validRevisions = s.validRevisions[:0]
s.refund = new(big.Int) s.refund = new(big.Int)
}
for _, stateObject := range s.stateObjects { func (s *StateDB) commit(dbw trie.DatabaseWriter) (root common.Hash, err error) {
if stateObject.remove { defer s.clearJournalAndRefund()
// Commit objects to the trie.
for addr, stateObject := range s.stateObjects {
if stateObject.suicided {
// If the object has been removed, don't bother syncing it // If the object has been removed, don't bother syncing it
// and just mark it for deletion in the trie. // and just mark it for deletion in the trie.
s.DeleteStateObject(stateObject) s.deleteStateObject(stateObject)
} else { } else if _, ok := s.stateObjectsDirty[addr]; ok {
// Write any contract code associated with the state object // Write any contract code associated with the state object
if len(stateObject.code) > 0 { if stateObject.code != nil && stateObject.dirtyCode {
if err := db.Put(stateObject.codeHash, stateObject.code); err != nil { if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
stateObject.dirtyCode = false
} }
// Write any storage changes in the state object to its trie. // Write any storage changes in the state object to its storage trie.
stateObject.Update() if err := stateObject.CommitTrie(s.db, dbw); err != nil {
// Commit the trie of the object to the batch.
// This updates the trie root internally, so
// getting the root hash of the storage trie
// through UpdateStateObject is fast.
if _, err := stateObject.trie.CommitTo(db); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Update the object in the account trie. // Update the object in the main account trie.
s.UpdateStateObject(stateObject) s.updateStateObject(stateObject)
} }
stateObject.dirty = false delete(s.stateObjectsDirty, addr)
} }
return s.trie.CommitTo(db) // Write trie changes.
} root, err = s.trie.CommitTo(dbw)
if err == nil {
func (self *StateDB) Refunds() *big.Int { s.pushTrie(s.trie)
return self.refund
}
// Debug stuff
func (self *StateDB) CreateOutputForDiff() {
for _, stateObject := range self.stateObjects {
stateObject.CreateOutputForDiff()
} }
return root, err
} }

View file

@ -17,10 +17,19 @@
package state package state
import ( import (
"bytes"
"encoding/binary"
"fmt"
"math"
"math/big" "math/big"
"math/rand"
"reflect"
"strings"
"testing" "testing"
"testing/quick"
"github.com/expanse-project/go-expanse/common" "github.com/expanse-project/go-expanse/common"
"github.com/expanse-project/go-expanse/core/vm"
"github.com/expanse-project/go-expanse/ethdb" "github.com/expanse-project/go-expanse/ethdb"
) )
@ -33,16 +42,16 @@ func TestUpdateLeaks(t *testing.T) {
// Update it with some accounts // Update it with some accounts
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) addr := common.BytesToAddress([]byte{i})
obj.AddBalance(big.NewInt(int64(11 * i))) state.AddBalance(addr, big.NewInt(int64(11*i)))
obj.SetNonce(uint64(42 * i)) state.SetNonce(addr, uint64(42*i))
if i%2 == 0 { if i%2 == 0 {
obj.SetState(common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
} }
if i%3 == 0 { if i%3 == 0 {
obj.SetCode([]byte{i, i, i, i, i}) state.SetCode(addr, []byte{i, i, i, i, i})
} }
state.UpdateStateObject(obj) state.IntermediateRoot()
} }
// Ensure that no data was leaked into the database // Ensure that no data was leaked into the database
for _, key := range db.Keys() { for _, key := range db.Keys() {
@ -60,51 +69,38 @@ func TestIntermediateLeaks(t *testing.T) {
transState, _ := New(common.Hash{}, transDb) transState, _ := New(common.Hash{}, transDb)
finalState, _ := New(common.Hash{}, finalDb) finalState, _ := New(common.Hash{}, finalDb)
// Update the states with some objects modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)))
state.SetNonce(addr, uint64(42*i+tweak))
if i%2 == 0 {
state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{})
state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak})
}
if i%3 == 0 {
state.SetCode(addr, []byte{i, i, i, i, i, tweak})
}
}
// Modify the transient state.
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
// Create a new state object with some data into the transition database modify(transState, common.Address{byte(i)}, i, 0)
obj := transState.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.SetBalance(big.NewInt(int64(11 * i)))
obj.SetNonce(uint64(42 * i))
if i%2 == 0 {
obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.BytesToHash([]byte{i, i, i, i, 0}))
} }
if i%3 == 0 { // Write modifications to trie.
obj.SetCode([]byte{i, i, i, i, i, 0}) transState.IntermediateRoot()
}
transState.UpdateStateObject(obj)
// Overwrite all the data with new values in the transition database // Overwrite all the data with new values in the transient database.
obj.SetBalance(big.NewInt(int64(11*i + 1))) for i := byte(0); i < 255; i++ {
obj.SetNonce(uint64(42*i + 1)) modify(transState, common.Address{byte(i)}, i, 99)
if i%2 == 0 { modify(finalState, common.Address{byte(i)}, i, 99)
obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.Hash{})
obj.SetState(common.BytesToHash([]byte{i, i, i, 1}), common.BytesToHash([]byte{i, i, i, i, 1}))
} }
if i%3 == 0 {
obj.SetCode([]byte{i, i, i, i, i, 1})
}
transState.UpdateStateObject(obj)
// Create the final state object directly in the final database // Commit and cross check the databases.
obj = finalState.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.SetBalance(big.NewInt(int64(11*i + 1)))
obj.SetNonce(uint64(42*i + 1))
if i%2 == 0 {
obj.SetState(common.BytesToHash([]byte{i, i, i, 1}), common.BytesToHash([]byte{i, i, i, i, 1}))
}
if i%3 == 0 {
obj.SetCode([]byte{i, i, i, i, i, 1})
}
finalState.UpdateStateObject(obj)
}
if _, err := transState.Commit(); err != nil { if _, err := transState.Commit(); err != nil {
t.Fatalf("failed to commit transition state: %v", err) t.Fatalf("failed to commit transition state: %v", err)
} }
if _, err := finalState.Commit(); err != nil { if _, err := finalState.Commit(); err != nil {
t.Fatalf("failed to commit final state: %v", err) t.Fatalf("failed to commit final state: %v", err)
} }
// Cross check the databases to ensure they are the same
for _, key := range finalDb.Keys() { for _, key := range finalDb.Keys() {
if _, err := transDb.Get(key); err != nil { if _, err := transDb.Get(key); err != nil {
val, _ := finalDb.Get(key) val, _ := finalDb.Get(key)
@ -118,3 +114,243 @@ func TestIntermediateLeaks(t *testing.T) {
} }
} }
} }
func TestSnapshotRandom(t *testing.T) {
config := &quick.Config{MaxCount: 1000}
err := quick.Check((*snapshotTest).run, config)
if cerr, ok := err.(*quick.CheckError); ok {
test := cerr.In[0].(*snapshotTest)
t.Errorf("%v:\n%s", test.err, test)
} else if err != nil {
t.Error(err)
}
}
// A snapshotTest checks that reverting StateDB snapshots properly undoes all changes
// captured by the snapshot. Instances of this test with pseudorandom content are created
// by Generate.
//
// The test works as follows:
//
// A new state is created and all actions are applied to it. Several snapshots are taken
// in between actions. The test then reverts each snapshot. For each snapshot the actions
// leading up to it are replayed on a fresh, empty state. The behaviour of all public
// accessor methods on the reverted state must match the return value of the equivalent
// methods on the replayed state.
type snapshotTest struct {
addrs []common.Address // all account addresses
actions []testAction // modifications to the state
snapshots []int // actions indexes at which snapshot is taken
err error // failure details are reported through this field
}
type testAction struct {
name string
fn func(testAction, *StateDB)
args []int64
noAddr bool
}
// newTestAction creates a random action that changes state.
func newTestAction(addr common.Address, r *rand.Rand) testAction {
actions := []testAction{
{
name: "SetBalance",
fn: func(a testAction, s *StateDB) {
s.SetBalance(addr, big.NewInt(a.args[0]))
},
args: make([]int64, 1),
},
{
name: "AddBalance",
fn: func(a testAction, s *StateDB) {
s.AddBalance(addr, big.NewInt(a.args[0]))
},
args: make([]int64, 1),
},
{
name: "SetNonce",
fn: func(a testAction, s *StateDB) {
s.SetNonce(addr, uint64(a.args[0]))
},
args: make([]int64, 1),
},
{
name: "SetState",
fn: func(a testAction, s *StateDB) {
var key, val common.Hash
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
s.SetState(addr, key, val)
},
args: make([]int64, 2),
},
{
name: "SetCode",
fn: func(a testAction, s *StateDB) {
code := make([]byte, 16)
binary.BigEndian.PutUint64(code, uint64(a.args[0]))
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
s.SetCode(addr, code)
},
args: make([]int64, 2),
},
{
name: "CreateAccount",
fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr)
},
},
{
name: "Suicide",
fn: func(a testAction, s *StateDB) {
s.Suicide(addr)
},
},
{
name: "AddRefund",
fn: func(a testAction, s *StateDB) {
s.AddRefund(big.NewInt(a.args[0]))
},
args: make([]int64, 1),
noAddr: true,
},
{
name: "AddLog",
fn: func(a testAction, s *StateDB) {
data := make([]byte, 2)
binary.BigEndian.PutUint16(data, uint16(a.args[0]))
s.AddLog(&vm.Log{Address: addr, Data: data})
},
args: make([]int64, 1),
},
}
action := actions[r.Intn(len(actions))]
var nameargs []string
if !action.noAddr {
nameargs = append(nameargs, addr.Hex())
}
for _, i := range action.args {
action.args[i] = rand.Int63n(100)
nameargs = append(nameargs, fmt.Sprint(action.args[i]))
}
action.name += strings.Join(nameargs, ", ")
return action
}
// Generate returns a new snapshot test of the given size. All randomness is
// derived from r.
func (*snapshotTest) Generate(r *rand.Rand, size int) reflect.Value {
// Generate random actions.
addrs := make([]common.Address, 50)
for i := range addrs {
addrs[i][0] = byte(i)
}
actions := make([]testAction, size)
for i := range actions {
addr := addrs[r.Intn(len(addrs))]
actions[i] = newTestAction(addr, r)
}
// Generate snapshot indexes.
nsnapshots := int(math.Sqrt(float64(size)))
if size > 0 && nsnapshots == 0 {
nsnapshots = 1
}
snapshots := make([]int, nsnapshots)
snaplen := len(actions) / nsnapshots
for i := range snapshots {
// Try to place the snapshots some number of actions apart from each other.
snapshots[i] = (i * snaplen) + r.Intn(snaplen)
}
return reflect.ValueOf(&snapshotTest{addrs, actions, snapshots, nil})
}
func (test *snapshotTest) String() string {
out := new(bytes.Buffer)
sindex := 0
for i, action := range test.actions {
if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
fmt.Fprintf(out, "---- snapshot %d ----\n", sindex)
sindex++
}
fmt.Fprintf(out, "%4d: %s\n", i, action.name)
}
return out.String()
}
func (test *snapshotTest) run() bool {
// Run all actions and create snapshots.
var (
db, _ = ethdb.NewMemDatabase()
state, _ = New(common.Hash{}, db)
snapshotRevs = make([]int, len(test.snapshots))
sindex = 0
)
for i, action := range test.actions {
if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
snapshotRevs[sindex] = state.Snapshot()
sindex++
}
action.fn(action, state)
}
// Revert all snapshots in reverse order. Each revert must yield a state
// that is equivalent to fresh state with all actions up the snapshot applied.
for sindex--; sindex >= 0; sindex-- {
checkstate, _ := New(common.Hash{}, db)
for _, action := range test.actions[:test.snapshots[sindex]] {
action.fn(action, checkstate)
}
state.RevertToSnapshot(snapshotRevs[sindex])
if err := test.checkEqual(state, checkstate); err != nil {
test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err)
return false
}
}
return true
}
// checkEqual checks that methods of state and checkstate return the same values.
func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
for _, addr := range test.addrs {
var err error
checkeq := func(op string, a, b interface{}) bool {
if err == nil && !reflect.DeepEqual(a, b) {
err = fmt.Errorf("got %s(%s) == %v, want %v", op, addr.Hex(), a, b)
return false
}
return true
}
// Check basic accessor methods.
checkeq("Exist", state.Exist(addr), checkstate.Exist(addr))
checkeq("HasSuicided", state.HasSuicided(addr), checkstate.HasSuicided(addr))
checkeq("GetBalance", state.GetBalance(addr), checkstate.GetBalance(addr))
checkeq("GetNonce", state.GetNonce(addr), checkstate.GetNonce(addr))
checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr))
checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr))
checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
// Check storage.
if obj := state.GetStateObject(addr); obj != nil {
obj.ForEachStorage(func(key, val common.Hash) bool {
return checkeq("GetState("+key.Hex()+")", val, checkstate.GetState(addr, key))
})
checkobj := checkstate.GetStateObject(addr)
checkobj.ForEachStorage(func(key, checkval common.Hash) bool {
return checkeq("GetState("+key.Hex()+")", state.GetState(addr, key), checkval)
})
}
if err != nil {
return err
}
}
if state.GetRefund().Cmp(checkstate.GetRefund()) != 0 {
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
state.GetRefund(), checkstate.GetRefund())
}
if !reflect.DeepEqual(state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) {
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{}))
}
return nil
}

View file

@ -54,17 +54,14 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
acc.nonce = uint64(42 * i) acc.nonce = uint64(42 * i)
if i%3 == 0 { if i%3 == 0 {
obj.SetCode([]byte{i, i, i, i, i}) obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i})
acc.code = []byte{i, i, i, i, i} acc.code = []byte{i, i, i, i, i}
} }
state.UpdateStateObject(obj) state.updateStateObject(obj)
accounts = append(accounts, acc) accounts = append(accounts, acc)
} }
root, _ := state.Commit() root, _ := state.Commit()
// Remove any potentially cached data from the test state creation
trie.ClearGlobalCache()
// Return the generated state // Return the generated state
return db, root, accounts return db, root, accounts
} }
@ -72,9 +69,6 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
// checkStateAccounts cross references a reconstructed state with an expected // checkStateAccounts cross references a reconstructed state with an expected
// account array. // account array.
func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) { func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) {
// Remove any potentially cached data from the state synchronisation
trie.ClearGlobalCache()
// Check root availability and state contents // Check root availability and state contents
state, err := New(root, db) state, err := New(root, db)
if err != nil { if err != nil {
@ -98,9 +92,6 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou
// checkStateConsistency checks that all nodes in a state trie are indeed present. // checkStateConsistency checks that all nodes in a state trie are indeed present.
func checkStateConsistency(db ethdb.Database, root common.Hash) error { func checkStateConsistency(db ethdb.Database, root common.Hash) error {
// Remove any potentially cached data from the test state creation or previous checks
trie.ClearGlobalCache()
// Create and iterate a state trie rooted in a sub-node // Create and iterate a state trie rooted in a sub-node
if _, err := db.Get(root.Bytes()); err != nil { if _, err := db.Get(root.Bytes()); err != nil {
return nil // Consider a non existent state consistent return nil // Consider a non existent state consistent

View file

@ -65,7 +65,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
allLogs vm.Logs allLogs vm.Logs
gp = new(GasPool).AddGas(block.GasLimit()) gp = new(GasPool).AddGas(block.GasLimit())
) )
// Mutate the the block and state according to any hard-fork specs
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
ApplyDAOHardFork(statedb)
}
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
statedb.StartRecord(tx.Hash(), block.Hash(), i) statedb.StartRecord(tx.Hash(), block.Hash(), i)
receipt, logs, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg) receipt, logs, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg)

View file

@ -240,7 +240,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
// Make sure the account exist. Non existent accounts // Make sure the account exist. Non existent accounts
// haven't got funds and well therefor never pass. // haven't got funds and well therefor never pass.
if !currentState.HasAccount(from) { if !currentState.Exist(from) {
return ErrNonExistentAccount return ErrNonExistentAccount
} }

View file

@ -28,7 +28,7 @@ type ContractRef interface {
ReturnGas(*big.Int, *big.Int) ReturnGas(*big.Int, *big.Int)
Address() common.Address Address() common.Address
Value() *big.Int Value() *big.Int
SetCode([]byte) SetCode(common.Hash, []byte)
ForEachStorage(callback func(key, value common.Hash) bool) ForEachStorage(callback func(key, value common.Hash) bool)
} }
@ -45,8 +45,9 @@ type Contract struct {
jumpdests destinations // result of JUMPDEST analysis. jumpdests destinations // result of JUMPDEST analysis.
Code []byte Code []byte
Input []byte CodeHash common.Hash
CodeAddr *common.Address CodeAddr *common.Address
Input []byte
value, Gas, UsedGas, Price *big.Int value, Gas, UsedGas, Price *big.Int
@ -144,14 +145,16 @@ func (c *Contract) Value() *big.Int {
} }
// SetCode sets the code to the contract // SetCode sets the code to the contract
func (self *Contract) SetCode(code []byte) { func (self *Contract) SetCode(hash common.Hash, code []byte) {
self.Code = code self.Code = code
self.CodeHash = hash
} }
// SetCallCode sets the code of the contract and address of the backing data // SetCallCode sets the code of the contract and address of the backing data
// object // object
func (self *Contract) SetCallCode(addr *common.Address, code []byte) { func (self *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
self.Code = code self.Code = code
self.CodeHash = hash
self.CodeAddr = addr self.CodeAddr = addr
} }

View file

@ -95,7 +95,7 @@ func ecrecoverFunc(in []byte) []byte {
// tighter sig s values in homestead only apply to tx sigs // tighter sig s values in homestead only apply to tx sigs
if !crypto.ValidateSignatureValues(v, r, s, false) { if !crypto.ValidateSignatureValues(v, r, s, false) {
glog.V(logger.Debug).Infof("EC RECOVER FAIL: v, r or s value invalid") glog.V(logger.Detail).Infof("ECRECOVER error: v, r or s value invalid")
return nil return nil
} }
@ -106,7 +106,7 @@ func ecrecoverFunc(in []byte) []byte {
pubKey, err := crypto.Ecrecover(in[:32], rsv) pubKey, err := crypto.Ecrecover(in[:32], rsv)
// make sure the public key is a valid one // make sure the public key is a valid one
if err != nil { if err != nil {
glog.V(logger.Error).Infof("EC RECOVER FAIL: ", err) glog.V(logger.Detail).Infoln("ECRECOVER error: ", err)
return nil return nil
} }

View file

@ -37,9 +37,9 @@ type Environment interface {
// The state database // The state database
Db() Database Db() Database
// Creates a restorable snapshot // Creates a restorable snapshot
MakeSnapshot() Database SnapshotDatabase() int
// Set database to previous snapshot // Set database to previous snapshot
SetSnapshot(Database) RevertToSnapshot(int)
// Address of the original invoker (first occurrence of the VM invoker) // Address of the original invoker (first occurrence of the VM invoker)
Origin() common.Address Origin() common.Address
// The block number this VM is invoked on // The block number this VM is invoked on
@ -95,6 +95,8 @@ type Database interface {
GetNonce(common.Address) uint64 GetNonce(common.Address) uint64
SetNonce(common.Address, uint64) SetNonce(common.Address, uint64)
GetCodeHash(common.Address) common.Hash
GetCodeSize(common.Address) int
GetCode(common.Address) []byte GetCode(common.Address) []byte
SetCode(common.Address, []byte) SetCode(common.Address, []byte)
@ -104,9 +106,12 @@ type Database interface {
GetState(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash) SetState(common.Address, common.Hash, common.Hash)
Delete(common.Address) bool Suicide(common.Address) bool
HasSuicided(common.Address) bool
// Exist reports whether the given account exists in state.
// Notably this should also return true for suicided accounts.
Exist(common.Address) bool Exist(common.Address) bool
IsDeleted(common.Address) bool
} }
// Account represents a contract or basic expanse account. // Account represents a contract or basic expanse account.
@ -118,7 +123,7 @@ type Account interface {
Balance() *big.Int Balance() *big.Int
Address() common.Address Address() common.Address
ReturnGas(*big.Int, *big.Int) ReturnGas(*big.Int, *big.Int)
SetCode([]byte) SetCode(common.Hash, []byte)
ForEachStorage(cb func(key, value common.Hash) bool) ForEachStorage(cb func(key, value common.Hash) bool)
Value() *big.Int Value() *big.Int
} }

View file

@ -365,7 +365,7 @@ func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Co
func opExtCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { func opExtCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
addr := common.BigToAddress(stack.pop()) addr := common.BigToAddress(stack.pop())
l := big.NewInt(int64(len(env.Db().GetCode(addr)))) l := big.NewInt(int64(env.Db().GetCodeSize(addr)))
stack.push(l) stack.push(l)
} }
@ -616,7 +616,7 @@ func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contrac
balance := env.Db().GetBalance(contract.Address()) balance := env.Db().GetBalance(contract.Address())
env.Db().AddBalance(common.BigToAddress(stack.pop()), balance) env.Db().AddBalance(common.BigToAddress(stack.pop()), balance)
env.Db().Delete(contract.Address()) env.Db().Suicide(contract.Address())
} }
// following functions are used by the instruction jump table // following functions are used by the instruction jump table

View file

@ -427,7 +427,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
} }
gas.Set(g) gas.Set(g)
case SUICIDE: case SUICIDE:
if !statedb.IsDeleted(contract.Address()) { if !statedb.HasSuicided(contract.Address()) {
statedb.AddRefund(params.SuicideRefundGas) statedb.AddRefund(params.SuicideRefundGas)
} }
case MLOAD: case MLOAD:

View file

@ -135,7 +135,7 @@ func (account) SetNonce(uint64) {}
func (account) Balance() *big.Int { return nil } func (account) Balance() *big.Int { return nil }
func (account) Address() common.Address { return common.Address{} } func (account) Address() common.Address { return common.Address{} }
func (account) ReturnGas(*big.Int, *big.Int) {} func (account) ReturnGas(*big.Int, *big.Int) {}
func (account) SetCode([]byte) {} func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
func runVmBench(test vmBench, b *testing.B) { func runVmBench(test vmBench, b *testing.B) {
@ -187,8 +187,8 @@ func (self *Env) StructLogs() []StructLog {
//func (self *Env) PrevHash() []byte { return self.parent } //func (self *Env) PrevHash() []byte { return self.parent }
func (self *Env) Coinbase() common.Address { return common.Address{} } func (self *Env) Coinbase() common.Address { return common.Address{} }
func (self *Env) MakeSnapshot() Database { return nil } func (self *Env) SnapshotDatabase() int { return 0 }
func (self *Env) SetSnapshot(Database) {} func (self *Env) RevertToSnapshot(int) {}
func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) } func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) }
func (self *Env) Difficulty() *big.Int { return big.NewInt(0) } func (self *Env) Difficulty() *big.Int { return big.NewInt(0) }
func (self *Env) Db() Database { return nil } func (self *Env) Db() Database { return nil }

View file

@ -30,7 +30,7 @@ type dummyContractRef struct {
func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {} func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {}
func (dummyContractRef) Address() common.Address { return common.Address{} } func (dummyContractRef) Address() common.Address { return common.Address{} }
func (dummyContractRef) Value() *big.Int { return new(big.Int) } func (dummyContractRef) Value() *big.Int { return new(big.Int) }
func (dummyContractRef) SetCode([]byte) {} func (dummyContractRef) SetCode(common.Hash, []byte) {}
func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) {
d.calledForEach = true d.calledForEach = true
} }

View file

@ -100,11 +100,11 @@ func (self *Env) SetDepth(i int) { self.depth = i }
func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool { func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
return self.state.GetBalance(from).Cmp(balance) >= 0 return self.state.GetBalance(from).Cmp(balance) >= 0
} }
func (self *Env) MakeSnapshot() vm.Database { func (self *Env) SnapshotDatabase() int {
return self.state.Copy() return self.state.Snapshot()
} }
func (self *Env) SetSnapshot(copy vm.Database) { func (self *Env) RevertToSnapshot(snapshot int) {
self.state.Set(copy.(*state.StateDB)) self.state.RevertToSnapshot(snapshot)
} }
func (self *Env) Transfer(from, to vm.Account, amount *big.Int) { func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {

View file

@ -104,7 +104,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
receiver = cfg.State.CreateAccount(common.StringToAddress("contract")) receiver = cfg.State.CreateAccount(common.StringToAddress("contract"))
) )
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
receiver.SetCode(code) receiver.SetCode(crypto.Keccak256Hash(code), code)
// Call the code with the given configuration. // Call the code with the given configuration.
ret, err := vmenv.Call( ret, err := vmenv.Call(

View file

@ -78,10 +78,11 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
return nil, nil return nil, nil
} }
var ( codehash := contract.CodeHash // codehash is used when doing jump dest caching
codehash = crypto.Keccak256Hash(contract.Code) // codehash is used when doing jump dest caching if codehash == (common.Hash{}) {
program *Program codehash = crypto.Keccak256Hash(contract.Code)
) }
var program *Program
if evm.cfg.EnableJit { if evm.cfg.EnableJit {
// If the JIT is enabled check the status of the JIT program, // If the JIT is enabled check the status of the JIT program,
// if it doesn't exist compile a new program in a separate // if it doesn't exist compile a new program in a separate
@ -309,7 +310,7 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
} }
gas.Set(g) gas.Set(g)
case SUICIDE: case SUICIDE:
if !statedb.IsDeleted(contract.Address()) { if !statedb.HasSuicided(contract.Address()) {
statedb.AddRefund(params.SuicideRefundGas) statedb.AddRefund(params.SuicideRefundGas)
} }
case MLOAD: case MLOAD:

View file

@ -95,12 +95,12 @@ func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
return self.state.GetBalance(from).Cmp(balance) >= 0 return self.state.GetBalance(from).Cmp(balance) >= 0
} }
func (self *VMEnv) MakeSnapshot() vm.Database { func (self *VMEnv) SnapshotDatabase() int {
return self.state.Copy() return self.state.Snapshot()
} }
func (self *VMEnv) SetSnapshot(copy vm.Database) { func (self *VMEnv) RevertToSnapshot(snapshot int) {
self.state.Set(copy.(*state.StateDB)) self.state.RevertToSnapshot(snapshot)
} }
func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) { func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {

View file

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !amd64 appengine gccgo
package sha3 package sha3
// rc stores the round constants for use in the ι step. // rc stores the round constants for use in the ι step.

View file

@ -0,0 +1,13 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64,!appengine,!gccgo
package sha3
// This function is implemented in keccakf_amd64.s.
//go:noescape
func keccakF1600(state *[25]uint64)

392
crypto/sha3/keccakf_amd64.s Normal file
View file

@ -0,0 +1,392 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64,!appengine,!gccgo
// This code was translated into a form compatible with 6a from the public
// domain sources at https://github.com/gvanas/KeccakCodePackage
// Offsets in state
#define _ba (0*8)
#define _be (1*8)
#define _bi (2*8)
#define _bo (3*8)
#define _bu (4*8)
#define _ga (5*8)
#define _ge (6*8)
#define _gi (7*8)
#define _go (8*8)
#define _gu (9*8)
#define _ka (10*8)
#define _ke (11*8)
#define _ki (12*8)
#define _ko (13*8)
#define _ku (14*8)
#define _ma (15*8)
#define _me (16*8)
#define _mi (17*8)
#define _mo (18*8)
#define _mu (19*8)
#define _sa (20*8)
#define _se (21*8)
#define _si (22*8)
#define _so (23*8)
#define _su (24*8)
// Temporary registers
#define rT1 AX
// Round vars
#define rpState DI
#define rpStack SP
#define rDa BX
#define rDe CX
#define rDi DX
#define rDo R8
#define rDu R9
#define rBa R10
#define rBe R11
#define rBi R12
#define rBo R13
#define rBu R14
#define rCa SI
#define rCe BP
#define rCi rBi
#define rCo rBo
#define rCu R15
#define MOVQ_RBI_RCE MOVQ rBi, rCe
#define XORQ_RT1_RCA XORQ rT1, rCa
#define XORQ_RT1_RCE XORQ rT1, rCe
#define XORQ_RBA_RCU XORQ rBa, rCu
#define XORQ_RBE_RCU XORQ rBe, rCu
#define XORQ_RDU_RCU XORQ rDu, rCu
#define XORQ_RDA_RCA XORQ rDa, rCa
#define XORQ_RDE_RCE XORQ rDe, rCe
#define mKeccakRound(iState, oState, rc, B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU, K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA, M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA, S_RDE_RCE) \
/* Prepare round */ \
MOVQ rCe, rDa; \
ROLQ $1, rDa; \
\
MOVQ _bi(iState), rCi; \
XORQ _gi(iState), rDi; \
XORQ rCu, rDa; \
XORQ _ki(iState), rCi; \
XORQ _mi(iState), rDi; \
XORQ rDi, rCi; \
\
MOVQ rCi, rDe; \
ROLQ $1, rDe; \
\
MOVQ _bo(iState), rCo; \
XORQ _go(iState), rDo; \
XORQ rCa, rDe; \
XORQ _ko(iState), rCo; \
XORQ _mo(iState), rDo; \
XORQ rDo, rCo; \
\
MOVQ rCo, rDi; \
ROLQ $1, rDi; \
\
MOVQ rCu, rDo; \
XORQ rCe, rDi; \
ROLQ $1, rDo; \
\
MOVQ rCa, rDu; \
XORQ rCi, rDo; \
ROLQ $1, rDu; \
\
/* Result b */ \
MOVQ _ba(iState), rBa; \
MOVQ _ge(iState), rBe; \
XORQ rCo, rDu; \
MOVQ _ki(iState), rBi; \
MOVQ _mo(iState), rBo; \
MOVQ _su(iState), rBu; \
XORQ rDe, rBe; \
ROLQ $44, rBe; \
XORQ rDi, rBi; \
XORQ rDa, rBa; \
ROLQ $43, rBi; \
\
MOVQ rBe, rCa; \
MOVQ rc, rT1; \
ORQ rBi, rCa; \
XORQ rBa, rT1; \
XORQ rT1, rCa; \
MOVQ rCa, _ba(oState); \
\
XORQ rDu, rBu; \
ROLQ $14, rBu; \
MOVQ rBa, rCu; \
ANDQ rBe, rCu; \
XORQ rBu, rCu; \
MOVQ rCu, _bu(oState); \
\
XORQ rDo, rBo; \
ROLQ $21, rBo; \
MOVQ rBo, rT1; \
ANDQ rBu, rT1; \
XORQ rBi, rT1; \
MOVQ rT1, _bi(oState); \
\
NOTQ rBi; \
ORQ rBa, rBu; \
ORQ rBo, rBi; \
XORQ rBo, rBu; \
XORQ rBe, rBi; \
MOVQ rBu, _bo(oState); \
MOVQ rBi, _be(oState); \
B_RBI_RCE; \
\
/* Result g */ \
MOVQ _gu(iState), rBe; \
XORQ rDu, rBe; \
MOVQ _ka(iState), rBi; \
ROLQ $20, rBe; \
XORQ rDa, rBi; \
ROLQ $3, rBi; \
MOVQ _bo(iState), rBa; \
MOVQ rBe, rT1; \
ORQ rBi, rT1; \
XORQ rDo, rBa; \
MOVQ _me(iState), rBo; \
MOVQ _si(iState), rBu; \
ROLQ $28, rBa; \
XORQ rBa, rT1; \
MOVQ rT1, _ga(oState); \
G_RT1_RCA; \
\
XORQ rDe, rBo; \
ROLQ $45, rBo; \
MOVQ rBi, rT1; \
ANDQ rBo, rT1; \
XORQ rBe, rT1; \
MOVQ rT1, _ge(oState); \
G_RT1_RCE; \
\
XORQ rDi, rBu; \
ROLQ $61, rBu; \
MOVQ rBu, rT1; \
ORQ rBa, rT1; \
XORQ rBo, rT1; \
MOVQ rT1, _go(oState); \
\
ANDQ rBe, rBa; \
XORQ rBu, rBa; \
MOVQ rBa, _gu(oState); \
NOTQ rBu; \
G_RBA_RCU; \
\
ORQ rBu, rBo; \
XORQ rBi, rBo; \
MOVQ rBo, _gi(oState); \
\
/* Result k */ \
MOVQ _be(iState), rBa; \
MOVQ _gi(iState), rBe; \
MOVQ _ko(iState), rBi; \
MOVQ _mu(iState), rBo; \
MOVQ _sa(iState), rBu; \
XORQ rDi, rBe; \
ROLQ $6, rBe; \
XORQ rDo, rBi; \
ROLQ $25, rBi; \
MOVQ rBe, rT1; \
ORQ rBi, rT1; \
XORQ rDe, rBa; \
ROLQ $1, rBa; \
XORQ rBa, rT1; \
MOVQ rT1, _ka(oState); \
K_RT1_RCA; \
\
XORQ rDu, rBo; \
ROLQ $8, rBo; \
MOVQ rBi, rT1; \
ANDQ rBo, rT1; \
XORQ rBe, rT1; \
MOVQ rT1, _ke(oState); \
K_RT1_RCE; \
\
XORQ rDa, rBu; \
ROLQ $18, rBu; \
NOTQ rBo; \
MOVQ rBo, rT1; \
ANDQ rBu, rT1; \
XORQ rBi, rT1; \
MOVQ rT1, _ki(oState); \
\
MOVQ rBu, rT1; \
ORQ rBa, rT1; \
XORQ rBo, rT1; \
MOVQ rT1, _ko(oState); \
\
ANDQ rBe, rBa; \
XORQ rBu, rBa; \
MOVQ rBa, _ku(oState); \
K_RBA_RCU; \
\
/* Result m */ \
MOVQ _ga(iState), rBe; \
XORQ rDa, rBe; \
MOVQ _ke(iState), rBi; \
ROLQ $36, rBe; \
XORQ rDe, rBi; \
MOVQ _bu(iState), rBa; \
ROLQ $10, rBi; \
MOVQ rBe, rT1; \
MOVQ _mi(iState), rBo; \
ANDQ rBi, rT1; \
XORQ rDu, rBa; \
MOVQ _so(iState), rBu; \
ROLQ $27, rBa; \
XORQ rBa, rT1; \
MOVQ rT1, _ma(oState); \
M_RT1_RCA; \
\
XORQ rDi, rBo; \
ROLQ $15, rBo; \
MOVQ rBi, rT1; \
ORQ rBo, rT1; \
XORQ rBe, rT1; \
MOVQ rT1, _me(oState); \
M_RT1_RCE; \
\
XORQ rDo, rBu; \
ROLQ $56, rBu; \
NOTQ rBo; \
MOVQ rBo, rT1; \
ORQ rBu, rT1; \
XORQ rBi, rT1; \
MOVQ rT1, _mi(oState); \
\
ORQ rBa, rBe; \
XORQ rBu, rBe; \
MOVQ rBe, _mu(oState); \
\
ANDQ rBa, rBu; \
XORQ rBo, rBu; \
MOVQ rBu, _mo(oState); \
M_RBE_RCU; \
\
/* Result s */ \
MOVQ _bi(iState), rBa; \
MOVQ _go(iState), rBe; \
MOVQ _ku(iState), rBi; \
XORQ rDi, rBa; \
MOVQ _ma(iState), rBo; \
ROLQ $62, rBa; \
XORQ rDo, rBe; \
MOVQ _se(iState), rBu; \
ROLQ $55, rBe; \
\
XORQ rDu, rBi; \
MOVQ rBa, rDu; \
XORQ rDe, rBu; \
ROLQ $2, rBu; \
ANDQ rBe, rDu; \
XORQ rBu, rDu; \
MOVQ rDu, _su(oState); \
\
ROLQ $39, rBi; \
S_RDU_RCU; \
NOTQ rBe; \
XORQ rDa, rBo; \
MOVQ rBe, rDa; \
ANDQ rBi, rDa; \
XORQ rBa, rDa; \
MOVQ rDa, _sa(oState); \
S_RDA_RCA; \
\
ROLQ $41, rBo; \
MOVQ rBi, rDe; \
ORQ rBo, rDe; \
XORQ rBe, rDe; \
MOVQ rDe, _se(oState); \
S_RDE_RCE; \
\
MOVQ rBo, rDi; \
MOVQ rBu, rDo; \
ANDQ rBu, rDi; \
ORQ rBa, rDo; \
XORQ rBi, rDi; \
XORQ rBo, rDo; \
MOVQ rDi, _si(oState); \
MOVQ rDo, _so(oState) \
// func keccakF1600(state *[25]uint64)
TEXT ·keccakF1600(SB), 0, $200-8
MOVQ state+0(FP), rpState
SUBQ $(8*25), SP
// Convert the user state into an internal state
NOTQ _be(rpState)
NOTQ _bi(rpState)
NOTQ _go(rpState)
NOTQ _ki(rpState)
NOTQ _mi(rpState)
NOTQ _sa(rpState)
// Execute the KeccakF permutation
MOVQ _ba(rpState), rCa
MOVQ _be(rpState), rCe
MOVQ _bu(rpState), rCu
XORQ _ga(rpState), rCa
XORQ _ge(rpState), rCe
XORQ _gu(rpState), rCu
XORQ _ka(rpState), rCa
XORQ _ke(rpState), rCe
XORQ _ku(rpState), rCu
XORQ _ma(rpState), rCa
XORQ _me(rpState), rCe
XORQ _mu(rpState), rCu
XORQ _sa(rpState), rCa
XORQ _se(rpState), rCe
MOVQ _si(rpState), rDi
MOVQ _so(rpState), rDo
XORQ _su(rpState), rCu
mKeccakRound(rpState, rpStack, $0x0000000000000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x0000000000008082, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x800000000000808a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x8000000080008000, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x000000000000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x8000000000008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x000000000000008a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x0000000000000088, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x0000000080008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x000000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x000000008000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x800000000000008b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x8000000000008089, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x8000000000008003, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x8000000000008002, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x8000000000000080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x000000000000800a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x800000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x8000000000008080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpState, rpStack, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
mKeccakRound(rpStack, rpState, $0x8000000080008008, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP)
// Revert the internal state to the user state
NOTQ _be(rpState)
NOTQ _bi(rpState)
NOTQ _go(rpState)
NOTQ _ki(rpState)
NOTQ _mi(rpState)
NOTQ _sa(rpState)
ADDQ $(8*25), SP
RET

View file

@ -42,7 +42,7 @@ type state struct {
storage [maxRate]byte storage [maxRate]byte
// Specific to SHA-3 and SHAKE. // Specific to SHA-3 and SHAKE.
fixedOutput bool // whether this is a fixed-ouput-length instance fixedOutput bool // whether this is a fixed-output-length instance
outputLen int // the default output size in bytes outputLen int // the default output size in bytes
state spongeDirection // whether the sponge is absorbing or squeezing state spongeDirection // whether the sponge is absorbing or squeezing
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !amd64,!386 appengine // +build !amd64,!386,!ppc64le appengine
package sha3 package sha3

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build amd64 386 // +build amd64 386 ppc64le
// +build !appengine // +build !appengine
package sha3 package sha3

View file

@ -28,6 +28,7 @@ import (
"github.com/expanse-project/go-expanse/metrics" "github.com/expanse-project/go-expanse/metrics"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
@ -86,6 +87,7 @@ func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) {
OpenFilesCacheCapacity: handles, OpenFilesCacheCapacity: handles,
BlockCacheCapacity: cache / 2 * opt.MiB, BlockCacheCapacity: cache / 2 * opt.MiB,
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
Filter: filter.NewBloomFilter(10),
}) })
if _, corrupted := err.(*errors.ErrCorrupted); corrupted { if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
db, err = leveldb.RecoverFile(file, nil) db, err = leveldb.RecoverFile(file, nil)

View file

@ -1575,14 +1575,14 @@ func NewPublicDebugAPI(exp *Expanse) *PublicDebugAPI {
} }
// DumpBlock retrieves the entire state of the database at a given block. // DumpBlock retrieves the entire state of the database at a given block.
func (api *PublicDebugAPI) DumpBlock(number uint64) (state.World, error) { func (api *PublicDebugAPI) DumpBlock(number uint64) (state.Dump, error) {
block := api.exp.BlockChain().GetBlockByNumber(number) block := api.exp.BlockChain().GetBlockByNumber(number)
if block == nil { if block == nil {
return state.World{}, fmt.Errorf("block #%d not found", number) return state.Dump{}, fmt.Errorf("block #%d not found", number)
} }
stateDb, err := state.New(block.Root(), api.exp.ChainDb()) stateDb, err := api.exp.BlockChain().StateAt(block.Root())
if err != nil { if err != nil {
return state.World{}, err return state.Dump{}, err
} }
return stateDb.RawDump(), nil return stateDb.RawDump(), nil
} }
@ -1748,7 +1748,7 @@ func (api *PrivateDebugAPI) traceBlock(block *types.Block, config *vm.Config) (b
if err := core.ValidateHeader(api.config, blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false); err != nil { if err := core.ValidateHeader(api.config, blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false); err != nil {
return false, collector.traces, err return false, collector.traces, err
} }
statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.exp.ChainDb()) statedb, err := blockchain.StateAt(blockchain.GetBlock(block.ParentHash()).Root())
if err != nil { if err != nil {
return false, collector.traces, err return false, collector.traces, err
} }
@ -1850,7 +1850,7 @@ func (api *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogC
if parent == nil { if parent == nil {
return nil, fmt.Errorf("block parent %x not found", block.ParentHash()) return nil, fmt.Errorf("block parent %x not found", block.ParentHash())
} }
stateDb, err := state.New(parent.Root(), api.exp.ChainDb()) stateDb, err := api.exp.BlockChain().StateAt(parent.Root())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -250,6 +250,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Expanse, error) {
if config.ChainConfig == nil { if config.ChainConfig == nil {
return nil, errors.New("missing chain config") return nil, errors.New("missing chain config")
} }
core.WriteChainConfig(chainDb, genesis.Hash(), config.ChainConfig)
exp.chainConfig = config.ChainConfig exp.chainConfig = config.ChainConfig
exp.chainConfig.VmConfig = vm.Config{ exp.chainConfig.VmConfig = vm.Config{
EnableJit: config.EnableJit, EnableJit: config.EnableJit,

View file

@ -33,7 +33,7 @@ func TestMipmapUpgrade(t *testing.T) {
addr := common.BytesToAddress([]byte("jeff")) addr := common.BytesToAddress([]byte("jeff"))
genesis := core.WriteGenesisBlockForTesting(db) genesis := core.WriteGenesisBlockForTesting(db)
chain, receipts := core.GenerateChain(genesis, db, 10, func(i int, gen *core.BlockGen) { chain, receipts := core.GenerateChain(nil, genesis, db, 10, func(i int, gen *core.BlockGen) {
var receipts types.Receipts var receipts types.Receipts
switch i { switch i {
case 1: case 1:

View file

@ -49,11 +49,6 @@ var (
MaxStateFetch = 384 // Amount of node state values to allow fetching per request MaxStateFetch = 384 // Amount of node state values to allow fetching per request
MaxForkAncestry = 3 * params.EpochDuration.Uint64() // Maximum chain reorganisation MaxForkAncestry = 3 * params.EpochDuration.Uint64() // Maximum chain reorganisation
hashTTL = 3 * time.Second // [eth/61] Time it takes for a hash request to time out
blockTargetRTT = 3 * time.Second / 2 // [eth/61] Target time for completing a block retrieval request
blockTTL = 3 * blockTargetRTT // [eth/61] Maximum time allowance before a block request is considered expired
rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests
rttMaxEstimate = 20 * time.Second // Maximum rount-trip time to target for download requests rttMaxEstimate = 20 * time.Second // Maximum rount-trip time to target for download requests
rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value
@ -64,7 +59,6 @@ var (
qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence
qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value
maxQueuedHashes = 32 * 1024 // [eth/61] Maximum number of hashes to queue for import (DOS protection)
maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection)
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
maxResultsProcess = 2048 // Number of content download results to import at once into the chain maxResultsProcess = 2048 // Number of content download results to import at once into the chain
@ -84,16 +78,13 @@ var (
errStallingPeer = errors.New("peer is stalling") errStallingPeer = errors.New("peer is stalling")
errNoPeers = errors.New("no peers to keep download active") errNoPeers = errors.New("no peers to keep download active")
errTimeout = errors.New("timeout") errTimeout = errors.New("timeout")
errEmptyHashSet = errors.New("empty hash set by peer")
errEmptyHeaderSet = errors.New("empty header set by peer") errEmptyHeaderSet = errors.New("empty header set by peer")
errPeersUnavailable = errors.New("no peers available or all tried for download") errPeersUnavailable = errors.New("no peers available or all tried for download")
errAlreadyInPool = errors.New("hash already in pool")
errInvalidAncestor = errors.New("retrieved ancestor is invalid") errInvalidAncestor = errors.New("retrieved ancestor is invalid")
errInvalidChain = errors.New("retrieved hash chain is invalid") errInvalidChain = errors.New("retrieved hash chain is invalid")
errInvalidBlock = errors.New("retrieved block is invalid") errInvalidBlock = errors.New("retrieved block is invalid")
errInvalidBody = errors.New("retrieved block body is invalid") errInvalidBody = errors.New("retrieved block body is invalid")
errInvalidReceipt = errors.New("retrieved receipt is invalid") errInvalidReceipt = errors.New("retrieved receipt is invalid")
errCancelHashFetch = errors.New("hash download canceled (requested)")
errCancelBlockFetch = errors.New("block download canceled (requested)") errCancelBlockFetch = errors.New("block download canceled (requested)")
errCancelHeaderFetch = errors.New("block header download canceled (requested)") errCancelHeaderFetch = errors.New("block header download canceled (requested)")
errCancelBodyFetch = errors.New("block body download canceled (requested)") errCancelBodyFetch = errors.New("block body download canceled (requested)")
@ -102,6 +93,7 @@ var (
errCancelHeaderProcessing = errors.New("header processing canceled (requested)") errCancelHeaderProcessing = errors.New("header processing canceled (requested)")
errCancelContentProcessing = errors.New("content processing canceled (requested)") errCancelContentProcessing = errors.New("content processing canceled (requested)")
errNoSyncActive = errors.New("no sync active") errNoSyncActive = errors.New("no sync active")
errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 62)")
) )
type Downloader struct { type Downloader struct {
@ -146,20 +138,19 @@ type Downloader struct {
// Channels // Channels
newPeerCh chan *peer newPeerCh chan *peer
hashCh chan dataPack // [eth/61] Channel receiving inbound hashes
blockCh chan dataPack // [eth/61] Channel receiving inbound blocks
headerCh chan dataPack // [eth/62] Channel receiving inbound block headers headerCh chan dataPack // [eth/62] Channel receiving inbound block headers
bodyCh chan dataPack // [eth/62] Channel receiving inbound block bodies bodyCh chan dataPack // [eth/62] Channel receiving inbound block bodies
receiptCh chan dataPack // [eth/63] Channel receiving inbound receipts receiptCh chan dataPack // [eth/63] Channel receiving inbound receipts
stateCh chan dataPack // [eth/63] Channel receiving inbound node state data stateCh chan dataPack // [eth/63] Channel receiving inbound node state data
blockWakeCh chan bool // [eth/61] Channel to signal the block fetcher of new tasks
bodyWakeCh chan bool // [eth/62] Channel to signal the block body fetcher of new tasks bodyWakeCh chan bool // [eth/62] Channel to signal the block body fetcher of new tasks
receiptWakeCh chan bool // [eth/63] Channel to signal the receipt fetcher of new tasks receiptWakeCh chan bool // [eth/63] Channel to signal the receipt fetcher of new tasks
stateWakeCh chan bool // [eth/63] Channel to signal the state fetcher of new tasks stateWakeCh chan bool // [eth/63] Channel to signal the state fetcher of new tasks
headerProcCh chan []*types.Header // [eth/62] Channel to feed the header processor new tasks headerProcCh chan []*types.Header // [eth/62] Channel to feed the header processor new tasks
// Cancellation and termination
cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop)
cancelCh chan struct{} // Channel to cancel mid-flight syncs cancelCh chan struct{} // Channel to cancel mid-flight syncs
cancelLock sync.RWMutex // Lock to protect the cancel channel in delivers cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers
quitCh chan struct{} // Quit channel to signal termination quitCh chan struct{} // Quit channel to signal termination
quitLock sync.RWMutex // Lock to prevent double closes quitLock sync.RWMutex // Lock to prevent double closes
@ -199,13 +190,10 @@ func New(stateDb ethdb.Database, mux *event.TypeMux, hasHeader headerCheckFn, ha
rollback: rollback, rollback: rollback,
dropPeer: dropPeer, dropPeer: dropPeer,
newPeerCh: make(chan *peer, 1), newPeerCh: make(chan *peer, 1),
hashCh: make(chan dataPack, 1),
blockCh: make(chan dataPack, 1),
headerCh: make(chan dataPack, 1), headerCh: make(chan dataPack, 1),
bodyCh: make(chan dataPack, 1), bodyCh: make(chan dataPack, 1),
receiptCh: make(chan dataPack, 1), receiptCh: make(chan dataPack, 1),
stateCh: make(chan dataPack, 1), stateCh: make(chan dataPack, 1),
blockWakeCh: make(chan bool, 1),
bodyWakeCh: make(chan bool, 1), bodyWakeCh: make(chan bool, 1),
receiptWakeCh: make(chan bool, 1), receiptWakeCh: make(chan bool, 1),
stateWakeCh: make(chan bool, 1), stateWakeCh: make(chan bool, 1),
@ -250,13 +238,12 @@ func (d *Downloader) Synchronising() bool {
// RegisterPeer injects a new download peer into the set of block source to be // RegisterPeer injects a new download peer into the set of block source to be
// used for fetching hashes and blocks from. // used for fetching hashes and blocks from.
func (d *Downloader) RegisterPeer(id string, version int, head common.Hash, func (d *Downloader) RegisterPeer(id string, version int, currentHead currentHeadRetrievalFn,
getRelHashes relativeHashFetcherFn, getAbsHashes absoluteHashFetcherFn, getBlocks blockFetcherFn, // eth/61 callbacks, remove when upgrading
getRelHeaders relativeHeaderFetcherFn, getAbsHeaders absoluteHeaderFetcherFn, getBlockBodies blockBodyFetcherFn, getRelHeaders relativeHeaderFetcherFn, getAbsHeaders absoluteHeaderFetcherFn, getBlockBodies blockBodyFetcherFn,
getReceipts receiptFetcherFn, getNodeData stateFetcherFn) error { getReceipts receiptFetcherFn, getNodeData stateFetcherFn) error {
glog.V(logger.Detail).Infoln("Registering peer", id) glog.V(logger.Detail).Infoln("Registering peer", id)
if err := d.peers.Register(newPeer(id, version, head, getRelHashes, getAbsHashes, getBlocks, getRelHeaders, getAbsHeaders, getBlockBodies, getReceipts, getNodeData)); err != nil { if err := d.peers.Register(newPeer(id, version, currentHead, getRelHeaders, getAbsHeaders, getBlockBodies, getReceipts, getNodeData)); err != nil {
glog.V(logger.Error).Infoln("Register failed:", err) glog.V(logger.Error).Infoln("Register failed:", err)
return err return err
} }
@ -269,12 +256,22 @@ func (d *Downloader) RegisterPeer(id string, version int, head common.Hash,
// the specified peer. An effort is also made to return any pending fetches into // the specified peer. An effort is also made to return any pending fetches into
// the queue. // the queue.
func (d *Downloader) UnregisterPeer(id string) error { func (d *Downloader) UnregisterPeer(id string) error {
// Unregister the peer from the active peer set and revoke any fetch tasks
glog.V(logger.Detail).Infoln("Unregistering peer", id) glog.V(logger.Detail).Infoln("Unregistering peer", id)
if err := d.peers.Unregister(id); err != nil { if err := d.peers.Unregister(id); err != nil {
glog.V(logger.Error).Infoln("Unregister failed:", err) glog.V(logger.Error).Infoln("Unregister failed:", err)
return err return err
} }
d.queue.Revoke(id) d.queue.Revoke(id)
// If this peer was the master peer, abort sync immediately
d.cancelLock.RLock()
master := id == d.cancelPeer
d.cancelLock.RUnlock()
if master {
d.cancel()
}
return nil return nil
} }
@ -291,7 +288,9 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode
case errBusy: case errBusy:
glog.V(logger.Detail).Infof("Synchronisation already in progress") glog.V(logger.Detail).Infof("Synchronisation already in progress")
case errTimeout, errBadPeer, errStallingPeer, errEmptyHashSet, errEmptyHeaderSet, errPeersUnavailable, errInvalidAncestor, errInvalidChain: case errTimeout, errBadPeer, errStallingPeer,
errEmptyHeaderSet, errPeersUnavailable, errTooOld,
errInvalidAncestor, errInvalidChain:
glog.V(logger.Debug).Infof("Removing peer %v: %v", id, err) glog.V(logger.Debug).Infof("Removing peer %v: %v", id, err)
d.dropPeer(id) d.dropPeer(id)
@ -323,13 +322,13 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
d.queue.Reset() d.queue.Reset()
d.peers.Reset() d.peers.Reset()
for _, ch := range []chan bool{d.blockWakeCh, d.bodyWakeCh, d.receiptWakeCh, d.stateWakeCh} { for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh, d.stateWakeCh} {
select { select {
case <-ch: case <-ch:
default: default:
} }
} }
for _, ch := range []chan dataPack{d.hashCh, d.blockCh, d.headerCh, d.bodyCh, d.receiptCh, d.stateCh} { for _, ch := range []chan dataPack{d.headerCh, d.bodyCh, d.receiptCh, d.stateCh} {
for empty := false; !empty; { for empty := false; !empty; {
select { select {
case <-ch: case <-ch:
@ -345,9 +344,10 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
empty = true empty = true
} }
} }
// Create cancel channel for aborting mid-flight // Create cancel channel for aborting mid-flight and mark the master peer
d.cancelLock.Lock() d.cancelLock.Lock()
d.cancelCh = make(chan struct{}) d.cancelCh = make(chan struct{})
d.cancelPeer = id
d.cancelLock.Unlock() d.cancelLock.Unlock()
defer d.cancel() // No matter what, we can't leave the cancel channel open defer d.cancel() // No matter what, we can't leave the cancel channel open
@ -377,40 +377,15 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
d.mux.Post(DoneEvent{}) d.mux.Post(DoneEvent{})
} }
}() }()
if p.version < 62 {
return errTooOld
}
glog.V(logger.Debug).Infof("Synchronising with the network using: %s [eth/%d]", p.id, p.version) glog.V(logger.Debug).Infof("Synchronising with the network using: %s [eth/%d]", p.id, p.version)
defer func(start time.Time) { defer func(start time.Time) {
glog.V(logger.Debug).Infof("Synchronisation terminated after %v", time.Since(start)) glog.V(logger.Debug).Infof("Synchronisation terminated after %v", time.Since(start))
}(time.Now()) }(time.Now())
switch {
case p.version == 61:
// Look up the sync boundaries: the common ancestor and the target block
latest, err := d.fetchHeight61(p)
if err != nil {
return err
}
origin, err := d.findAncestor61(p, latest)
if err != nil {
return err
}
d.syncStatsLock.Lock()
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin {
d.syncStatsChainOrigin = origin
}
d.syncStatsChainHeight = latest
d.syncStatsLock.Unlock()
// Initiate the sync using a concurrent hash and block retrieval algorithm
d.queue.Prepare(origin+1, d.mode, 0, nil)
if d.syncInitHook != nil {
d.syncInitHook(origin, latest)
}
return d.spawnSync(origin+1,
func() error { return d.fetchHashes61(p, td, origin+1) },
func() error { return d.fetchBlocks61(origin + 1) },
)
case p.version >= 62:
// Look up the sync boundaries: the common ancestor and the target block // Look up the sync boundaries: the common ancestor and the target block
latest, err := d.fetchHeight(p) latest, err := d.fetchHeight(p)
if err != nil { if err != nil {
@ -469,12 +444,6 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync
func() error { return d.fetchNodeData() }, // Node state data is retrieved during fast sync func() error { return d.fetchNodeData() }, // Node state data is retrieved during fast sync
) )
default:
// Something very wrong, stop right here
glog.V(logger.Error).Infof("Unsupported exp protocol: %d", p.version)
return errBadPeer
}
} }
// spawnSync runs d.process and all given fetcher functions to completion in // spawnSync runs d.process and all given fetcher functions to completion in
@ -539,459 +508,14 @@ func (d *Downloader) Terminate() {
d.cancel() d.cancel()
} }
// fetchHeight61 retrieves the head block of the remote peer to aid in estimating
// the total time a pending synchronisation would take.
func (d *Downloader) fetchHeight61(p *peer) (uint64, error) {
glog.V(logger.Debug).Infof("%v: retrieving remote chain height", p)
// Request the advertised remote head block and wait for the response
go p.getBlocks([]common.Hash{p.head})
timeout := time.After(hashTTL)
for {
select {
case <-d.cancelCh:
return 0, errCancelBlockFetch
case packet := <-d.blockCh:
// Discard anything not from the origin peer
if packet.PeerId() != p.id {
glog.V(logger.Debug).Infof("Received blocks from incorrect peer(%s)", packet.PeerId())
break
}
// Make sure the peer actually gave something valid
blocks := packet.(*blockPack).blocks
if len(blocks) != 1 {
glog.V(logger.Debug).Infof("%v: invalid number of head blocks: %d != 1", p, len(blocks))
return 0, errBadPeer
}
return blocks[0].NumberU64(), nil
case <-timeout:
glog.V(logger.Debug).Infof("%v: head block timeout", p)
return 0, errTimeout
case <-d.hashCh:
// Out of bounds hashes received, ignore them
case <-d.headerCh:
case <-d.bodyCh:
case <-d.stateCh:
case <-d.receiptCh:
// Ignore eth/{62,63} packets because this is eth/61.
// These can arrive as a late delivery from a previous sync.
}
}
}
// findAncestor61 tries to locate the common ancestor block of the local chain and
// a remote peers blockchain. In the general case when our node was in sync and
// on the correct chain, checking the top N blocks should already get us a match.
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
// the head blocks match), we do a binary search to find the common ancestor.
func (d *Downloader) findAncestor61(p *peer, height uint64) (uint64, error) {
glog.V(logger.Debug).Infof("%v: looking for common ancestor", p)
// Figure out the valid ancestor range to prevent rewrite attacks
floor, ceil := int64(-1), d.headBlock().NumberU64()
if ceil >= MaxForkAncestry {
floor = int64(ceil - MaxForkAncestry)
}
// Request the topmost blocks to short circuit binary ancestor lookup
head := ceil
if head > height {
head = height
}
from := int64(head) - int64(MaxHashFetch) + 1
if from < 0 {
from = 0
}
go p.getAbsHashes(uint64(from), MaxHashFetch)
// Wait for the remote response to the head fetch
number, hash := uint64(0), common.Hash{}
timeout := time.After(hashTTL)
for finished := false; !finished; {
select {
case <-d.cancelCh:
return 0, errCancelHashFetch
case packet := <-d.hashCh:
// Discard anything not from the origin peer
if packet.PeerId() != p.id {
glog.V(logger.Debug).Infof("Received hashes from incorrect peer(%s)", packet.PeerId())
break
}
// Make sure the peer actually gave something valid
hashes := packet.(*hashPack).hashes
if len(hashes) == 0 {
glog.V(logger.Debug).Infof("%v: empty head hash set", p)
return 0, errEmptyHashSet
}
// Check if a common ancestor was found
finished = true
for i := len(hashes) - 1; i >= 0; i-- {
// Skip any headers that underflow/overflow our requested set
header := d.getHeader(hashes[i])
if header == nil || header.Number.Int64() < from || header.Number.Uint64() > head {
continue
}
// Otherwise check if we already know the header or not
if d.hasBlockAndState(hashes[i]) {
number, hash = header.Number.Uint64(), header.Hash()
break
}
}
case <-timeout:
glog.V(logger.Debug).Infof("%v: head hash timeout", p)
return 0, errTimeout
case <-d.blockCh:
// Out of bounds blocks received, ignore them
case <-d.headerCh:
case <-d.bodyCh:
case <-d.stateCh:
case <-d.receiptCh:
// Ignore eth/{62,63} packets because this is eth/61.
// These can arrive as a late delivery from a previous sync.
}
}
// If the head fetch already found an ancestor, return
if !common.EmptyHash(hash) {
if int64(number) <= floor {
glog.V(logger.Warn).Infof("%v: potential rewrite attack: #%d [%x…] <= #%d limit", p, number, hash[:4], floor)
return 0, errInvalidAncestor
}
glog.V(logger.Debug).Infof("%v: common ancestor: #%d [%x…]", p, number, hash[:4])
return number, nil
}
// Ancestor not found, we need to binary search over our chain
start, end := uint64(0), head
if floor > 0 {
start = uint64(floor)
}
for start+1 < end {
// Split our chain interval in two, and request the hash to cross check
check := (start + end) / 2
timeout := time.After(hashTTL)
go p.getAbsHashes(uint64(check), 1)
// Wait until a reply arrives to this request
for arrived := false; !arrived; {
select {
case <-d.cancelCh:
return 0, errCancelHashFetch
case packet := <-d.hashCh:
// Discard anything not from the origin peer
if packet.PeerId() != p.id {
glog.V(logger.Debug).Infof("Received hashes from incorrect peer(%s)", packet.PeerId())
break
}
// Make sure the peer actually gave something valid
hashes := packet.(*hashPack).hashes
if len(hashes) != 1 {
glog.V(logger.Debug).Infof("%v: invalid search hash set (%d)", p, len(hashes))
return 0, errBadPeer
}
arrived = true
// Modify the search interval based on the response
if !d.hasBlockAndState(hashes[0]) {
end = check
break
}
block := d.getBlock(hashes[0]) // this doesn't check state, hence the above explicit check
if block.NumberU64() != check {
glog.V(logger.Debug).Infof("%v: non requested hash #%d [%x…], instead of #%d", p, block.NumberU64(), block.Hash().Bytes()[:4], check)
return 0, errBadPeer
}
start = check
case <-timeout:
glog.V(logger.Debug).Infof("%v: search hash timeout", p)
return 0, errTimeout
case <-d.blockCh:
// Out of bounds blocks received, ignore them
case <-d.headerCh:
case <-d.bodyCh:
case <-d.stateCh:
case <-d.receiptCh:
// Ignore eth/{62,63} packets because this is eth/61.
// These can arrive as a late delivery from a previous sync.
}
}
}
// Ensure valid ancestry and return
if int64(start) <= floor {
glog.V(logger.Warn).Infof("%v: potential rewrite attack: #%d [%x…] <= #%d limit", p, start, hash[:4], floor)
return 0, errInvalidAncestor
}
glog.V(logger.Debug).Infof("%v: common ancestor: #%d [%x…]", p, start, hash[:4])
return start, nil
}
// fetchHashes61 keeps retrieving hashes from the requested number, until no more
// are returned, potentially throttling on the way.
func (d *Downloader) fetchHashes61(p *peer, td *big.Int, from uint64) error {
glog.V(logger.Debug).Infof("%v: downloading hashes from #%d", p, from)
// Create a timeout timer, and the associated hash fetcher
request := time.Now() // time of the last fetch request
timeout := time.NewTimer(0) // timer to dump a non-responsive active peer
<-timeout.C // timeout channel should be initially empty
defer timeout.Stop()
getHashes := func(from uint64) {
glog.V(logger.Detail).Infof("%v: fetching %d hashes from #%d", p, MaxHashFetch, from)
request = time.Now()
timeout.Reset(hashTTL)
go p.getAbsHashes(from, MaxHashFetch)
}
// Start pulling hashes, until all are exhausted
getHashes(from)
gotHashes := false
for {
select {
case <-d.cancelCh:
return errCancelHashFetch
case packet := <-d.hashCh:
// Make sure the active peer is giving us the hashes
if packet.PeerId() != p.id {
glog.V(logger.Debug).Infof("Received hashes from incorrect peer(%s)", packet.PeerId())
break
}
hashReqTimer.UpdateSince(request)
timeout.Stop()
// If no more hashes are inbound, notify the block fetcher and return
if packet.Items() == 0 {
glog.V(logger.Debug).Infof("%v: no available hashes", p)
select {
case d.blockWakeCh <- false:
case <-d.cancelCh:
}
// If no hashes were retrieved at all, the peer violated it's TD promise that it had a
// better chain compared to ours. The only exception is if it's promised blocks were
// already imported by other means (e.g. fetcher):
//
// R <remote peer>, L <local node>: Both at block 10
// R: Mine block 11, and propagate it to L
// L: Queue block 11 for import
// L: Notice that R's head and TD increased compared to ours, start sync
// L: Import of block 11 finishes
// L: Sync begins, and finds common ancestor at 11
// L: Request new hashes up from 11 (R's TD was higher, it must have something)
// R: Nothing to give
if !gotHashes && td.Cmp(d.getTd(d.headBlock().Hash())) > 0 {
return errStallingPeer
}
return nil
}
gotHashes = true
hashes := packet.(*hashPack).hashes
// Otherwise insert all the new hashes, aborting in case of junk
glog.V(logger.Detail).Infof("%v: scheduling %d hashes from #%d", p, len(hashes), from)
inserts := d.queue.Schedule61(hashes, true)
if len(inserts) != len(hashes) {
glog.V(logger.Debug).Infof("%v: stale hashes", p)
return errBadPeer
}
// Notify the block fetcher of new hashes, but stop if queue is full
if d.queue.PendingBlocks() < maxQueuedHashes {
// We still have hashes to fetch, send continuation wake signal (potential)
select {
case d.blockWakeCh <- true:
default:
}
} else {
// Hash limit reached, send a termination wake signal (enforced)
select {
case d.blockWakeCh <- false:
case <-d.cancelCh:
}
return nil
}
// Queue not yet full, fetch the next batch
from += uint64(len(hashes))
getHashes(from)
case <-timeout.C:
glog.V(logger.Debug).Infof("%v: hash request timed out", p)
hashTimeoutMeter.Mark(1)
return errTimeout
case <-d.headerCh:
case <-d.bodyCh:
case <-d.stateCh:
case <-d.receiptCh:
// Ignore eth/{62,63} packets because this is eth/61.
// These can arrive as a late delivery from a previous sync.
}
}
}
// fetchBlocks61 iteratively downloads the scheduled hashes, taking any available
// peers, reserving a chunk of blocks for each, waiting for delivery and also
// periodically checking for timeouts.
func (d *Downloader) fetchBlocks61(from uint64) error {
glog.V(logger.Debug).Infof("Downloading blocks from #%d", from)
defer glog.V(logger.Debug).Infof("Block download terminated")
// Create a timeout timer for scheduling expiration tasks
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
update := make(chan struct{}, 1)
// Fetch blocks until the hash fetcher's done
finished := false
for {
select {
case <-d.cancelCh:
return errCancelBlockFetch
case packet := <-d.blockCh:
// If the peer was previously banned and failed to deliver it's pack
// in a reasonable time frame, ignore it's message.
if peer := d.peers.Peer(packet.PeerId()); peer != nil {
blocks := packet.(*blockPack).blocks
// Deliver the received chunk of blocks and check chain validity
accepted, err := d.queue.DeliverBlocks(peer.id, blocks)
if err == errInvalidChain {
return err
}
// Unless a peer delivered something completely else than requested (usually
// caused by a timed out request which came through in the end), set it to
// idle. If the delivery's stale, the peer should have already been idled.
if err != errStaleDelivery {
peer.SetBlocksIdle(accepted)
}
// Issue a log to the user to see what's going on
switch {
case err == nil && len(blocks) == 0:
glog.V(logger.Detail).Infof("%s: no blocks delivered", peer)
case err == nil:
glog.V(logger.Detail).Infof("%s: delivered %d blocks", peer, len(blocks))
default:
glog.V(logger.Detail).Infof("%s: delivery failed: %v", peer, err)
}
}
// Blocks arrived, try to update the progress
select {
case update <- struct{}{}:
default:
}
case cont := <-d.blockWakeCh:
// The hash fetcher sent a continuation flag, check if it's done
if !cont {
finished = true
}
// Hashes arrive, try to update the progress
select {
case update <- struct{}{}:
default:
}
case <-ticker.C:
// Sanity check update the progress
select {
case update <- struct{}{}:
default:
}
case <-update:
// Short circuit if we lost all our peers
if d.peers.Len() == 0 {
return errNoPeers
}
// Check for block request timeouts and demote the responsible peers
for pid, fails := range d.queue.ExpireBlocks(blockTTL) {
if peer := d.peers.Peer(pid); peer != nil {
if fails > 1 {
glog.V(logger.Detail).Infof("%s: block delivery timeout", peer)
peer.SetBlocksIdle(0)
} else {
glog.V(logger.Debug).Infof("%s: stalling block delivery, dropping", peer)
d.dropPeer(pid)
}
}
}
// If there's nothing more to fetch, wait or terminate
if d.queue.PendingBlocks() == 0 {
if !d.queue.InFlightBlocks() && finished {
glog.V(logger.Debug).Infof("Block fetching completed")
return nil
}
break
}
// Send a download request to all idle peers, until throttled
throttled := false
idles, total := d.peers.BlockIdlePeers()
for _, peer := range idles {
// Short circuit if throttling activated
if d.queue.ShouldThrottleBlocks() {
throttled = true
break
}
// Reserve a chunk of hashes for a peer. A nil can mean either that
// no more hashes are available, or that the peer is known not to
// have them.
request := d.queue.ReserveBlocks(peer, peer.BlockCapacity(blockTargetRTT))
if request == nil {
continue
}
if glog.V(logger.Detail) {
glog.Infof("%s: requesting %d blocks", peer, len(request.Hashes))
}
// Fetch the chunk and make sure any errors return the hashes to the queue
if err := peer.Fetch61(request); err != nil {
// Although we could try and make an attempt to fix this, this error really
// means that we've double allocated a fetch task to a peer. If that is the
// case, the internal state of the downloader and the queue is very wrong so
// better hard crash and note the error instead of silently accumulating into
// a much bigger issue.
panic(fmt.Sprintf("%v: fetch assignment failed", peer))
}
}
// Make sure that we have peers available for fetching. If all peers have been tried
// and all failed throw an error
if !throttled && !d.queue.InFlightBlocks() && len(idles) == total {
return errPeersUnavailable
}
case <-d.headerCh:
case <-d.bodyCh:
case <-d.stateCh:
case <-d.receiptCh:
// Ignore eth/{62,63} packets because this is eth/61.
// These can arrive as a late delivery from a previous sync.
}
}
}
// fetchHeight retrieves the head header of the remote peer to aid in estimating // fetchHeight retrieves the head header of the remote peer to aid in estimating
// the total time a pending synchronisation would take. // the total time a pending synchronisation would take.
func (d *Downloader) fetchHeight(p *peer) (*types.Header, error) { func (d *Downloader) fetchHeight(p *peer) (*types.Header, error) {
glog.V(logger.Debug).Infof("%v: retrieving remote chain height", p) glog.V(logger.Debug).Infof("%v: retrieving remote chain height", p)
// Request the advertised remote head block and wait for the response // Request the advertised remote head block and wait for the response
go p.getRelHeaders(p.head, 1, 0, false) head, _ := p.currentHead()
go p.getRelHeaders(head, 1, 0, false)
timeout := time.After(d.requestTTL()) timeout := time.After(d.requestTTL())
for { for {
@ -1021,11 +545,6 @@ func (d *Downloader) fetchHeight(p *peer) (*types.Header, error) {
case <-d.stateCh: case <-d.stateCh:
case <-d.receiptCh: case <-d.receiptCh:
// Out of bounds delivery, ignore // Out of bounds delivery, ignore
case <-d.hashCh:
case <-d.blockCh:
// Ignore eth/61 packets because this is eth/62+.
// These can arrive as a late delivery from a previous sync.
} }
} }
} }
@ -1036,7 +555,7 @@ func (d *Downloader) fetchHeight(p *peer) (*types.Header, error) {
// In the rare scenario when we ended up on a long reorganisation (i.e. none of // In the rare scenario when we ended up on a long reorganisation (i.e. none of
// the head links match), we do a binary search to find the common ancestor. // the head links match), we do a binary search to find the common ancestor.
func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) { func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
glog.V(logger.Debug).Infof("%v: looking for common ancestor", p) glog.V(logger.Debug).Infof("%v: looking for common ancestor (remote height %d)", p, height)
// Figure out the valid ancestor range to prevent rewrite attacks // Figure out the valid ancestor range to prevent rewrite attacks
floor, ceil := int64(-1), d.headHeader().Number.Uint64() floor, ceil := int64(-1), d.headHeader().Number.Uint64()
@ -1053,11 +572,17 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
if head > height { if head > height {
head = height head = height
} }
from := int64(head) - int64(MaxHeaderFetch) + 1 from := int64(head) - int64(MaxHeaderFetch)
if from < 0 { if from < 0 {
from = 0 from = 0
} }
go p.getAbsHeaders(uint64(from), MaxHeaderFetch, 0, false) // Span out with 15 block gaps into the future to catch bad head reports
limit := 2 * MaxHeaderFetch / 16
count := 1 + int((int64(ceil)-from)/16)
if count > limit {
count = limit
}
go p.getAbsHeaders(uint64(from), count, 15, false)
// Wait for the remote response to the head fetch // Wait for the remote response to the head fetch
number, hash := uint64(0), common.Hash{} number, hash := uint64(0), common.Hash{}
@ -1066,7 +591,7 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
for finished := false; !finished; { for finished := false; !finished; {
select { select {
case <-d.cancelCh: case <-d.cancelCh:
return 0, errCancelHashFetch return 0, errCancelHeaderFetch
case packet := <-d.headerCh: case packet := <-d.headerCh:
// Discard anything not from the origin peer // Discard anything not from the origin peer
@ -1082,12 +607,8 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
} }
// Make sure the peer's reply conforms to the request // Make sure the peer's reply conforms to the request
for i := 0; i < len(headers); i++ { for i := 0; i < len(headers); i++ {
if number := headers[i].Number.Int64(); number != from+int64(i) { if number := headers[i].Number.Int64(); number != from+int64(i)*16 {
glog.V(logger.Warn).Infof("%v: head header set (item %d) broke chain ordering: requested %d, got %d", p, i, from+int64(i), number) glog.V(logger.Warn).Infof("%v: head header set (item %d) broke chain ordering: requested %d, got %d", p, i, from+int64(i)*16, number)
return 0, errInvalidChain
}
if i > 0 && headers[i-1].Hash() != headers[i].ParentHash {
glog.V(logger.Warn).Infof("%v: head header set (item %d) broke chain ancestry: expected [%x], got [%x]", p, i, headers[i-1].Hash().Bytes()[:4], headers[i].ParentHash[:4])
return 0, errInvalidChain return 0, errInvalidChain
} }
} }
@ -1096,12 +617,18 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
for i := len(headers) - 1; i >= 0; i-- { for i := len(headers) - 1; i >= 0; i-- {
// Skip any headers that underflow/overflow our requested set // Skip any headers that underflow/overflow our requested set
if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > head { if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > ceil {
continue continue
} }
// Otherwise check if we already know the header or not // Otherwise check if we already know the header or not
if (d.mode == FullSync && d.hasBlockAndState(headers[i].Hash())) || (d.mode != FullSync && d.hasHeader(headers[i].Hash())) { if (d.mode == FullSync && d.hasBlockAndState(headers[i].Hash())) || (d.mode != FullSync && d.hasHeader(headers[i].Hash())) {
number, hash = headers[i].Number.Uint64(), headers[i].Hash() number, hash = headers[i].Number.Uint64(), headers[i].Hash()
// If every header is known, even future ones, the peer straight out lied about its head
if number > height && i == limit-1 {
glog.V(logger.Warn).Infof("%v: lied about chain head: reported %d, found above %d", p, height, number)
return 0, errStallingPeer
}
break break
} }
} }
@ -1114,11 +641,6 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
case <-d.stateCh: case <-d.stateCh:
case <-d.receiptCh: case <-d.receiptCh:
// Out of bounds delivery, ignore // Out of bounds delivery, ignore
case <-d.hashCh:
case <-d.blockCh:
// Ignore eth/61 packets because this is eth/62+.
// These can arrive as a late delivery from a previous sync.
} }
} }
// If the head fetch already found an ancestor, return // If the head fetch already found an ancestor, return
@ -1146,7 +668,7 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
for arrived := false; !arrived; { for arrived := false; !arrived; {
select { select {
case <-d.cancelCh: case <-d.cancelCh:
return 0, errCancelHashFetch return 0, errCancelHeaderFetch
case packer := <-d.headerCh: case packer := <-d.headerCh:
// Discard anything not from the origin peer // Discard anything not from the origin peer
@ -1182,11 +704,6 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
case <-d.stateCh: case <-d.stateCh:
case <-d.receiptCh: case <-d.receiptCh:
// Out of bounds delivery, ignore // Out of bounds delivery, ignore
case <-d.hashCh:
case <-d.blockCh:
// Ignore eth/61 packets because this is eth/62+.
// These can arrive as a late delivery from a previous sync.
} }
} }
} }
@ -1305,11 +822,6 @@ func (d *Downloader) fetchHeaders(p *peer, from uint64) error {
case <-d.cancelCh: case <-d.cancelCh:
} }
return errBadPeer return errBadPeer
case <-d.hashCh:
case <-d.blockCh:
// Ignore eth/61 packets because this is eth/62+.
// These can arrive as a late delivery from a previous sync.
} }
} }
} }
@ -1555,7 +1067,14 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv
// Check for fetch request timeouts and demote the responsible peers // Check for fetch request timeouts and demote the responsible peers
for pid, fails := range expire() { for pid, fails := range expire() {
if peer := d.peers.Peer(pid); peer != nil { if peer := d.peers.Peer(pid); peer != nil {
if fails > 1 { // If a lot of retrieval elements expired, we might have overestimated the remote peer or perhaps
// ourselves. Only reset to minimal throughput but don't drop just yet. If even the minimal times
// out that sync wise we need to get rid of the peer.
//
// The reason the minimum threshold is 2 is because the downloader tries to estimate the bandwidth
// and latency of a peer separately, which requires pushing the measures capacity a bit and seeing
// how response times reacts, to it always requests one more than the minimum (i.e. min 2).
if fails > 2 {
glog.V(logger.Detail).Infof("%s: %s delivery timeout", peer, strings.ToLower(kind)) glog.V(logger.Detail).Infof("%s: %s delivery timeout", peer, strings.ToLower(kind))
setIdle(peer, 0) setIdle(peer, 0)
} else { } else {
@ -1623,11 +1142,6 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv
if !progressed && !throttled && !running && len(idles) == total && pending() > 0 { if !progressed && !throttled && !running && len(idles) == total && pending() > 0 {
return errPeersUnavailable return errPeersUnavailable
} }
case <-d.hashCh:
case <-d.blockCh:
// Ignore eth/61 packets because this is eth/62+.
// These can arrive as a late delivery from a previous sync.
} }
} }
} }
@ -1859,7 +1373,7 @@ func (d *Downloader) processContent() error {
} }
if err != nil { if err != nil {
glog.V(logger.Debug).Infof("Result #%d [%x…] processing failed: %v", results[index].Header.Number, results[index].Header.Hash().Bytes()[:4], err) glog.V(logger.Debug).Infof("Result #%d [%x…] processing failed: %v", results[index].Header.Number, results[index].Header.Hash().Bytes()[:4], err)
return err return errInvalidChain
} }
// Shift the results to the next batch // Shift the results to the next batch
results = results[items:] results = results[items:]
@ -1867,19 +1381,6 @@ func (d *Downloader) processContent() error {
} }
} }
// DeliverHashes injects a new batch of hashes received from a remote node into
// the download schedule. This is usually invoked through the BlockHashesMsg by
// the protocol handler.
func (d *Downloader) DeliverHashes(id string, hashes []common.Hash) (err error) {
return d.deliver(id, d.hashCh, &hashPack{id, hashes}, hashInMeter, hashDropMeter)
}
// DeliverBlocks injects a new batch of blocks received from a remote node.
// This is usually invoked through the BlocksMsg by the protocol handler.
func (d *Downloader) DeliverBlocks(id string, blocks []*types.Block) (err error) {
return d.deliver(id, d.blockCh, &blockPack{id, blocks}, blockInMeter, blockDropMeter)
}
// DeliverHeaders injects a new batch of block headers received from a remote // DeliverHeaders injects a new batch of block headers received from a remote
// node into the download schedule. // node into the download schedule.
func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) { func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) {

View file

@ -55,7 +55,7 @@ func init() {
// reassembly. // reassembly.
func makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) { func makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) {
// Generate the block chain // Generate the block chain
blocks, receipts := core.GenerateChain(parent, testdb, n, func(i int, block *core.BlockGen) { blocks, receipts := core.GenerateChain(nil, parent, testdb, n, func(i int, block *core.BlockGen) {
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// If a heavy chain is requested, delay blocks to raise difficulty // If a heavy chain is requested, delay blocks to raise difficulty
@ -399,14 +399,12 @@ func (dl *downloadTester) newSlowPeer(id string, version int, hashes []common.Ha
var err error var err error
switch version { switch version {
case 61:
err = dl.downloader.RegisterPeer(id, version, hashes[0], dl.peerGetRelHashesFn(id, delay), dl.peerGetAbsHashesFn(id, delay), dl.peerGetBlocksFn(id, delay), nil, nil, nil, nil, nil)
case 62: case 62:
err = dl.downloader.RegisterPeer(id, version, hashes[0], nil, nil, nil, dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay), nil, nil) err = dl.downloader.RegisterPeer(id, version, dl.peerCurrentHeadFn(id), dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay), nil, nil)
case 63: case 63:
err = dl.downloader.RegisterPeer(id, version, hashes[0], nil, nil, nil, dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay), dl.peerGetReceiptsFn(id, delay), dl.peerGetNodeDataFn(id, delay)) err = dl.downloader.RegisterPeer(id, version, dl.peerCurrentHeadFn(id), dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay), dl.peerGetReceiptsFn(id, delay), dl.peerGetNodeDataFn(id, delay))
case 64: case 64:
err = dl.downloader.RegisterPeer(id, version, hashes[0], nil, nil, nil, dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay), dl.peerGetReceiptsFn(id, delay), dl.peerGetNodeDataFn(id, delay)) err = dl.downloader.RegisterPeer(id, version, dl.peerCurrentHeadFn(id), dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay), dl.peerGetReceiptsFn(id, delay), dl.peerGetNodeDataFn(id, delay))
} }
if err == nil { if err == nil {
// Assign the owned hashes, headers and blocks to the peer (deep copy) // Assign the owned hashes, headers and blocks to the peer (deep copy)
@ -465,83 +463,14 @@ func (dl *downloadTester) dropPeer(id string) {
dl.downloader.UnregisterPeer(id) dl.downloader.UnregisterPeer(id)
} }
// peerGetRelHashesFn constructs a GetHashes function associated with a specific // peerCurrentHeadFn constructs a function to retrieve a peer's current head hash
// peer in the download tester. The returned function can be used to retrieve // and total difficulty.
// batches of hashes from the particularly requested peer. func (dl *downloadTester) peerCurrentHeadFn(id string) func() (common.Hash, *big.Int) {
func (dl *downloadTester) peerGetRelHashesFn(id string, delay time.Duration) func(head common.Hash) error { return func() (common.Hash, *big.Int) {
return func(head common.Hash) error {
time.Sleep(delay)
dl.lock.RLock() dl.lock.RLock()
defer dl.lock.RUnlock() defer dl.lock.RUnlock()
// Gather the next batch of hashes return dl.peerHashes[id][0], nil
hashes := dl.peerHashes[id]
result := make([]common.Hash, 0, MaxHashFetch)
for i, hash := range hashes {
if hash == head {
i++
for len(result) < cap(result) && i < len(hashes) {
result = append(result, hashes[i])
i++
}
break
}
}
// Delay delivery a bit to allow attacks to unfold
go func() {
time.Sleep(time.Millisecond)
dl.downloader.DeliverHashes(id, result)
}()
return nil
}
}
// peerGetAbsHashesFn constructs a GetHashesFromNumber function associated with
// a particular peer in the download tester. The returned function can be used to
// retrieve batches of hashes from the particularly requested peer.
func (dl *downloadTester) peerGetAbsHashesFn(id string, delay time.Duration) func(uint64, int) error {
return func(head uint64, count int) error {
time.Sleep(delay)
dl.lock.RLock()
defer dl.lock.RUnlock()
// Gather the next batch of hashes
hashes := dl.peerHashes[id]
result := make([]common.Hash, 0, count)
for i := 0; i < count && len(hashes)-int(head)-1-i >= 0; i++ {
result = append(result, hashes[len(hashes)-int(head)-1-i])
}
// Delay delivery a bit to allow attacks to unfold
go func() {
time.Sleep(time.Millisecond)
dl.downloader.DeliverHashes(id, result)
}()
return nil
}
}
// peerGetBlocksFn constructs a getBlocks function associated with a particular
// peer in the download tester. The returned function can be used to retrieve
// batches of blocks from the particularly requested peer.
func (dl *downloadTester) peerGetBlocksFn(id string, delay time.Duration) func([]common.Hash) error {
return func(hashes []common.Hash) error {
time.Sleep(delay)
dl.lock.RLock()
defer dl.lock.RUnlock()
blocks := dl.peerBlocks[id]
result := make([]*types.Block, 0, len(hashes))
for _, hash := range hashes {
if block, ok := blocks[hash]; ok {
result = append(result, block)
}
}
go dl.downloader.DeliverBlocks(id, result)
return nil
} }
} }
@ -730,7 +659,6 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
// Tests that simple synchronization against a canonical chain works correctly. // Tests that simple synchronization against a canonical chain works correctly.
// In this test common ancestor lookup should be short circuited and not require // In this test common ancestor lookup should be short circuited and not require
// binary searching. // binary searching.
func TestCanonicalSynchronisation61(t *testing.T) { testCanonicalSynchronisation(t, 61, FullSync) }
func TestCanonicalSynchronisation62(t *testing.T) { testCanonicalSynchronisation(t, 62, FullSync) } func TestCanonicalSynchronisation62(t *testing.T) { testCanonicalSynchronisation(t, 62, FullSync) }
func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) } func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) }
func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) } func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) }
@ -759,7 +687,6 @@ func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
// Tests that if a large batch of blocks are being downloaded, it is throttled // Tests that if a large batch of blocks are being downloaded, it is throttled
// until the cached blocks are retrieved. // until the cached blocks are retrieved.
func TestThrottling61(t *testing.T) { testThrottling(t, 61, FullSync) }
func TestThrottling62(t *testing.T) { testThrottling(t, 62, FullSync) } func TestThrottling62(t *testing.T) { testThrottling(t, 62, FullSync) }
func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) } func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) }
func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) } func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) }
@ -845,7 +772,6 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
// Tests that simple synchronization against a forked chain works correctly. In // Tests that simple synchronization against a forked chain works correctly. In
// this test common ancestor lookup should *not* be short circuited, and a full // this test common ancestor lookup should *not* be short circuited, and a full
// binary search should be executed. // binary search should be executed.
func TestForkedSync61(t *testing.T) { testForkedSync(t, 61, FullSync) }
func TestForkedSync62(t *testing.T) { testForkedSync(t, 62, FullSync) } func TestForkedSync62(t *testing.T) { testForkedSync(t, 62, FullSync) }
func TestForkedSync63Full(t *testing.T) { testForkedSync(t, 63, FullSync) } func TestForkedSync63Full(t *testing.T) { testForkedSync(t, 63, FullSync) }
func TestForkedSync63Fast(t *testing.T) { testForkedSync(t, 63, FastSync) } func TestForkedSync63Fast(t *testing.T) { testForkedSync(t, 63, FastSync) }
@ -881,7 +807,6 @@ func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
// Tests that synchronising against a much shorter but much heavyer fork works // Tests that synchronising against a much shorter but much heavyer fork works
// corrently and is not dropped. // corrently and is not dropped.
func TestHeavyForkedSync61(t *testing.T) { testHeavyForkedSync(t, 61, FullSync) }
func TestHeavyForkedSync62(t *testing.T) { testHeavyForkedSync(t, 62, FullSync) } func TestHeavyForkedSync62(t *testing.T) { testHeavyForkedSync(t, 62, FullSync) }
func TestHeavyForkedSync63Full(t *testing.T) { testHeavyForkedSync(t, 63, FullSync) } func TestHeavyForkedSync63Full(t *testing.T) { testHeavyForkedSync(t, 63, FullSync) }
func TestHeavyForkedSync63Fast(t *testing.T) { testHeavyForkedSync(t, 63, FastSync) } func TestHeavyForkedSync63Fast(t *testing.T) { testHeavyForkedSync(t, 63, FastSync) }
@ -915,24 +840,9 @@ func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
assertOwnForkedChain(t, tester, common+1, []int{common + fork + 1, common + fork/2 + 1}) assertOwnForkedChain(t, tester, common+1, []int{common + fork + 1, common + fork/2 + 1})
} }
// Tests that an inactive downloader will not accept incoming hashes and blocks.
func TestInactiveDownloader61(t *testing.T) {
t.Parallel()
tester := newTester()
// Check that neither hashes nor blocks are accepted
if err := tester.downloader.DeliverHashes("bad peer", []common.Hash{}); err != errNoSyncActive {
t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive)
}
if err := tester.downloader.DeliverBlocks("bad peer", []*types.Block{}); err != errNoSyncActive {
t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive)
}
}
// Tests that chain forks are contained within a certain interval of the current // Tests that chain forks are contained within a certain interval of the current
// chain head, ensuring that malicious peers cannot waste resources by feeding // chain head, ensuring that malicious peers cannot waste resources by feeding
// long dead chains. // long dead chains.
func TestBoundedForkedSync61(t *testing.T) { testBoundedForkedSync(t, 61, FullSync) }
func TestBoundedForkedSync62(t *testing.T) { testBoundedForkedSync(t, 62, FullSync) } func TestBoundedForkedSync62(t *testing.T) { testBoundedForkedSync(t, 62, FullSync) }
func TestBoundedForkedSync63Full(t *testing.T) { testBoundedForkedSync(t, 63, FullSync) } func TestBoundedForkedSync63Full(t *testing.T) { testBoundedForkedSync(t, 63, FullSync) }
func TestBoundedForkedSync63Fast(t *testing.T) { testBoundedForkedSync(t, 63, FastSync) } func TestBoundedForkedSync63Fast(t *testing.T) { testBoundedForkedSync(t, 63, FastSync) }
@ -968,7 +878,6 @@ func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
// Tests that chain forks are contained within a certain interval of the current // Tests that chain forks are contained within a certain interval of the current
// chain head for short but heavy forks too. These are a bit special because they // chain head for short but heavy forks too. These are a bit special because they
// take different ancestor lookup paths. // take different ancestor lookup paths.
func TestBoundedHeavyForkedSync61(t *testing.T) { testBoundedHeavyForkedSync(t, 61, FullSync) }
func TestBoundedHeavyForkedSync62(t *testing.T) { testBoundedHeavyForkedSync(t, 62, FullSync) } func TestBoundedHeavyForkedSync62(t *testing.T) { testBoundedHeavyForkedSync(t, 62, FullSync) }
func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) } func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) }
func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) } func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) }
@ -1039,7 +948,6 @@ func TestInactiveDownloader63(t *testing.T) {
} }
// Tests that a canceled download wipes all previously accumulated state. // Tests that a canceled download wipes all previously accumulated state.
func TestCancel61(t *testing.T) { testCancel(t, 61, FullSync) }
func TestCancel62(t *testing.T) { testCancel(t, 62, FullSync) } func TestCancel62(t *testing.T) { testCancel(t, 62, FullSync) }
func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) } func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) }
func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) } func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) }
@ -1081,7 +989,6 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) {
} }
// Tests that synchronisation from multiple peers works as intended (multi thread sanity test). // Tests that synchronisation from multiple peers works as intended (multi thread sanity test).
func TestMultiSynchronisation61(t *testing.T) { testMultiSynchronisation(t, 61, FullSync) }
func TestMultiSynchronisation62(t *testing.T) { testMultiSynchronisation(t, 62, FullSync) } func TestMultiSynchronisation62(t *testing.T) { testMultiSynchronisation(t, 62, FullSync) }
func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) } func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) }
func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) } func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) }
@ -1112,7 +1019,6 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
// Tests that synchronisations behave well in multi-version protocol environments // Tests that synchronisations behave well in multi-version protocol environments
// and not wreak havoc on other nodes in the network. // and not wreak havoc on other nodes in the network.
func TestMultiProtoSynchronisation61(t *testing.T) { testMultiProtoSync(t, 61, FullSync) }
func TestMultiProtoSynchronisation62(t *testing.T) { testMultiProtoSync(t, 62, FullSync) } func TestMultiProtoSynchronisation62(t *testing.T) { testMultiProtoSync(t, 62, FullSync) }
func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) } func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) }
func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) } func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) }
@ -1131,7 +1037,6 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
tester := newTester() tester := newTester()
defer tester.terminate() defer tester.terminate()
tester.newPeer("peer 61", 61, hashes, nil, blocks, nil)
tester.newPeer("peer 62", 62, hashes, headers, blocks, nil) tester.newPeer("peer 62", 62, hashes, headers, blocks, nil)
tester.newPeer("peer 63", 63, hashes, headers, blocks, receipts) tester.newPeer("peer 63", 63, hashes, headers, blocks, receipts)
tester.newPeer("peer 64", 64, hashes, headers, blocks, receipts) tester.newPeer("peer 64", 64, hashes, headers, blocks, receipts)
@ -1143,7 +1048,7 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
assertOwnChain(t, tester, targetBlocks+1) assertOwnChain(t, tester, targetBlocks+1)
// Check that no peers have been dropped off // Check that no peers have been dropped off
for _, version := range []int{61, 62, 63, 64} { for _, version := range []int{62, 63, 64} {
peer := fmt.Sprintf("peer %d", version) peer := fmt.Sprintf("peer %d", version)
if _, ok := tester.peerHashes[peer]; !ok { if _, ok := tester.peerHashes[peer]; !ok {
t.Errorf("%s dropped", peer) t.Errorf("%s dropped", peer)
@ -1368,7 +1273,6 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
// Tests that a peer advertising an high TD doesn't get to stall the downloader // Tests that a peer advertising an high TD doesn't get to stall the downloader
// afterwards by not sending any useful hashes. // afterwards by not sending any useful hashes.
func TestHighTDStarvationAttack61(t *testing.T) { testHighTDStarvationAttack(t, 61, FullSync) }
func TestHighTDStarvationAttack62(t *testing.T) { testHighTDStarvationAttack(t, 62, FullSync) } func TestHighTDStarvationAttack62(t *testing.T) { testHighTDStarvationAttack(t, 62, FullSync) }
func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) } func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) }
func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) } func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) }
@ -1391,7 +1295,6 @@ func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) {
} }
// Tests that misbehaving peers are disconnected, whilst behaving ones are not. // Tests that misbehaving peers are disconnected, whilst behaving ones are not.
func TestBlockHeaderAttackerDropping61(t *testing.T) { testBlockHeaderAttackerDropping(t, 61) }
func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDropping(t, 62) } func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDropping(t, 62) }
func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) } func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) }
func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) }
@ -1409,7 +1312,6 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
{errStallingPeer, true}, // Peer was detected to be stalling, drop it {errStallingPeer, true}, // Peer was detected to be stalling, drop it
{errNoPeers, false}, // No peers to download from, soft race, no issue {errNoPeers, false}, // No peers to download from, soft race, no issue
{errTimeout, true}, // No hashes received in due time, drop the peer {errTimeout, true}, // No hashes received in due time, drop the peer
{errEmptyHashSet, true}, // No hashes were returned as a response, drop as it's a dead end
{errEmptyHeaderSet, true}, // No headers were returned as a response, drop as it's a dead end {errEmptyHeaderSet, true}, // No headers were returned as a response, drop as it's a dead end
{errPeersUnavailable, true}, // Nobody had the advertised blocks, drop the advertiser {errPeersUnavailable, true}, // Nobody had the advertised blocks, drop the advertiser
{errInvalidAncestor, true}, // Agreed upon ancestor is not acceptable, drop the chain rewriter {errInvalidAncestor, true}, // Agreed upon ancestor is not acceptable, drop the chain rewriter
@ -1417,7 +1319,6 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
{errInvalidBlock, false}, // A bad peer was detected, but not the sync origin {errInvalidBlock, false}, // A bad peer was detected, but not the sync origin
{errInvalidBody, false}, // A bad peer was detected, but not the sync origin {errInvalidBody, false}, // A bad peer was detected, but not the sync origin
{errInvalidReceipt, false}, // A bad peer was detected, but not the sync origin {errInvalidReceipt, false}, // A bad peer was detected, but not the sync origin
{errCancelHashFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop
{errCancelBlockFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop {errCancelBlockFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop
{errCancelHeaderFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop {errCancelHeaderFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop
{errCancelBodyFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop {errCancelBodyFetch, false}, // Synchronisation was canceled, origin may be innocent, don't drop
@ -1450,7 +1351,6 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
// Tests that synchronisation progress (origin block number, current block number // Tests that synchronisation progress (origin block number, current block number
// and highest block number) is tracked and updated correctly. // and highest block number) is tracked and updated correctly.
func TestSyncProgress61(t *testing.T) { testSyncProgress(t, 61, FullSync) }
func TestSyncProgress62(t *testing.T) { testSyncProgress(t, 62, FullSync) } func TestSyncProgress62(t *testing.T) { testSyncProgress(t, 62, FullSync) }
func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) } func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) }
func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) } func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) }
@ -1524,7 +1424,6 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
// Tests that synchronisation progress (origin block number and highest block // Tests that synchronisation progress (origin block number and highest block
// number) is tracked and updated correctly in case of a fork (or manual head // number) is tracked and updated correctly in case of a fork (or manual head
// revertal). // revertal).
func TestForkedSyncProgress61(t *testing.T) { testForkedSyncProgress(t, 61, FullSync) }
func TestForkedSyncProgress62(t *testing.T) { testForkedSyncProgress(t, 62, FullSync) } func TestForkedSyncProgress62(t *testing.T) { testForkedSyncProgress(t, 62, FullSync) }
func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) } func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) }
func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) } func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) }
@ -1601,7 +1500,6 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
// Tests that if synchronisation is aborted due to some failure, then the progress // Tests that if synchronisation is aborted due to some failure, then the progress
// origin is not updated in the next sync cycle, as it should be considered the // origin is not updated in the next sync cycle, as it should be considered the
// continuation of the previous sync and not a new instance. // continuation of the previous sync and not a new instance.
func TestFailedSyncProgress61(t *testing.T) { testFailedSyncProgress(t, 61, FullSync) }
func TestFailedSyncProgress62(t *testing.T) { testFailedSyncProgress(t, 62, FullSync) } func TestFailedSyncProgress62(t *testing.T) { testFailedSyncProgress(t, 62, FullSync) }
func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) } func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) }
func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) } func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) }
@ -1679,7 +1577,6 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
// Tests that if an attacker fakes a chain height, after the attack is detected, // Tests that if an attacker fakes a chain height, after the attack is detected,
// the progress height is successfully reduced at the next sync invocation. // the progress height is successfully reduced at the next sync invocation.
func TestFakedSyncProgress61(t *testing.T) { testFakedSyncProgress(t, 61, FullSync) }
func TestFakedSyncProgress62(t *testing.T) { testFakedSyncProgress(t, 62, FullSync) } func TestFakedSyncProgress62(t *testing.T) { testFakedSyncProgress(t, 62, FullSync) }
func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) } func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) }
func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) } func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) }

View file

@ -23,16 +23,6 @@ import (
) )
var ( var (
hashInMeter = metrics.NewMeter("eth/downloader/hashes/in")
hashReqTimer = metrics.NewTimer("eth/downloader/hashes/req")
hashDropMeter = metrics.NewMeter("eth/downloader/hashes/drop")
hashTimeoutMeter = metrics.NewMeter("eth/downloader/hashes/timeout")
blockInMeter = metrics.NewMeter("eth/downloader/blocks/in")
blockReqTimer = metrics.NewTimer("eth/downloader/blocks/req")
blockDropMeter = metrics.NewMeter("eth/downloader/blocks/drop")
blockTimeoutMeter = metrics.NewMeter("eth/downloader/blocks/timeout")
headerInMeter = metrics.NewMeter("eth/downloader/headers/in") headerInMeter = metrics.NewMeter("eth/downloader/headers/in")
headerReqTimer = metrics.NewTimer("eth/downloader/headers/req") headerReqTimer = metrics.NewTimer("eth/downloader/headers/req")
headerDropMeter = metrics.NewMeter("eth/downloader/headers/drop") headerDropMeter = metrics.NewMeter("eth/downloader/headers/drop")

View file

@ -23,6 +23,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"math/big"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -37,10 +38,8 @@ const (
measurementImpact = 0.1 // The impact a single measurement has on a peer's final throughput value. measurementImpact = 0.1 // The impact a single measurement has on a peer's final throughput value.
) )
// Hash and block fetchers belonging to eth/61 and below // Head hash and total difficulty retriever for
type relativeHashFetcherFn func(common.Hash) error type currentHeadRetrievalFn func() (common.Hash, *big.Int)
type absoluteHashFetcherFn func(uint64, int) error
type blockFetcherFn func([]common.Hash) error
// Block header and body fetchers belonging to eth/62 and above // Block header and body fetchers belonging to eth/62 and above
type relativeHeaderFetcherFn func(common.Hash, int, int, bool) error type relativeHeaderFetcherFn func(common.Hash, int, int, bool) error
@ -58,7 +57,6 @@ var (
// peer represents an active peer from which hashes and blocks are retrieved. // peer represents an active peer from which hashes and blocks are retrieved.
type peer struct { type peer struct {
id string // Unique identifier of the peer id string // Unique identifier of the peer
head common.Hash // Hash of the peers latest known block
headerIdle int32 // Current header activity state of the peer (idle = 0, active = 1) headerIdle int32 // Current header activity state of the peer (idle = 0, active = 1)
blockIdle int32 // Current block activity state of the peer (idle = 0, active = 1) blockIdle int32 // Current block activity state of the peer (idle = 0, active = 1)
@ -79,9 +77,7 @@ type peer struct {
lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously) lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
getRelHashes relativeHashFetcherFn // [eth/61] Method to retrieve a batch of hashes from an origin hash currentHead currentHeadRetrievalFn // Method to fetch the currently known head of the peer
getAbsHashes absoluteHashFetcherFn // [eth/61] Method to retrieve a batch of hashes from an absolute position
getBlocks blockFetcherFn // [eth/61] Method to retrieve a batch of blocks
getRelHeaders relativeHeaderFetcherFn // [eth/62] Method to retrieve a batch of headers from an origin hash getRelHeaders relativeHeaderFetcherFn // [eth/62] Method to retrieve a batch of headers from an origin hash
getAbsHeaders absoluteHeaderFetcherFn // [eth/62] Method to retrieve a batch of headers from an absolute position getAbsHeaders absoluteHeaderFetcherFn // [eth/62] Method to retrieve a batch of headers from an absolute position
@ -96,19 +92,14 @@ type peer struct {
// newPeer create a new downloader peer, with specific hash and block retrieval // newPeer create a new downloader peer, with specific hash and block retrieval
// mechanisms. // mechanisms.
func newPeer(id string, version int, head common.Hash, func newPeer(id string, version int, currentHead currentHeadRetrievalFn,
getRelHashes relativeHashFetcherFn, getAbsHashes absoluteHashFetcherFn, getBlocks blockFetcherFn, // eth/61 callbacks, remove when upgrading
getRelHeaders relativeHeaderFetcherFn, getAbsHeaders absoluteHeaderFetcherFn, getBlockBodies blockBodyFetcherFn, getRelHeaders relativeHeaderFetcherFn, getAbsHeaders absoluteHeaderFetcherFn, getBlockBodies blockBodyFetcherFn,
getReceipts receiptFetcherFn, getNodeData stateFetcherFn) *peer { getReceipts receiptFetcherFn, getNodeData stateFetcherFn) *peer {
return &peer{ return &peer{
id: id, id: id,
head: head,
lacking: make(map[common.Hash]struct{}), lacking: make(map[common.Hash]struct{}),
getRelHashes: getRelHashes, currentHead: currentHead,
getAbsHashes: getAbsHashes,
getBlocks: getBlocks,
getRelHeaders: getRelHeaders, getRelHeaders: getRelHeaders,
getAbsHeaders: getAbsHeaders, getAbsHeaders: getAbsHeaders,
getBlockBodies: getBlockBodies, getBlockBodies: getBlockBodies,
@ -138,28 +129,6 @@ func (p *peer) Reset() {
p.lacking = make(map[common.Hash]struct{}) p.lacking = make(map[common.Hash]struct{})
} }
// Fetch61 sends a block retrieval request to the remote peer.
func (p *peer) Fetch61(request *fetchRequest) error {
// Sanity check the protocol version
if p.version != 61 {
panic(fmt.Sprintf("block fetch [eth/61] requested on eth/%d", p.version))
}
// Short circuit if the peer is already fetching
if !atomic.CompareAndSwapInt32(&p.blockIdle, 0, 1) {
return errAlreadyFetching
}
p.blockStarted = time.Now()
// Convert the hash set to a retrievable slice
hashes := make([]common.Hash, 0, len(request.Hashes))
for hash, _ := range request.Hashes {
hashes = append(hashes, hash)
}
go p.getBlocks(hashes)
return nil
}
// FetchHeaders sends a header retrieval request to the remote peer. // FetchHeaders sends a header retrieval request to the remote peer.
func (p *peer) FetchHeaders(from uint64, count int) error { func (p *peer) FetchHeaders(from uint64, count int) error {
// Sanity check the protocol version // Sanity check the protocol version
@ -481,20 +450,6 @@ func (ps *peerSet) AllPeers() []*peer {
return list return list
} }
// BlockIdlePeers retrieves a flat list of all the currently idle peers within the
// active peer set, ordered by their reputation.
func (ps *peerSet) BlockIdlePeers() ([]*peer, int) {
idle := func(p *peer) bool {
return atomic.LoadInt32(&p.blockIdle) == 0
}
throughput := func(p *peer) float64 {
p.lock.RLock()
defer p.lock.RUnlock()
return p.blockThroughput
}
return ps.idlePeers(61, 61, idle, throughput)
}
// HeaderIdlePeers retrieves a flat list of all the currently header-idle peers // HeaderIdlePeers retrieves a flat list of all the currently header-idle peers
// within the active peer set, ordered by their reputation. // within the active peer set, ordered by their reputation.
func (ps *peerSet) HeaderIdlePeers() ([]*peer, int) { func (ps *peerSet) HeaderIdlePeers() ([]*peer, int) {

View file

@ -45,7 +45,6 @@ var (
var ( var (
errNoFetchesPending = errors.New("no fetches pending") errNoFetchesPending = errors.New("no fetches pending")
errStateSyncPending = errors.New("state trie sync already scheduled")
errStaleDelivery = errors.New("stale delivery") errStaleDelivery = errors.New("stale delivery")
) )
@ -74,10 +73,6 @@ type queue struct {
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
fastSyncPivot uint64 // Block number where the fast sync pivots into archive synchronisation mode fastSyncPivot uint64 // Block number where the fast sync pivots into archive synchronisation mode
hashPool map[common.Hash]int // [eth/61] Pending hashes, mapping to their insertion index (priority)
hashQueue *prque.Prque // [eth/61] Priority queue of the block hashes to fetch
hashCounter int // [eth/61] Counter indexing the added hashes to ensure retrieval order
headerHead common.Hash // [eth/62] Hash of the last queued header to verify order headerHead common.Hash // [eth/62] Hash of the last queued header to verify order
// Headers are "special", they download in batches, supported by a skeleton chain // Headers are "special", they download in batches, supported by a skeleton chain
@ -85,7 +80,6 @@ type queue struct {
headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for
headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable
headerPendPool map[string]*fetchRequest // [eth/62] Currently pending header retrieval operations headerPendPool map[string]*fetchRequest // [eth/62] Currently pending header retrieval operations
headerDonePool map[uint64]struct{} // [eth/62] Set of the completed header fetches
headerResults []*types.Header // [eth/62] Result cache accumulating the completed headers headerResults []*types.Header // [eth/62] Result cache accumulating the completed headers
headerProced int // [eth/62] Number of headers already processed from the results headerProced int // [eth/62] Number of headers already processed from the results
headerOffset uint64 // [eth/62] Number of the first header in the result cache headerOffset uint64 // [eth/62] Number of the first header in the result cache
@ -124,8 +118,6 @@ type queue struct {
func newQueue(stateDb ethdb.Database) *queue { func newQueue(stateDb ethdb.Database) *queue {
lock := new(sync.Mutex) lock := new(sync.Mutex)
return &queue{ return &queue{
hashPool: make(map[common.Hash]int),
hashQueue: prque.New(),
headerPendPool: make(map[string]*fetchRequest), headerPendPool: make(map[string]*fetchRequest),
headerContCh: make(chan bool), headerContCh: make(chan bool),
blockTaskPool: make(map[common.Hash]*types.Header), blockTaskPool: make(map[common.Hash]*types.Header),
@ -158,10 +150,6 @@ func (q *queue) Reset() {
q.mode = FullSync q.mode = FullSync
q.fastSyncPivot = 0 q.fastSyncPivot = 0
q.hashPool = make(map[common.Hash]int)
q.hashQueue.Reset()
q.hashCounter = 0
q.headerHead = common.Hash{} q.headerHead = common.Hash{}
q.headerPendPool = make(map[string]*fetchRequest) q.headerPendPool = make(map[string]*fetchRequest)
@ -208,7 +196,7 @@ func (q *queue) PendingBlocks() int {
q.lock.Lock() q.lock.Lock()
defer q.lock.Unlock() defer q.lock.Unlock()
return q.hashQueue.Size() + q.blockTaskQueue.Size() return q.blockTaskQueue.Size()
} }
// PendingReceipts retrieves the number of block receipts pending for retrieval. // PendingReceipts retrieves the number of block receipts pending for retrieval.
@ -272,7 +260,7 @@ func (q *queue) Idle() bool {
q.lock.Lock() q.lock.Lock()
defer q.lock.Unlock() defer q.lock.Unlock()
queued := q.hashQueue.Size() + q.blockTaskQueue.Size() + q.receiptTaskQueue.Size() + q.stateTaskQueue.Size() queued := q.blockTaskQueue.Size() + q.receiptTaskQueue.Size() + q.stateTaskQueue.Size()
pending := len(q.blockPendPool) + len(q.receiptPendPool) + len(q.statePendPool) pending := len(q.blockPendPool) + len(q.receiptPendPool) + len(q.statePendPool)
cached := len(q.blockDonePool) + len(q.receiptDonePool) cached := len(q.blockDonePool) + len(q.receiptDonePool)
@ -323,34 +311,6 @@ func (q *queue) ShouldThrottleReceipts() bool {
return pending >= len(q.resultCache)-len(q.receiptDonePool) return pending >= len(q.resultCache)-len(q.receiptDonePool)
} }
// Schedule61 adds a set of hashes for the download queue for scheduling, returning
// the new hashes encountered.
func (q *queue) Schedule61(hashes []common.Hash, fifo bool) []common.Hash {
q.lock.Lock()
defer q.lock.Unlock()
// Insert all the hashes prioritised in the arrival order
inserts := make([]common.Hash, 0, len(hashes))
for _, hash := range hashes {
// Skip anything we already have
if old, ok := q.hashPool[hash]; ok {
glog.V(logger.Warn).Infof("Hash %x already scheduled at index %v", hash, old)
continue
}
// Update the counters and insert the hash
q.hashCounter = q.hashCounter + 1
inserts = append(inserts, hash)
q.hashPool[hash] = q.hashCounter
if fifo {
q.hashQueue.Push(hash, -float32(q.hashCounter)) // Lowest gets schedules first
} else {
q.hashQueue.Push(hash, float32(q.hashCounter)) // Highest gets schedules first
}
}
return inserts
}
// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill // ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
// up an already retrieved header skeleton. // up an already retrieved header skeleton.
func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) { func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) {
@ -550,15 +510,6 @@ func (q *queue) ReserveHeaders(p *peer, count int) *fetchRequest {
return request return request
} }
// ReserveBlocks reserves a set of block hashes for the given peer, skipping any
// previously failed download.
func (q *queue) ReserveBlocks(p *peer, count int) *fetchRequest {
q.lock.Lock()
defer q.lock.Unlock()
return q.reserveHashes(p, count, q.hashQueue, nil, q.blockPendPool, len(q.resultCache)-len(q.blockDonePool))
}
// ReserveNodeData reserves a set of node data hashes for the given peer, skipping // ReserveNodeData reserves a set of node data hashes for the given peer, skipping
// any previously failed download. // any previously failed download.
func (q *queue) ReserveNodeData(p *peer, count int) *fetchRequest { func (q *queue) ReserveNodeData(p *peer, count int) *fetchRequest {
@ -753,11 +704,6 @@ func (q *queue) CancelHeaders(request *fetchRequest) {
q.cancel(request, q.headerTaskQueue, q.headerPendPool) q.cancel(request, q.headerTaskQueue, q.headerPendPool)
} }
// CancelBlocks aborts a fetch request, returning all pending hashes to the queue.
func (q *queue) CancelBlocks(request *fetchRequest) {
q.cancel(request, q.hashQueue, q.blockPendPool)
}
// CancelBodies aborts a body fetch request, returning all pending headers to the // CancelBodies aborts a body fetch request, returning all pending headers to the
// task queue. // task queue.
func (q *queue) CancelBodies(request *fetchRequest) { func (q *queue) CancelBodies(request *fetchRequest) {
@ -801,9 +747,6 @@ func (q *queue) Revoke(peerId string) {
defer q.lock.Unlock() defer q.lock.Unlock()
if request, ok := q.blockPendPool[peerId]; ok { if request, ok := q.blockPendPool[peerId]; ok {
for hash, index := range request.Hashes {
q.hashQueue.Push(hash, float32(index))
}
for _, header := range request.Headers { for _, header := range request.Headers {
q.blockTaskQueue.Push(header, -float32(header.Number.Uint64())) q.blockTaskQueue.Push(header, -float32(header.Number.Uint64()))
} }
@ -832,15 +775,6 @@ func (q *queue) ExpireHeaders(timeout time.Duration) map[string]int {
return q.expire(timeout, q.headerPendPool, q.headerTaskQueue, headerTimeoutMeter) return q.expire(timeout, q.headerPendPool, q.headerTaskQueue, headerTimeoutMeter)
} }
// ExpireBlocks checks for in flight requests that exceeded a timeout allowance,
// canceling them and returning the responsible peers for penalisation.
func (q *queue) ExpireBlocks(timeout time.Duration) map[string]int {
q.lock.Lock()
defer q.lock.Unlock()
return q.expire(timeout, q.blockPendPool, q.hashQueue, blockTimeoutMeter)
}
// ExpireBodies checks for in flight block body requests that exceeded a timeout // ExpireBodies checks for in flight block body requests that exceeded a timeout
// allowance, canceling them and returning the responsible peers for penalisation. // allowance, canceling them and returning the responsible peers for penalisation.
func (q *queue) ExpireBodies(timeout time.Duration) map[string]int { func (q *queue) ExpireBodies(timeout time.Duration) map[string]int {
@ -907,74 +841,6 @@ func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest,
return expiries return expiries
} }
// DeliverBlocks injects a block retrieval response into the download queue. The
// method returns the number of blocks accepted from the delivery and also wakes
// any threads waiting for data delivery.
func (q *queue) DeliverBlocks(id string, blocks []*types.Block) (int, error) {
q.lock.Lock()
defer q.lock.Unlock()
// Short circuit if the blocks were never requested
request := q.blockPendPool[id]
if request == nil {
return 0, errNoFetchesPending
}
blockReqTimer.UpdateSince(request.Time)
delete(q.blockPendPool, id)
// If no blocks were retrieved, mark them as unavailable for the origin peer
if len(blocks) == 0 {
for hash, _ := range request.Hashes {
request.Peer.MarkLacking(hash)
}
}
// Iterate over the downloaded blocks and add each of them
accepted, errs := 0, make([]error, 0)
for _, block := range blocks {
// Skip any blocks that were not requested
hash := block.Hash()
if _, ok := request.Hashes[hash]; !ok {
errs = append(errs, fmt.Errorf("non-requested block %x", hash))
continue
}
// Reconstruct the next result if contents match up
index := int(block.Number().Int64() - int64(q.resultOffset))
if index >= len(q.resultCache) || index < 0 {
errs = []error{errInvalidChain}
break
}
q.resultCache[index] = &fetchResult{
Header: block.Header(),
Transactions: block.Transactions(),
Uncles: block.Uncles(),
}
q.blockDonePool[block.Hash()] = struct{}{}
delete(request.Hashes, hash)
delete(q.hashPool, hash)
accepted++
}
// Return all failed or missing fetches to the queue
for hash, index := range request.Hashes {
q.hashQueue.Push(hash, float32(index))
}
// Wake up WaitResults
if accepted > 0 {
q.active.Signal()
}
// If none of the blocks were good, it's a stale delivery
switch {
case len(errs) == 0:
return accepted, nil
case len(errs) == 1 && (errs[0] == errInvalidChain || errs[0] == errInvalidBlock):
return accepted, errs[0]
case len(errs) == len(blocks):
return accepted, errStaleDelivery
default:
return accepted, fmt.Errorf("multiple failures: %v", errs)
}
}
// DeliverHeaders injects a header retrieval response into the header results // DeliverHeaders injects a header retrieval response into the header results
// cache. This method either accepts all headers it received, or none of them // cache. This method either accepts all headers it received, or none of them
// if they do not map correctly to the skeleton. // if they do not map correctly to the skeleton.

View file

@ -73,26 +73,6 @@ type dataPack interface {
Stats() string Stats() string
} }
// hashPack is a batch of block hashes returned by a peer (eth/61).
type hashPack struct {
peerId string
hashes []common.Hash
}
func (p *hashPack) PeerId() string { return p.peerId }
func (p *hashPack) Items() int { return len(p.hashes) }
func (p *hashPack) Stats() string { return fmt.Sprintf("%d", len(p.hashes)) }
// blockPack is a batch of blocks returned by a peer (eth/61).
type blockPack struct {
peerId string
blocks []*types.Block
}
func (p *blockPack) PeerId() string { return p.peerId }
func (p *blockPack) Items() int { return len(p.blocks) }
func (p *blockPack) Stats() string { return fmt.Sprintf("%d", len(p.blocks)) }
// headerPack is a batch of block headers returned by a peer. // headerPack is a batch of block headers returned by a peer.
type headerPack struct { type headerPack struct {
peerId string peerId string

View file

@ -48,9 +48,6 @@ var (
// blockRetrievalFn is a callback type for retrieving a block from the local chain. // blockRetrievalFn is a callback type for retrieving a block from the local chain.
type blockRetrievalFn func(common.Hash) *types.Block type blockRetrievalFn func(common.Hash) *types.Block
// blockRequesterFn is a callback type for sending a block retrieval request.
type blockRequesterFn func([]common.Hash) error
// headerRequesterFn is a callback type for sending a header retrieval request. // headerRequesterFn is a callback type for sending a header retrieval request.
type headerRequesterFn func(common.Hash) error type headerRequesterFn func(common.Hash) error
@ -82,7 +79,6 @@ type announce struct {
origin string // Identifier of the peer originating the notification origin string // Identifier of the peer originating the notification
fetch61 blockRequesterFn // [eth/61] Fetcher function to retrieve an announced block
fetchHeader headerRequesterFn // [eth/62] Fetcher function to retrieve the header of an announced block fetchHeader headerRequesterFn // [eth/62] Fetcher function to retrieve the header of an announced block
fetchBodies bodyRequesterFn // [eth/62] Fetcher function to retrieve the body of an announced block fetchBodies bodyRequesterFn // [eth/62] Fetcher function to retrieve the body of an announced block
} }
@ -191,14 +187,12 @@ func (f *Fetcher) Stop() {
// Notify announces the fetcher of the potential availability of a new block in // Notify announces the fetcher of the potential availability of a new block in
// the network. // the network.
func (f *Fetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time, func (f *Fetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time,
blockFetcher blockRequesterFn, // eth/61 specific whole block fetcher
headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error { headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error {
block := &announce{ block := &announce{
hash: hash, hash: hash,
number: number, number: number,
time: time, time: time,
origin: peer, origin: peer,
fetch61: blockFetcher,
fetchHeader: headerFetcher, fetchHeader: headerFetcher,
fetchBodies: bodyFetcher, fetchBodies: bodyFetcher,
} }
@ -224,34 +218,6 @@ func (f *Fetcher) Enqueue(peer string, block *types.Block) error {
} }
} }
// FilterBlocks extracts all the blocks that were explicitly requested by the fetcher,
// returning those that should be handled differently.
func (f *Fetcher) FilterBlocks(blocks types.Blocks) types.Blocks {
glog.V(logger.Detail).Infof("[eth/61] filtering %d blocks", len(blocks))
// Send the filter channel to the fetcher
filter := make(chan []*types.Block)
select {
case f.blockFilter <- filter:
case <-f.quit:
return nil
}
// Request the filtering of the block list
select {
case filter <- blocks:
case <-f.quit:
return nil
}
// Retrieve the blocks remaining after filtering
select {
case blocks := <-filter:
return blocks
case <-f.quit:
return nil
}
}
// FilterHeaders extracts all the headers that were explicitly requested by the fetcher, // FilterHeaders extracts all the headers that were explicitly requested by the fetcher,
// returning those that should be handled differently. // returning those that should be handled differently.
func (f *Fetcher) FilterHeaders(headers []*types.Header, time time.Time) []*types.Header { func (f *Fetcher) FilterHeaders(headers []*types.Header, time time.Time) []*types.Header {
@ -413,7 +379,7 @@ func (f *Fetcher) loop() {
} }
} }
} }
// Send out all block (eth/61) or header (eth/62) requests // Send out all block header requests
for peer, hashes := range request { for peer, hashes := range request {
if glog.V(logger.Detail) && len(hashes) > 0 { if glog.V(logger.Detail) && len(hashes) > 0 {
list := "[" list := "["
@ -421,30 +387,18 @@ func (f *Fetcher) loop() {
list += fmt.Sprintf("%x…, ", hash[:4]) list += fmt.Sprintf("%x…, ", hash[:4])
} }
list = list[:len(list)-2] + "]" list = list[:len(list)-2] + "]"
if f.fetching[hashes[0]].fetch61 != nil {
glog.V(logger.Detail).Infof("[eth/61] Peer %s: fetching blocks %s", peer, list)
} else {
glog.V(logger.Detail).Infof("[eth/62] Peer %s: fetching headers %s", peer, list) glog.V(logger.Detail).Infof("[eth/62] Peer %s: fetching headers %s", peer, list)
} }
}
// Create a closure of the fetch and schedule in on a new thread // Create a closure of the fetch and schedule in on a new thread
fetchBlocks, fetchHeader, hashes := f.fetching[hashes[0]].fetch61, f.fetching[hashes[0]].fetchHeader, hashes fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes
go func() { go func() {
if f.fetchingHook != nil { if f.fetchingHook != nil {
f.fetchingHook(hashes) f.fetchingHook(hashes)
} }
if fetchBlocks != nil {
// Use old eth/61 protocol to retrieve whole blocks
blockFetchMeter.Mark(int64(len(hashes)))
fetchBlocks(hashes)
} else {
// Use new eth/62 protocol to retrieve headers first
for _, hash := range hashes { for _, hash := range hashes {
headerFetchMeter.Mark(1) headerFetchMeter.Mark(1)
fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals
} }
}
}() }()
} }
// Schedule the next fetch if blocks are still pending // Schedule the next fetch if blocks are still pending
@ -486,46 +440,6 @@ func (f *Fetcher) loop() {
// Schedule the next fetch if blocks are still pending // Schedule the next fetch if blocks are still pending
f.rescheduleComplete(completeTimer) f.rescheduleComplete(completeTimer)
case filter := <-f.blockFilter:
// Blocks arrived, extract any explicit fetches, return all else
var blocks types.Blocks
select {
case blocks = <-filter:
case <-f.quit:
return
}
blockFilterInMeter.Mark(int64(len(blocks)))
explicit, download := []*types.Block{}, []*types.Block{}
for _, block := range blocks {
hash := block.Hash()
// Filter explicitly requested blocks from hash announcements
if f.fetching[hash] != nil && f.queued[hash] == nil {
// Discard if already imported by other means
if f.getBlock(hash) == nil {
explicit = append(explicit, block)
} else {
f.forgetHash(hash)
}
} else {
download = append(download, block)
}
}
blockFilterOutMeter.Mark(int64(len(download)))
select {
case filter <- download:
case <-f.quit:
return
}
// Schedule the retrieved blocks for ordered import
for _, block := range explicit {
if announce := f.fetching[block.Hash()]; announce != nil {
f.enqueue(announce.origin, block)
}
}
case filter := <-f.headerFilter: case filter := <-f.headerFilter:
// Headers arrived from a remote peer. Extract those that were explicitly // Headers arrived from a remote peer. Extract those that were explicitly
// requested by the fetcher, and return everything else so it's delivered // requested by the fetcher, and return everything else so it's delivered

View file

@ -46,7 +46,7 @@ var (
// contains a transaction and every 5th an uncle to allow testing correct block // contains a transaction and every 5th an uncle to allow testing correct block
// reassembly. // reassembly.
func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) {
blocks, _ := core.GenerateChain(parent, testdb, n, func(i int, block *core.BlockGen) { blocks, _ := core.GenerateChain(nil, parent, testdb, n, func(i int, block *core.BlockGen) {
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// If the block number is multiple of 3, send a bonus transaction to the miner // If the block number is multiple of 3, send a bonus transaction to the miner
@ -152,28 +152,6 @@ func (f *fetcherTester) dropPeer(peer string) {
f.drops[peer] = true f.drops[peer] = true
} }
// makeBlockFetcher retrieves a block fetcher associated with a simulated peer.
func (f *fetcherTester) makeBlockFetcher(blocks map[common.Hash]*types.Block) blockRequesterFn {
closure := make(map[common.Hash]*types.Block)
for hash, block := range blocks {
closure[hash] = block
}
// Create a function that returns blocks from the closure
return func(hashes []common.Hash) error {
// Gather the blocks to return
blocks := make([]*types.Block, 0, len(hashes))
for _, hash := range hashes {
if block, ok := closure[hash]; ok {
blocks = append(blocks, block)
}
}
// Return on a new thread
go f.fetcher.FilterBlocks(blocks)
return nil
}
}
// makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer. // makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer.
func (f *fetcherTester) makeHeaderFetcher(blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn { func (f *fetcherTester) makeHeaderFetcher(blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn {
closure := make(map[common.Hash]*types.Block) closure := make(map[common.Hash]*types.Block)
@ -294,7 +272,6 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
// Tests that a fetcher accepts block announcements and initiates retrievals for // Tests that a fetcher accepts block announcements and initiates retrievals for
// them, successfully importing into the local chain. // them, successfully importing into the local chain.
func TestSequentialAnnouncements61(t *testing.T) { testSequentialAnnouncements(t, 61) }
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) } func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
func TestSequentialAnnouncements63(t *testing.T) { testSequentialAnnouncements(t, 63) } func TestSequentialAnnouncements63(t *testing.T) { testSequentialAnnouncements(t, 63) }
func TestSequentialAnnouncements64(t *testing.T) { testSequentialAnnouncements(t, 64) } func TestSequentialAnnouncements64(t *testing.T) { testSequentialAnnouncements(t, 64) }
@ -305,7 +282,6 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
hashes, blocks := makeChain(targetBlocks, 0, genesis) hashes, blocks := makeChain(targetBlocks, 0, genesis)
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
@ -314,11 +290,7 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
tester.fetcher.importedHook = func(block *types.Block) { imported <- block } tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
for i := len(hashes) - 2; i >= 0; i-- { for i := len(hashes) - 2; i >= 0; i-- {
if protocol < 62 { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
tester.fetcher.Notify("valid", hashes[i], 0, time.Now().Add(-arriveTimeout), blockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher)
}
verifyImportEvent(t, imported, true) verifyImportEvent(t, imported, true)
} }
verifyImportDone(t, imported) verifyImportDone(t, imported)
@ -326,7 +298,6 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
// Tests that if blocks are announced by multiple peers (or even the same buggy // Tests that if blocks are announced by multiple peers (or even the same buggy
// peer), they will only get downloaded at most once. // peer), they will only get downloaded at most once.
func TestConcurrentAnnouncements61(t *testing.T) { testConcurrentAnnouncements(t, 61) }
func TestConcurrentAnnouncements62(t *testing.T) { testConcurrentAnnouncements(t, 62) } func TestConcurrentAnnouncements62(t *testing.T) { testConcurrentAnnouncements(t, 62) }
func TestConcurrentAnnouncements63(t *testing.T) { testConcurrentAnnouncements(t, 63) } func TestConcurrentAnnouncements63(t *testing.T) { testConcurrentAnnouncements(t, 63) }
func TestConcurrentAnnouncements64(t *testing.T) { testConcurrentAnnouncements(t, 64) } func TestConcurrentAnnouncements64(t *testing.T) { testConcurrentAnnouncements(t, 64) }
@ -338,15 +309,10 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
// Assemble a tester with a built in counter for the requests // Assemble a tester with a built in counter for the requests
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
counter := uint32(0) counter := uint32(0)
blockWrapper := func(hashes []common.Hash) error {
atomic.AddUint32(&counter, uint32(len(hashes)))
return blockFetcher(hashes)
}
headerWrapper := func(hash common.Hash) error { headerWrapper := func(hash common.Hash) error {
atomic.AddUint32(&counter, 1) atomic.AddUint32(&counter, 1)
return headerFetcher(hash) return headerFetcher(hash)
@ -356,15 +322,9 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
tester.fetcher.importedHook = func(block *types.Block) { imported <- block } tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
for i := len(hashes) - 2; i >= 0; i-- { for i := len(hashes) - 2; i >= 0; i-- {
if protocol < 62 { tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
tester.fetcher.Notify("first", hashes[i], 0, time.Now().Add(-arriveTimeout), blockWrapper, nil, nil) tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), headerWrapper, bodyFetcher)
tester.fetcher.Notify("second", hashes[i], 0, time.Now().Add(-arriveTimeout+time.Millisecond), blockWrapper, nil, nil) tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), headerWrapper, bodyFetcher)
tester.fetcher.Notify("second", hashes[i], 0, time.Now().Add(-arriveTimeout-time.Millisecond), blockWrapper, nil, nil)
} else {
tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, headerWrapper, bodyFetcher)
tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), nil, headerWrapper, bodyFetcher)
tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), nil, headerWrapper, bodyFetcher)
}
verifyImportEvent(t, imported, true) verifyImportEvent(t, imported, true)
} }
verifyImportDone(t, imported) verifyImportDone(t, imported)
@ -377,7 +337,6 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
// Tests that announcements arriving while a previous is being fetched still // Tests that announcements arriving while a previous is being fetched still
// results in a valid import. // results in a valid import.
func TestOverlappingAnnouncements61(t *testing.T) { testOverlappingAnnouncements(t, 61) }
func TestOverlappingAnnouncements62(t *testing.T) { testOverlappingAnnouncements(t, 62) } func TestOverlappingAnnouncements62(t *testing.T) { testOverlappingAnnouncements(t, 62) }
func TestOverlappingAnnouncements63(t *testing.T) { testOverlappingAnnouncements(t, 63) } func TestOverlappingAnnouncements63(t *testing.T) { testOverlappingAnnouncements(t, 63) }
func TestOverlappingAnnouncements64(t *testing.T) { testOverlappingAnnouncements(t, 64) } func TestOverlappingAnnouncements64(t *testing.T) { testOverlappingAnnouncements(t, 64) }
@ -388,7 +347,6 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
hashes, blocks := makeChain(targetBlocks, 0, genesis) hashes, blocks := makeChain(targetBlocks, 0, genesis)
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
@ -401,11 +359,7 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
tester.fetcher.importedHook = func(block *types.Block) { imported <- block } tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
for i := len(hashes) - 2; i >= 0; i-- { for i := len(hashes) - 2; i >= 0; i-- {
if protocol < 62 { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
tester.fetcher.Notify("valid", hashes[i], 0, time.Now().Add(-arriveTimeout), blockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher)
}
select { select {
case <-imported: case <-imported:
case <-time.After(time.Second): case <-time.After(time.Second):
@ -417,7 +371,6 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
} }
// Tests that announces already being retrieved will not be duplicated. // Tests that announces already being retrieved will not be duplicated.
func TestPendingDeduplication61(t *testing.T) { testPendingDeduplication(t, 61) }
func TestPendingDeduplication62(t *testing.T) { testPendingDeduplication(t, 62) } func TestPendingDeduplication62(t *testing.T) { testPendingDeduplication(t, 62) }
func TestPendingDeduplication63(t *testing.T) { testPendingDeduplication(t, 63) } func TestPendingDeduplication63(t *testing.T) { testPendingDeduplication(t, 63) }
func TestPendingDeduplication64(t *testing.T) { testPendingDeduplication(t, 64) } func TestPendingDeduplication64(t *testing.T) { testPendingDeduplication(t, 64) }
@ -428,22 +381,11 @@ func testPendingDeduplication(t *testing.T, protocol int) {
// Assemble a tester with a built in counter and delayed fetcher // Assemble a tester with a built in counter and delayed fetcher
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
delay := 50 * time.Millisecond delay := 50 * time.Millisecond
counter := uint32(0) counter := uint32(0)
blockWrapper := func(hashes []common.Hash) error {
atomic.AddUint32(&counter, uint32(len(hashes)))
// Simulate a long running fetch
go func() {
time.Sleep(delay)
blockFetcher(hashes)
}()
return nil
}
headerWrapper := func(hash common.Hash) error { headerWrapper := func(hash common.Hash) error {
atomic.AddUint32(&counter, 1) atomic.AddUint32(&counter, 1)
@ -456,11 +398,7 @@ func testPendingDeduplication(t *testing.T, protocol int) {
} }
// Announce the same block many times until it's fetched (wait for any pending ops) // Announce the same block many times until it's fetched (wait for any pending ops)
for tester.getBlock(hashes[0]) == nil { for tester.getBlock(hashes[0]) == nil {
if protocol < 62 { tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
tester.fetcher.Notify("repeater", hashes[0], 0, time.Now().Add(-arriveTimeout), blockWrapper, nil, nil)
} else {
tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), nil, headerWrapper, bodyFetcher)
}
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
time.Sleep(delay) time.Sleep(delay)
@ -476,7 +414,6 @@ func testPendingDeduplication(t *testing.T, protocol int) {
// Tests that announcements retrieved in a random order are cached and eventually // Tests that announcements retrieved in a random order are cached and eventually
// imported when all the gaps are filled in. // imported when all the gaps are filled in.
func TestRandomArrivalImport61(t *testing.T) { testRandomArrivalImport(t, 61) }
func TestRandomArrivalImport62(t *testing.T) { testRandomArrivalImport(t, 62) } func TestRandomArrivalImport62(t *testing.T) { testRandomArrivalImport(t, 62) }
func TestRandomArrivalImport63(t *testing.T) { testRandomArrivalImport(t, 63) } func TestRandomArrivalImport63(t *testing.T) { testRandomArrivalImport(t, 63) }
func TestRandomArrivalImport64(t *testing.T) { testRandomArrivalImport(t, 64) } func TestRandomArrivalImport64(t *testing.T) { testRandomArrivalImport(t, 64) }
@ -488,7 +425,6 @@ func testRandomArrivalImport(t *testing.T, protocol int) {
skip := targetBlocks / 2 skip := targetBlocks / 2
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
@ -498,26 +434,17 @@ func testRandomArrivalImport(t *testing.T, protocol int) {
for i := len(hashes) - 1; i >= 0; i-- { for i := len(hashes) - 1; i >= 0; i-- {
if i != skip { if i != skip {
if protocol < 62 { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
tester.fetcher.Notify("valid", hashes[i], 0, time.Now().Add(-arriveTimeout), blockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher)
}
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
} }
// Finally announce the skipped entry and check full import // Finally announce the skipped entry and check full import
if protocol < 62 { tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
tester.fetcher.Notify("valid", hashes[skip], 0, time.Now().Add(-arriveTimeout), blockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher)
}
verifyImportCount(t, imported, len(hashes)-1) verifyImportCount(t, imported, len(hashes)-1)
} }
// Tests that direct block enqueues (due to block propagation vs. hash announce) // Tests that direct block enqueues (due to block propagation vs. hash announce)
// are correctly schedule, filling and import queue gaps. // are correctly schedule, filling and import queue gaps.
func TestQueueGapFill61(t *testing.T) { testQueueGapFill(t, 61) }
func TestQueueGapFill62(t *testing.T) { testQueueGapFill(t, 62) } func TestQueueGapFill62(t *testing.T) { testQueueGapFill(t, 62) }
func TestQueueGapFill63(t *testing.T) { testQueueGapFill(t, 63) } func TestQueueGapFill63(t *testing.T) { testQueueGapFill(t, 63) }
func TestQueueGapFill64(t *testing.T) { testQueueGapFill(t, 64) } func TestQueueGapFill64(t *testing.T) { testQueueGapFill(t, 64) }
@ -529,7 +456,6 @@ func testQueueGapFill(t *testing.T, protocol int) {
skip := targetBlocks / 2 skip := targetBlocks / 2
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
@ -539,11 +465,7 @@ func testQueueGapFill(t *testing.T, protocol int) {
for i := len(hashes) - 1; i >= 0; i-- { for i := len(hashes) - 1; i >= 0; i-- {
if i != skip { if i != skip {
if protocol < 62 { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
tester.fetcher.Notify("valid", hashes[i], 0, time.Now().Add(-arriveTimeout), blockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher)
}
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
} }
@ -554,7 +476,6 @@ func testQueueGapFill(t *testing.T, protocol int) {
// Tests that blocks arriving from various sources (multiple propagations, hash // Tests that blocks arriving from various sources (multiple propagations, hash
// announces, etc) do not get scheduled for import multiple times. // announces, etc) do not get scheduled for import multiple times.
func TestImportDeduplication61(t *testing.T) { testImportDeduplication(t, 61) }
func TestImportDeduplication62(t *testing.T) { testImportDeduplication(t, 62) } func TestImportDeduplication62(t *testing.T) { testImportDeduplication(t, 62) }
func TestImportDeduplication63(t *testing.T) { testImportDeduplication(t, 63) } func TestImportDeduplication63(t *testing.T) { testImportDeduplication(t, 63) }
func TestImportDeduplication64(t *testing.T) { testImportDeduplication(t, 64) } func TestImportDeduplication64(t *testing.T) { testImportDeduplication(t, 64) }
@ -565,7 +486,6 @@ func testImportDeduplication(t *testing.T, protocol int) {
// Create the tester and wrap the importer with a counter // Create the tester and wrap the importer with a counter
tester := newTester() tester := newTester()
blockFetcher := tester.makeBlockFetcher(blocks)
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
bodyFetcher := tester.makeBodyFetcher(blocks, 0) bodyFetcher := tester.makeBodyFetcher(blocks, 0)
@ -581,11 +501,7 @@ func testImportDeduplication(t *testing.T, protocol int) {
tester.fetcher.importedHook = func(block *types.Block) { imported <- block } tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
// Announce the duplicating block, wait for retrieval, and also propagate directly // Announce the duplicating block, wait for retrieval, and also propagate directly
if protocol < 62 { tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
tester.fetcher.Notify("valid", hashes[0], 0, time.Now().Add(-arriveTimeout), blockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher)
}
<-fetching <-fetching
tester.fetcher.Enqueue("valid", blocks[hashes[0]]) tester.fetcher.Enqueue("valid", blocks[hashes[0]])
@ -661,14 +577,14 @@ func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} } tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} }
// Ensure that a block with a lower number than the threshold is discarded // Ensure that a block with a lower number than the threshold is discarded
tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher) tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
select { select {
case <-time.After(50 * time.Millisecond): case <-time.After(50 * time.Millisecond):
case <-fetching: case <-fetching:
t.Fatalf("fetcher requested stale header") t.Fatalf("fetcher requested stale header")
} }
// Ensure that a block with a higher number than the threshold is discarded // Ensure that a block with a higher number than the threshold is discarded
tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher) tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
select { select {
case <-time.After(50 * time.Millisecond): case <-time.After(50 * time.Millisecond):
case <-fetching: case <-fetching:
@ -694,7 +610,7 @@ func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
tester.fetcher.importedHook = func(block *types.Block) { imported <- block } tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
// Announce a block with a bad number, check for immediate drop // Announce a block with a bad number, check for immediate drop
tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher) tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
verifyImportEvent(t, imported, false) verifyImportEvent(t, imported, false)
tester.lock.RLock() tester.lock.RLock()
@ -705,7 +621,7 @@ func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
t.Fatalf("peer with invalid numbered announcement not dropped") t.Fatalf("peer with invalid numbered announcement not dropped")
} }
// Make sure a good announcement passes without a drop // Make sure a good announcement passes without a drop
tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher) tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
verifyImportEvent(t, imported, true) verifyImportEvent(t, imported, true)
tester.lock.RLock() tester.lock.RLock()
@ -744,7 +660,7 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
// Iteratively announce blocks until all are imported // Iteratively announce blocks until all are imported
for i := len(hashes) - 2; i >= 0; i-- { for i := len(hashes) - 2; i >= 0; i-- {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, headerFetcher, bodyFetcher) tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
// All announces should fetch the header // All announces should fetch the header
verifyFetchingEvent(t, fetching, true) verifyFetchingEvent(t, fetching, true)
@ -761,7 +677,6 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
// Tests that a peer is unable to use unbounded memory with sending infinite // Tests that a peer is unable to use unbounded memory with sending infinite
// block announcements to a node, but that even in the face of such an attack, // block announcements to a node, but that even in the face of such an attack,
// the fetcher remains operational. // the fetcher remains operational.
func TestHashMemoryExhaustionAttack61(t *testing.T) { testHashMemoryExhaustionAttack(t, 61) }
func TestHashMemoryExhaustionAttack62(t *testing.T) { testHashMemoryExhaustionAttack(t, 62) } func TestHashMemoryExhaustionAttack62(t *testing.T) { testHashMemoryExhaustionAttack(t, 62) }
func TestHashMemoryExhaustionAttack63(t *testing.T) { testHashMemoryExhaustionAttack(t, 63) } func TestHashMemoryExhaustionAttack63(t *testing.T) { testHashMemoryExhaustionAttack(t, 63) }
func TestHashMemoryExhaustionAttack64(t *testing.T) { testHashMemoryExhaustionAttack(t, 64) } func TestHashMemoryExhaustionAttack64(t *testing.T) { testHashMemoryExhaustionAttack(t, 64) }
@ -782,29 +697,19 @@ func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
// Create a valid chain and an infinite junk chain // Create a valid chain and an infinite junk chain
targetBlocks := hashLimit + 2*maxQueueDist targetBlocks := hashLimit + 2*maxQueueDist
hashes, blocks := makeChain(targetBlocks, 0, genesis) hashes, blocks := makeChain(targetBlocks, 0, genesis)
validBlockFetcher := tester.makeBlockFetcher(blocks)
validHeaderFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack) validHeaderFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
validBodyFetcher := tester.makeBodyFetcher(blocks, 0) validBodyFetcher := tester.makeBodyFetcher(blocks, 0)
attack, _ := makeChain(targetBlocks, 0, unknownBlock) attack, _ := makeChain(targetBlocks, 0, unknownBlock)
attackerBlockFetcher := tester.makeBlockFetcher(nil)
attackerHeaderFetcher := tester.makeHeaderFetcher(nil, -gatherSlack) attackerHeaderFetcher := tester.makeHeaderFetcher(nil, -gatherSlack)
attackerBodyFetcher := tester.makeBodyFetcher(nil, 0) attackerBodyFetcher := tester.makeBodyFetcher(nil, 0)
// Feed the tester a huge hashset from the attacker, and a limited from the valid peer // Feed the tester a huge hashset from the attacker, and a limited from the valid peer
for i := 0; i < len(attack); i++ { for i := 0; i < len(attack); i++ {
if i < maxQueueDist { if i < maxQueueDist {
if protocol < 62 { tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher)
tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], 0, time.Now(), validBlockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), nil, validHeaderFetcher, validBodyFetcher)
}
}
if protocol < 62 {
tester.fetcher.Notify("attacker", attack[i], 0, time.Now(), attackerBlockFetcher, nil, nil)
} else {
tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), nil, attackerHeaderFetcher, attackerBodyFetcher)
} }
tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher)
} }
if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist { if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist {
t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist)
@ -814,11 +719,7 @@ func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
// Feed the remaining valid hashes to ensure DOS protection state remains clean // Feed the remaining valid hashes to ensure DOS protection state remains clean
for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- { for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- {
if protocol < 62 { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher)
tester.fetcher.Notify("valid", hashes[i], 0, time.Now().Add(-arriveTimeout), validBlockFetcher, nil, nil)
} else {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), nil, validHeaderFetcher, validBodyFetcher)
}
verifyImportEvent(t, imported, true) verifyImportEvent(t, imported, true)
} }
verifyImportDone(t, imported) verifyImportDone(t, imported)

View file

@ -33,12 +33,9 @@ var (
propBroadcastDropMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/drop") propBroadcastDropMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/drop")
propBroadcastDOSMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/dos") propBroadcastDOSMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/dos")
blockFetchMeter = metrics.NewMeter("eth/fetcher/fetch/blocks")
headerFetchMeter = metrics.NewMeter("eth/fetcher/fetch/headers") headerFetchMeter = metrics.NewMeter("eth/fetcher/fetch/headers")
bodyFetchMeter = metrics.NewMeter("eth/fetcher/fetch/bodies") bodyFetchMeter = metrics.NewMeter("eth/fetcher/fetch/bodies")
blockFilterInMeter = metrics.NewMeter("eth/fetcher/filter/blocks/in")
blockFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/blocks/out")
headerFilterInMeter = metrics.NewMeter("eth/fetcher/filter/headers/in") headerFilterInMeter = metrics.NewMeter("eth/fetcher/filter/headers/in")
headerFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/headers/out") headerFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/headers/out")
bodyFilterInMeter = metrics.NewMeter("eth/fetcher/filter/bodies/in") bodyFilterInMeter = metrics.NewMeter("eth/fetcher/filter/bodies/in")

View file

@ -68,8 +68,6 @@ type PublicFilterAPI struct {
transactionMu sync.RWMutex transactionMu sync.RWMutex
transactionQueue map[int]*hashQueue transactionQueue map[int]*hashQueue
transactMu sync.Mutex
} }
// NewPublicFilterAPI returns a new PublicFilterAPI instance. // NewPublicFilterAPI returns a new PublicFilterAPI instance.
@ -100,6 +98,7 @@ done:
for { for {
select { select {
case <-timer.C: case <-timer.C:
s.filterManager.Lock() // lock order like filterLoop()
s.logMu.Lock() s.logMu.Lock()
for id, filter := range s.logQueue { for id, filter := range s.logQueue {
if time.Since(filter.timeout) > filterTickerTime { if time.Since(filter.timeout) > filterTickerTime {
@ -126,6 +125,7 @@ done:
} }
} }
s.transactionMu.Unlock() s.transactionMu.Unlock()
s.filterManager.Unlock()
case <-s.quit: case <-s.quit:
break done break done
} }
@ -135,19 +135,24 @@ done:
// NewBlockFilter create a new filter that returns blocks that are included into the canonical chain. // NewBlockFilter create a new filter that returns blocks that are included into the canonical chain.
func (s *PublicFilterAPI) NewBlockFilter() (string, error) { func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
// protect filterManager.Add() and setting of filter fields
s.filterManager.Lock()
defer s.filterManager.Unlock()
externalId, err := newFilterId() externalId, err := newFilterId()
if err != nil { if err != nil {
return "", err return "", err
} }
s.blockMu.Lock()
filter := New(s.chainDb) filter := New(s.chainDb)
id, err := s.filterManager.Add(filter, ChainFilter) id, err := s.filterManager.Add(filter, ChainFilter)
if err != nil { if err != nil {
return "", err return "", err
} }
s.blockMu.Lock()
s.blockQueue[id] = &hashQueue{timeout: time.Now()} s.blockQueue[id] = &hashQueue{timeout: time.Now()}
s.blockMu.Unlock()
filter.BlockCallback = func(block *types.Block, logs vm.Logs) { filter.BlockCallback = func(block *types.Block, logs vm.Logs) {
s.blockMu.Lock() s.blockMu.Lock()
@ -158,8 +163,6 @@ func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
} }
} }
defer s.blockMu.Unlock()
s.filterMapMu.Lock() s.filterMapMu.Lock()
s.filterMapping[externalId] = id s.filterMapping[externalId] = id
s.filterMapMu.Unlock() s.filterMapMu.Unlock()
@ -169,21 +172,24 @@ func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
// NewPendingTransactionFilter creates a filter that returns new pending transactions. // NewPendingTransactionFilter creates a filter that returns new pending transactions.
func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) { func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) {
// protect filterManager.Add() and setting of filter fields
s.filterManager.Lock()
defer s.filterManager.Unlock()
externalId, err := newFilterId() externalId, err := newFilterId()
if err != nil { if err != nil {
return "", err return "", err
} }
s.transactionMu.Lock()
defer s.transactionMu.Unlock()
filter := New(s.chainDb) filter := New(s.chainDb)
id, err := s.filterManager.Add(filter, PendingTxFilter) id, err := s.filterManager.Add(filter, PendingTxFilter)
if err != nil { if err != nil {
return "", err return "", err
} }
s.transactionMu.Lock()
s.transactionQueue[id] = &hashQueue{timeout: time.Now()} s.transactionQueue[id] = &hashQueue{timeout: time.Now()}
s.transactionMu.Unlock()
filter.TransactionCallback = func(tx *types.Transaction) { filter.TransactionCallback = func(tx *types.Transaction) {
s.transactionMu.Lock() s.transactionMu.Lock()
@ -203,8 +209,9 @@ func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) {
// newLogFilter creates a new log filter. // newLogFilter creates a new log filter.
func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []common.Address, topics [][]common.Hash, callback func(log *vm.Log, removed bool)) (int, error) { func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []common.Address, topics [][]common.Hash, callback func(log *vm.Log, removed bool)) (int, error) {
s.logMu.Lock() // protect filterManager.Add() and setting of filter fields
defer s.logMu.Unlock() s.filterManager.Lock()
defer s.filterManager.Unlock()
filter := New(s.chainDb) filter := New(s.chainDb)
id, err := s.filterManager.Add(filter, LogFilter) id, err := s.filterManager.Add(filter, LogFilter)
@ -212,7 +219,9 @@ func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []commo
return 0, err return 0, err
} }
s.logMu.Lock()
s.logQueue[id] = &logQueue{timeout: time.Now()} s.logQueue[id] = &logQueue{timeout: time.Now()}
s.logMu.Unlock()
filter.SetBeginBlock(earliest) filter.SetBeginBlock(earliest)
filter.SetEndBlock(latest) filter.SetEndBlock(latest)
@ -443,35 +452,43 @@ func (s *PublicFilterAPI) GetLogs(args NewFilterArgs) []vmlog {
// UninstallFilter removes the filter with the given filter id. // UninstallFilter removes the filter with the given filter id.
func (s *PublicFilterAPI) UninstallFilter(filterId string) bool { func (s *PublicFilterAPI) UninstallFilter(filterId string) bool {
s.filterMapMu.Lock() s.filterManager.Lock()
defer s.filterMapMu.Unlock() defer s.filterManager.Unlock()
s.filterMapMu.Lock()
id, ok := s.filterMapping[filterId] id, ok := s.filterMapping[filterId]
if !ok { if !ok {
s.filterMapMu.Unlock()
return false return false
} }
defer s.filterManager.Remove(id)
delete(s.filterMapping, filterId) delete(s.filterMapping, filterId)
s.filterMapMu.Unlock()
s.filterManager.Remove(id)
if _, ok := s.logQueue[id]; ok {
s.logMu.Lock() s.logMu.Lock()
defer s.logMu.Unlock() if _, ok := s.logQueue[id]; ok {
delete(s.logQueue, id) delete(s.logQueue, id)
s.logMu.Unlock()
return true return true
} }
if _, ok := s.blockQueue[id]; ok { s.logMu.Unlock()
s.blockMu.Lock() s.blockMu.Lock()
defer s.blockMu.Unlock() if _, ok := s.blockQueue[id]; ok {
delete(s.blockQueue, id) delete(s.blockQueue, id)
s.blockMu.Unlock()
return true return true
} }
if _, ok := s.transactionQueue[id]; ok { s.blockMu.Unlock()
s.transactionMu.Lock() s.transactionMu.Lock()
defer s.transactionMu.Unlock() if _, ok := s.transactionQueue[id]; ok {
delete(s.transactionQueue, id) delete(s.transactionQueue, id)
s.transactionMu.Unlock()
return true return true
} }
s.transactionMu.Unlock()
return false return false
} }
@ -525,7 +542,9 @@ func (s *PublicFilterAPI) logFilterChanged(id int) []vmlog {
// GetFilterLogs returns the logs for the filter with the given id. // GetFilterLogs returns the logs for the filter with the given id.
func (s *PublicFilterAPI) GetFilterLogs(filterId string) []vmlog { func (s *PublicFilterAPI) GetFilterLogs(filterId string) []vmlog {
s.filterMapMu.RLock()
id, ok := s.filterMapping[filterId] id, ok := s.filterMapping[filterId]
s.filterMapMu.RUnlock()
if !ok { if !ok {
return toRPCLogs(nil, false) return toRPCLogs(nil, false)
} }
@ -540,9 +559,9 @@ func (s *PublicFilterAPI) GetFilterLogs(filterId string) []vmlog {
// GetFilterChanges returns the logs for the filter with the given id since last time is was called. // GetFilterChanges returns the logs for the filter with the given id since last time is was called.
// This can be used for polling. // This can be used for polling.
func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} { func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} {
s.filterMapMu.Lock() s.filterMapMu.RLock()
id, ok := s.filterMapping[filterId] id, ok := s.filterMapping[filterId]
s.filterMapMu.Unlock() s.filterMapMu.RUnlock()
if !ok { // filter not found if !ok { // filter not found
return []interface{}{} return []interface{}{}

View file

@ -72,7 +72,11 @@ func (self *Filter) SetTopics(topics [][]common.Hash) {
// Run filters logs with the current parameters set // Run filters logs with the current parameters set
func (self *Filter) Find() vm.Logs { func (self *Filter) Find() vm.Logs {
latestBlock := core.GetBlock(self.db, core.GetHeadBlockHash(self.db)) latestHash := core.GetHeadBlockHash(self.db)
latestBlock := core.GetBlock(self.db, latestHash)
if latestBlock == nil {
return vm.Logs{}
}
var beginBlockNo uint64 = uint64(self.begin) var beginBlockNo uint64 = uint64(self.begin)
if self.begin == -1 { if self.begin == -1 {
beginBlockNo = latestBlock.NumberU64() beginBlockNo = latestBlock.NumberU64()
@ -122,13 +126,13 @@ func (self *Filter) mipFind(start, end uint64, depth int) (logs vm.Logs) {
} }
func (self *Filter) getLogs(start, end uint64) (logs vm.Logs) { func (self *Filter) getLogs(start, end uint64) (logs vm.Logs) {
var block *types.Block
for i := start; i <= end; i++ { for i := start; i <= end; i++ {
var block *types.Block
hash := core.GetCanonicalHash(self.db, i) hash := core.GetCanonicalHash(self.db, i)
if hash != (common.Hash{}) { if hash != (common.Hash{}) {
block = core.GetBlock(self.db, hash) block = core.GetBlock(self.db, hash)
} else { // block not found }
if block == nil { // block not found/written
return logs return logs
} }

View file

@ -82,11 +82,20 @@ func (fs *FilterSystem) Stop() {
fs.sub.Unsubscribe() fs.sub.Unsubscribe()
} }
// Add adds a filter to the filter manager // Acquire filter system maps lock, required to force lock acquisition
func (fs *FilterSystem) Add(filter *Filter, filterType FilterType) (int, error) { // sequence with filterMu acquired first to avoid deadlocks by callbacks
func (fs *FilterSystem) Lock() {
fs.filterMu.Lock() fs.filterMu.Lock()
defer fs.filterMu.Unlock() }
// Release filter system maps lock
func (fs *FilterSystem) Unlock() {
fs.filterMu.Unlock()
}
// Add adds a filter to the filter manager
// Expects filterMu to be locked.
func (fs *FilterSystem) Add(filter *Filter, filterType FilterType) (int, error) {
id := fs.filterId id := fs.filterId
filter.created = time.Now() filter.created = time.Now()
@ -110,10 +119,8 @@ func (fs *FilterSystem) Add(filter *Filter, filterType FilterType) (int, error)
} }
// Remove removes a filter by filter id // Remove removes a filter by filter id
// Expects filterMu to be locked.
func (fs *FilterSystem) Remove(id int) { func (fs *FilterSystem) Remove(id int) {
fs.filterMu.Lock()
defer fs.filterMu.Unlock()
delete(fs.chainFilters, id) delete(fs.chainFilters, id)
delete(fs.pendingTxFilters, id) delete(fs.pendingTxFilters, id)
delete(fs.logFilters, id) delete(fs.logFilters, id)

View file

@ -57,7 +57,7 @@ func BenchmarkMipmaps(b *testing.B) {
defer db.Close() defer db.Close()
genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{Address: addr1, Balance: big.NewInt(1000000)}) genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{Address: addr1, Balance: big.NewInt(1000000)})
chain, receipts := core.GenerateChain(genesis, db, 100010, func(i int, gen *core.BlockGen) { chain, receipts := core.GenerateChain(nil, genesis, db, 100010, func(i int, gen *core.BlockGen) {
var receipts types.Receipts var receipts types.Receipts
switch i { switch i {
case 2403: case 2403:
@ -133,7 +133,7 @@ func TestFilters(t *testing.T) {
defer db.Close() defer db.Close()
genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{Address: addr, Balance: big.NewInt(1000000)}) genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{Address: addr, Balance: big.NewInt(1000000)})
chain, receipts := core.GenerateChain(genesis, db, 1000, func(i int, gen *core.BlockGen) { chain, receipts := core.GenerateChain(nil, genesis, db, 1000, func(i int, gen *core.BlockGen) {
var receipts types.Receipts var receipts types.Receipts
switch i { switch i {
case 1: case 1:

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