Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cb7d7d3d5d | ||
|
1639f1174e | ||
|
d9ed63ec38 | ||
|
ee58202f2f | ||
|
9315bc9c3c | ||
|
945bcb8293 | ||
|
f1949f4d99 | ||
|
968ab8aa4f | ||
|
f88bca7ba9 | ||
|
d4608ae0d2 | ||
|
f50c2a5c70 | ||
|
ddadf402fc | ||
|
7c17a6704c | ||
|
25205d64d7 | ||
|
03b2f56485 | ||
|
8f0db69762 | ||
|
7a5843de31 | ||
|
0cc6397195 | ||
|
dc2b23c869 | ||
|
6999f1da6b | ||
|
95bfedd599 | ||
|
42e4e18667 | ||
|
9bdbaf459a | ||
|
421df866cd | ||
|
dfc63c49c7 | ||
|
e44b2dc881 | ||
|
fed692f67e | ||
|
99a0c76435 | ||
|
5ca5ccf90c | ||
|
c4ed34f008 | ||
|
0ab7e90cbb | ||
|
bdbfe572f1 | ||
|
c4e4baf668 | ||
|
86493f9103 | ||
|
6c672a55c0 | ||
|
48709d5340 | ||
|
65da8f601f | ||
|
2c6214e846 | ||
|
0398075ced | ||
|
d1696dbf07 | ||
|
626604e86d | ||
|
9eb2873a9c | ||
|
08a7cd74da | ||
|
35d479b6d3 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -23,17 +23,11 @@ Godeps/_workspace/bin
|
||||
.project
|
||||
.settings
|
||||
|
||||
deploy/osx/Mist.app
|
||||
deploy/osx/Mist\ Installer.dmg
|
||||
cmd/mist/assets/ext/ethereum.js/
|
||||
|
||||
# used by the Makefile
|
||||
/build/_workspace/
|
||||
/build/bin/
|
||||
/geth*.zip
|
||||
|
||||
# travis
|
||||
profile.tmp
|
||||
profile.cov
|
||||
|
||||
# vagrant
|
||||
.vagrant
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "cmd/mist/assets/ext/ethereum.js"]
|
||||
path = cmd/mist/assets/ext/ethereum.js
|
||||
url = https://github.com/ethereum/web3.js
|
60
.travis.yml
60
.travis.yml
@@ -1,31 +1,45 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.4.2
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
install:
|
||||
# - go get code.google.com/p/go.tools/cmd/goimports
|
||||
# - go get github.com/golang/lint/golint
|
||||
# - go get golang.org/x/tools/cmd/vet
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
before_script:
|
||||
# - gofmt -l -w .
|
||||
# - goimports -l -w .
|
||||
# - golint .
|
||||
# - go vet ./...
|
||||
# - go test -race ./...
|
||||
script:
|
||||
- make travis-test-with-coverage
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
env:
|
||||
global:
|
||||
- secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64="
|
||||
go_import_path: github.com/ethereum/go-ethereum
|
||||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.4.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.5.4
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.6.2
|
||||
- os: osx
|
||||
go: 1.6.2
|
||||
|
||||
# This builder does the PPA upload (and nothing else).
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.6.2
|
||||
env: PPA
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- devscripts
|
||||
- debhelper
|
||||
- dput
|
||||
script:
|
||||
- go run build/ci.go debsrc -signer "Felix Lange (Geth CI Testing Key) <fjl@twurst.com>" -upload ppa:lp-fjl/geth-ci-testing
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage -vet
|
||||
after_success:
|
||||
# - go run build/ci.go archive -type tar
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/e09ccdce1048c5e03445
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_start: false
|
||||
|
6
Godeps/Godeps.json
generated
6
Godeps/Godeps.json
generated
@@ -20,8 +20,8 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/ethash",
|
||||
"Comment": "v23.1-245-g25b32de",
|
||||
"Rev": "25b32de0c0271065c28c3719c2bfe86959d72f0c"
|
||||
"Comment": "v23.1-247-g2e80de5",
|
||||
"Rev": "2e80de5022370cfe632195b1720db52d07ff8a77"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatih/color",
|
||||
@@ -117,7 +117,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rjeczalik/notify",
|
||||
"Rev": "5dd6205716539662f8f14ab513552b41eab69d5d"
|
||||
"Rev": "f627deca7a510d96f0ef9388f2d0e8b16d21f87f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/robertkrimen/otto",
|
||||
|
0
Godeps/_workspace/src/github.com/ethereum/ethash/setup.py
generated
vendored
Normal file → Executable file
0
Godeps/_workspace/src/github.com/ethereum/ethash/setup.py
generated
vendored
Normal file → Executable file
29
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/endian.h
generated
vendored
29
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/endian.h
generated
vendored
@@ -19,7 +19,7 @@
|
||||
# define BYTE_ORDER LITTLE_ENDIAN
|
||||
#elif defined( __QNXNTO__ ) && defined( __BIGENDIAN__ )
|
||||
# define BIG_ENDIAN 1234
|
||||
# define BYTE_ORDER BIG_ENDIAN
|
||||
# define BYTE_ORDER BIG_ENDIAN
|
||||
#else
|
||||
# include <endian.h>
|
||||
#endif
|
||||
@@ -59,21 +59,20 @@
|
||||
|
||||
#define fix_endian32(dst_, src_) dst_ = ethash_swap_u32(src_)
|
||||
#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_endian_arr32(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_), ++i_) { \
|
||||
arr_[i_] = ethash_swap_u32(arr_[i_]); \
|
||||
} \
|
||||
while (0)
|
||||
#define fix_endian_arr64(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_), ++i_) { \
|
||||
arr_[i_] = ethash_swap_u64(arr_[i_]); \
|
||||
} \
|
||||
while (0) \
|
||||
|
||||
#define fix_endian_arr32(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_); ++i_) { \
|
||||
arr_[i_] = ethash_swap_u32(arr_[i_]); \
|
||||
} \
|
||||
} while (0)
|
||||
#define fix_endian_arr64(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_); ++i_) { \
|
||||
arr_[i_] = ethash_swap_u64(arr_[i_]); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
# error "endian not supported"
|
||||
#endif // BYTE_ORDER
|
||||
|
2
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/internal.c
generated
vendored
2
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/internal.c
generated
vendored
@@ -257,7 +257,7 @@ static bool ethash_hash(
|
||||
void ethash_quick_hash(
|
||||
ethash_h256_t* return_hash,
|
||||
ethash_h256_t const* header_hash,
|
||||
uint64_t const nonce,
|
||||
uint64_t nonce,
|
||||
ethash_h256_t const* mix_hash
|
||||
)
|
||||
{
|
||||
|
3
Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml
generated
vendored
3
Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml
generated
vendored
@@ -21,10 +21,9 @@ env:
|
||||
- PATH=$HOME/bin:$PATH
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go tool vet -all .
|
||||
- "(go version | grep -q 1.4) || go tool vet -all ."
|
||||
- go install $GOFLAGS ./...
|
||||
- go test -v -race $GOFLAGS ./...
|
||||
|
1
Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml
generated
vendored
1
Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml
generated
vendored
@@ -11,7 +11,6 @@ environment:
|
||||
|
||||
install:
|
||||
- go version
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get -v -t ./...
|
||||
|
||||
build_script:
|
||||
|
2
Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go
generated
vendored
2
Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go
generated
vendored
@@ -133,7 +133,7 @@ func (w *watch) Dispatch(ev []FSEvent) {
|
||||
ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
|
||||
if ev[i].Flags&failure != 0 {
|
||||
// TODO(rjeczalik): missing error handling
|
||||
panic("unhandled error: " + Event(ev[i].Flags).String())
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(ev[i].Path, w.path) {
|
||||
continue
|
||||
|
105
Makefile
105
Makefile
@@ -2,8 +2,8 @@
|
||||
# with Go source code. If you know what GOPATH is then you probably
|
||||
# don't need to bother with make.
|
||||
|
||||
.PHONY: geth geth-cross evm all test travis-test-with-coverage xgo clean
|
||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64
|
||||
.PHONY: geth geth-cross evm all test clean
|
||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
|
||||
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
|
||||
.PHONY: geth-windows geth-windows-386 geth-windows-amd64
|
||||
@@ -13,25 +13,41 @@ GOBIN = build/bin
|
||||
GO ?= latest
|
||||
|
||||
geth:
|
||||
build/env.sh go build -i -v $(shell build/flags.sh) -o $(GOBIN)/geth ./cmd/geth
|
||||
build/env.sh go run build/ci.go install ./cmd/geth
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||
|
||||
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)
|
||||
|
||||
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||
@echo "Full cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-*
|
||||
|
||||
geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm
|
||||
geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm geth-linux-mips64 geth-linux-mips64le
|
||||
@echo "Linux cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-*
|
||||
|
||||
geth-linux-386: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-386:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v ./cmd/geth
|
||||
@echo "Linux 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep 386
|
||||
|
||||
geth-linux-amd64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-amd64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v ./cmd/geth
|
||||
@echo "Linux amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep amd64
|
||||
|
||||
@@ -39,37 +55,47 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar
|
||||
@echo "Linux ARM cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm
|
||||
|
||||
geth-linux-arm-5: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm-5:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v ./cmd/geth
|
||||
@echo "Linux ARMv5 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-5
|
||||
|
||||
geth-linux-arm-6: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm-6:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v ./cmd/geth
|
||||
@echo "Linux ARMv6 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-6
|
||||
|
||||
geth-linux-arm-7: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm-7:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v ./cmd/geth
|
||||
@echo "Linux ARMv7 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-7
|
||||
|
||||
geth-linux-arm64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v ./cmd/geth
|
||||
@echo "Linux ARM64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm64
|
||||
|
||||
geth-linux-mips64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/mips64 -v ./cmd/geth
|
||||
@echo "Linux MIPS64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips64
|
||||
|
||||
geth-linux-mips64le:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=linux/mips64le -v ./cmd/geth
|
||||
@echo "Linux MIPS64le cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips64le
|
||||
|
||||
geth-darwin: geth-darwin-386 geth-darwin-amd64
|
||||
@echo "Darwin cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-*
|
||||
|
||||
geth-darwin-386: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-darwin-386:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v ./cmd/geth
|
||||
@echo "Darwin 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-* | grep 386
|
||||
|
||||
geth-darwin-amd64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-darwin-amd64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v ./cmd/geth
|
||||
@echo "Darwin amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-* | grep amd64
|
||||
|
||||
@@ -77,45 +103,22 @@ geth-windows: geth-windows-386 geth-windows-amd64
|
||||
@echo "Windows cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-*
|
||||
|
||||
geth-windows-386: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-windows-386:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v ./cmd/geth
|
||||
@echo "Windows 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-* | grep 386
|
||||
|
||||
geth-windows-amd64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-windows-amd64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v ./cmd/geth
|
||||
@echo "Windows amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-* | grep amd64
|
||||
|
||||
geth-android: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-android:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v ./cmd/geth
|
||||
@echo "Android cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-android-*
|
||||
|
||||
geth-ios: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-ios:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v ./cmd/geth
|
||||
@echo "iOS framework cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-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)/*
|
||||
|
33
appveyor.yml
Normal file
33
appveyor.yml
Normal 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
49
build/ci-notes.md
Normal 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
483
build/ci.go
Normal 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
5
build/deb.changelog
Normal file
@@ -0,0 +1,5 @@
|
||||
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||
|
||||
* git build of {{.Env.Commit}}
|
||||
|
||||
-- {{.Author}} {{.Time}}
|
25
build/deb.control
Normal file
25
build/deb.control
Normal 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/ethereum/go-ethereum.git
|
||||
Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||
|
||||
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
14
build/deb.copyright
Normal 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
1
build/deb.docs
Normal file
@@ -0,0 +1 @@
|
||||
AUTHORS
|
1
build/deb.install
Normal file
1
build/deb.install
Normal file
@@ -0,0 +1 @@
|
||||
build/bin/{{.Name}} usr/bin
|
13
build/deb.rules
Normal file
13
build/deb.rules
Normal 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 $@
|
@@ -20,9 +20,8 @@ fi
|
||||
|
||||
# Set up the environment to use the workspace.
|
||||
# Also add Godeps workspace so we build using canned dependencies.
|
||||
GOPATH="$ethdir/go-ethereum/Godeps/_workspace:$workspace"
|
||||
GOBIN="$PWD/build/bin"
|
||||
export GOPATH GOBIN
|
||||
GOPATH="$workspace"
|
||||
export GOPATH
|
||||
|
||||
# Run the command inside the workspace.
|
||||
cd "$ethdir/go-ethereum"
|
||||
|
@@ -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
|
@@ -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
|
@@ -49,7 +49,6 @@ var (
|
||||
// don't relicense vendored sources
|
||||
"crypto/sha3/", "crypto/ecies/", "logger/glog/",
|
||||
"crypto/secp256k1/curve.go",
|
||||
"trie/arc.go",
|
||||
}
|
||||
|
||||
// paths with this prefix are licensed as GPL. all other files are LGPL.
|
||||
|
@@ -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% ./...
|
@@ -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 ./...
|
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
@@ -141,7 +142,9 @@ func run(ctx *cli.Context) error {
|
||||
)
|
||||
} else {
|
||||
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(
|
||||
sender,
|
||||
receiver.Address(),
|
||||
|
@@ -50,7 +50,7 @@ const (
|
||||
clientIdentifier = "Geth" // Client identifier to advertise over the network
|
||||
versionMajor = 1 // Major version component of the current release
|
||||
versionMinor = 4 // Minor version component of the current release
|
||||
versionPatch = 10 // Patch version component of the current release
|
||||
versionPatch = 15 // Patch version component of the current release
|
||||
versionMeta = "stable" // Version metadata to append to the version string
|
||||
|
||||
versionOracle = "0xfa7b9770ca4cb04296cac84f37736d4041251cdf" // Ethereum address of the Geth release oracle
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@@ -52,10 +53,16 @@ func openLogFile(Datadir string, filename string) *os.File {
|
||||
// is redirected to a different file.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
w := io.MultiWriter(os.Stdout, os.Stderr)
|
||||
outf, _ := os.Stdout.Stat()
|
||||
errf, _ := os.Stderr.Stat()
|
||||
if outf != nil && errf != nil && os.SameFile(outf, errf) {
|
||||
w = 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()
|
||||
errf, _ := os.Stderr.Stat()
|
||||
if outf != nil && errf != nil && os.SameFile(outf, errf) {
|
||||
w = os.Stderr
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "Fatal: "+format+"\n", args...)
|
||||
logger.Flush()
|
||||
|
@@ -95,10 +95,11 @@ type BlockChain struct {
|
||||
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!)
|
||||
|
||||
bodyCache *lru.Cache // Cache for the most recent block bodies
|
||||
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
|
||||
blockCache *lru.Cache // Cache for the most recent entire blocks
|
||||
futureBlocks *lru.Cache // future blocks are blocks added for later processing
|
||||
stateCache *state.StateDB // State database to reuse between imports (contains state cache)
|
||||
bodyCache *lru.Cache // Cache for the most recent block bodies
|
||||
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
|
||||
blockCache *lru.Cache // Cache for the most recent entire blocks
|
||||
futureBlocks *lru.Cache // future blocks are blocks added for later processing
|
||||
|
||||
quit chan struct{} // blockchain quit channel
|
||||
running int32 // running must be called atomically
|
||||
@@ -198,11 +199,18 @@ func (self *BlockChain) loadLastState() error {
|
||||
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
|
||||
headerTd := self.GetTd(self.hc.CurrentHeader().Hash())
|
||||
blockTd := self.GetTd(self.currentBlock.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 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)
|
||||
@@ -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.
|
||||
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.
|
||||
@@ -770,6 +783,14 @@ func (self *BlockChain) WriteBlock(block *types.Block) (status WriteStatus, err
|
||||
localTd := self.GetTd(self.currentBlock.Hash())
|
||||
externTd := new(big.Int).Add(block.Difficulty(), ptd)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
|
||||
@@ -780,20 +801,11 @@ func (self *BlockChain) WriteBlock(block *types.Block) (status WriteStatus, err
|
||||
return NonStatTy, err
|
||||
}
|
||||
}
|
||||
// Insert the block as the new head of the chain
|
||||
self.insert(block)
|
||||
self.insert(block) // Insert the block as the new head of the chain
|
||||
status = CanonStatTy
|
||||
} else {
|
||||
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())
|
||||
|
||||
return
|
||||
@@ -818,7 +830,6 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||
tstart = time.Now()
|
||||
|
||||
nonceChecked = make([]bool, len(chain))
|
||||
statedb *state.StateDB
|
||||
)
|
||||
|
||||
// Start the parallel nonce verifier.
|
||||
@@ -885,29 +896,30 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||
|
||||
// Create a new statedb using the parent block and report an
|
||||
// error if it fails.
|
||||
if statedb == nil {
|
||||
statedb, err = state.New(self.GetBlock(block.ParentHash()).Root(), self.chainDb)
|
||||
} else {
|
||||
err = statedb.Reset(chain[i-1].Root())
|
||||
switch {
|
||||
case i == 0:
|
||||
err = self.stateCache.Reset(self.GetBlock(block.ParentHash()).Root())
|
||||
default:
|
||||
err = self.stateCache.Reset(chain[i-1].Root())
|
||||
}
|
||||
if err != nil {
|
||||
reportBlock(block, err)
|
||||
return i, err
|
||||
}
|
||||
// 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 {
|
||||
reportBlock(block, err)
|
||||
return i, err
|
||||
}
|
||||
// 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 {
|
||||
reportBlock(block, err)
|
||||
return i, err
|
||||
}
|
||||
// Write state changes to database
|
||||
_, err = statedb.Commit()
|
||||
_, err = self.stateCache.Commit()
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
@@ -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]})
|
||||
}
|
||||
}
|
||||
|
@@ -79,7 +79,7 @@ func ExampleGenerateChain() {
|
||||
evmux := &event.TypeMux{}
|
||||
blockchain, _ := NewBlockChain(db, MakeChainConfig(), FakePow{}, evmux)
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
// 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 {
|
||||
// Retrieve the block header and body contents
|
||||
header := GetHeader(db, hash)
|
||||
|
@@ -27,14 +27,14 @@ import (
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address
|
||||
callerAddr := caller.Address()
|
||||
originAddr := env.Origin()
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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,
|
||||
// See: https://github.com/ethereum/yellowpaper/pull/131
|
||||
// and YP definitions for CREATE instruction
|
||||
@@ -59,7 +59,7 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPric
|
||||
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()
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
@@ -105,7 +105,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
|
||||
// EVM. The contract is a scoped environment for this execution context
|
||||
// only.
|
||||
contract := vm.NewContract(caller, to, value, gas, gasPrice)
|
||||
contract.SetCallCode(codeAddr, code)
|
||||
contract.SetCallCode(codeAddr, codeHash, code)
|
||||
defer contract.Finalise()
|
||||
|
||||
ret, err = evm.Run(contract, input)
|
||||
@@ -135,7 +135,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
|
||||
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()
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
@@ -155,7 +155,7 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA
|
||||
|
||||
// Iinitialise a new contract and make initialise the delegate values
|
||||
contract := vm.NewContract(caller, to, value, gas, gasPrice).AsDelegate()
|
||||
contract.SetCallCode(codeAddr, code)
|
||||
contract.SetCallCode(codeAddr, codeHash, code)
|
||||
defer contract.Finalise()
|
||||
|
||||
ret, err = evm.Run(contract, input)
|
||||
|
@@ -129,6 +129,14 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
|
||||
localTd := hc.GetTd(hc.currentHeaderHash)
|
||||
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
|
||||
// Second clause in the if statement reduces the vulnerability to selfish mining.
|
||||
// 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)
|
||||
headNumber = headHeader.Number.Uint64()
|
||||
}
|
||||
|
||||
// Extend the canonical chain with the new header
|
||||
if err := WriteCanonicalHash(hc.chainDb, hash, number); err != nil {
|
||||
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 {
|
||||
glog.Fatalf("failed to insert head header hash: %v", err)
|
||||
}
|
||||
|
||||
hc.currentHeaderHash, hc.currentHeader = hash, types.CopyHeader(header)
|
||||
|
||||
status = CanonStatTy
|
||||
} else {
|
||||
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)
|
||||
|
||||
return
|
||||
|
@@ -21,9 +21,10 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
type DumpAccount struct {
|
||||
Balance string `json:"balance"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Root string `json:"root"`
|
||||
@@ -32,40 +33,41 @@ type Account struct {
|
||||
Storage map[string]string `json:"storage"`
|
||||
}
|
||||
|
||||
type World struct {
|
||||
Root string `json:"root"`
|
||||
Accounts map[string]Account `json:"accounts"`
|
||||
type Dump struct {
|
||||
Root string `json:"root"`
|
||||
Accounts map[string]DumpAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
func (self *StateDB) RawDump() World {
|
||||
world := World{
|
||||
func (self *StateDB) RawDump() Dump {
|
||||
dump := Dump{
|
||||
Root: common.Bytes2Hex(self.trie.Root()),
|
||||
Accounts: make(map[string]Account),
|
||||
Accounts: make(map[string]DumpAccount),
|
||||
}
|
||||
|
||||
it := self.trie.Iterator()
|
||||
for it.Next() {
|
||||
addr := self.trie.GetKey(it.Key)
|
||||
stateObject, err := DecodeObject(common.BytesToAddress(addr), self.db, it.Value)
|
||||
if err != nil {
|
||||
var data Account
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
account := Account{
|
||||
Balance: stateObject.balance.String(),
|
||||
Nonce: stateObject.nonce,
|
||||
Root: common.Bytes2Hex(stateObject.Root()),
|
||||
CodeHash: common.Bytes2Hex(stateObject.codeHash),
|
||||
Code: common.Bytes2Hex(stateObject.Code()),
|
||||
obj := NewObject(common.BytesToAddress(addr), data, nil)
|
||||
account := DumpAccount{
|
||||
Balance: data.Balance.String(),
|
||||
Nonce: data.Nonce,
|
||||
Root: common.Bytes2Hex(data.Root[:]),
|
||||
CodeHash: common.Bytes2Hex(data.CodeHash),
|
||||
Code: common.Bytes2Hex(obj.Code(self.db)),
|
||||
Storage: make(map[string]string),
|
||||
}
|
||||
storageIt := stateObject.trie.Iterator()
|
||||
storageIt := obj.getTrie(self.db).Iterator()
|
||||
for storageIt.Next() {
|
||||
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 {
|
||||
@@ -76,12 +78,3 @@ func (self *StateDB) Dump() []byte {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@ func (it *NodeIterator) step() error {
|
||||
}
|
||||
// Initialize the iterator if we've just started
|
||||
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 it.dataIt != nil {
|
||||
|
@@ -33,14 +33,14 @@ type ManagedState struct {
|
||||
|
||||
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
|
||||
func ManageState(statedb *StateDB) *ManagedState {
|
||||
return &ManagedState{
|
||||
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.SetNonce(nonce)
|
||||
|
||||
ms.accounts[addr.Str()] = newAccount(so)
|
||||
ms.accounts[addr] = newAccount(so)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
_, ok := ms.accounts[addr.Str()]
|
||||
_, ok := ms.accounts[addr]
|
||||
return ok
|
||||
}
|
||||
|
||||
// populate the managed state
|
||||
func (ms *ManagedState) getAccount(addr common.Address) *account {
|
||||
straddr := addr.Str()
|
||||
if account, ok := ms.accounts[straddr]; !ok {
|
||||
if account, ok := ms.accounts[addr]; !ok {
|
||||
so := ms.GetOrNewStateObject(addr)
|
||||
ms.accounts[straddr] = newAccount(so)
|
||||
ms.accounts[addr] = newAccount(so)
|
||||
} else {
|
||||
// Always make sure the state account nonce isn't actually higher
|
||||
// than the tracked one.
|
||||
so := ms.StateDB.GetStateObject(addr)
|
||||
if so != nil && uint64(len(account.nonces))+account.nstart < so.nonce {
|
||||
ms.accounts[straddr] = newAccount(so)
|
||||
if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
|
||||
ms.accounts[addr] = newAccount(so)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ms.accounts[straddr]
|
||||
return ms.accounts[addr]
|
||||
}
|
||||
|
||||
func newAccount(so *StateObject) *account {
|
||||
return &account{so, so.nonce, nil}
|
||||
return &account{so, so.Nonce(), nil}
|
||||
}
|
||||
|
@@ -29,11 +29,12 @@ func create() (*ManagedState, *account) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := New(common.Hash{}, db)
|
||||
ms := ManageState(statedb)
|
||||
so := &StateObject{address: addr, nonce: 100}
|
||||
ms.StateDB.stateObjects[addr.Str()] = so
|
||||
ms.accounts[addr.Str()] = newAccount(so)
|
||||
so := &StateObject{address: addr}
|
||||
so.SetNonce(100)
|
||||
ms.StateDB.stateObjects[addr] = so
|
||||
ms.accounts[addr] = newAccount(so)
|
||||
|
||||
return ms, ms.accounts[addr.Str()]
|
||||
return ms, ms.accounts[addr]
|
||||
}
|
||||
|
||||
func TestNewNonce(t *testing.T) {
|
||||
@@ -92,7 +93,7 @@ func TestRemoteNonceChange(t *testing.T) {
|
||||
account.nonces = append(account.nonces, nn...)
|
||||
nonce := ms.NewNonce(addr)
|
||||
|
||||
ms.StateDB.stateObjects[addr.Str()].nonce = 200
|
||||
ms.StateDB.stateObjects[addr].data.Nonce = 200
|
||||
nonce = ms.NewNonce(addr)
|
||||
if nonce != 200 {
|
||||
t.Error("expected nonce after remote update to be", 201, "got", nonce)
|
||||
@@ -100,7 +101,7 @@ func TestRemoteNonceChange(t *testing.T) {
|
||||
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)
|
||||
if nonce != 204 {
|
||||
t.Error("expected nonce after remote update to be", 201, "got", nonce)
|
||||
|
@@ -57,143 +57,200 @@ func (self Storage) Copy() Storage {
|
||||
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 {
|
||||
db trie.Database // State database for storing state changes
|
||||
trie *trie.SecureTrie
|
||||
address common.Address // Ethereum address of this account
|
||||
data Account
|
||||
|
||||
// Address belonging to this account
|
||||
address common.Address
|
||||
// The balance of the account
|
||||
balance *big.Int
|
||||
// The nonce of the account
|
||||
nonce uint64
|
||||
// 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
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by StateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Mark for deletion
|
||||
// Write caches.
|
||||
trie *trie.SecureTrie // storage trie, which becomes non-nil on first access
|
||||
code Code // contract bytecode, which gets set when code is loaded
|
||||
|
||||
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 for deletion it will be delete from the trie
|
||||
// during the "update" phase of the state transition
|
||||
remove bool
|
||||
deleted bool
|
||||
dirty bool
|
||||
dirtyCode bool // true if the code was updated
|
||||
remove bool
|
||||
deleted bool
|
||||
onDirty func(addr common.Address) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
func NewStateObject(address common.Address, db trie.Database) *StateObject {
|
||||
object := &StateObject{
|
||||
db: db,
|
||||
address: address,
|
||||
balance: new(big.Int),
|
||||
dirty: true,
|
||||
codeHash: emptyCodeHash,
|
||||
storage: make(Storage),
|
||||
// Account is the Ethereum consensus representation of accounts.
|
||||
// These objects are stored in the main account trie.
|
||||
type Account struct {
|
||||
Nonce uint64
|
||||
Balance *big.Int
|
||||
Root common.Hash // merkle root of the storage trie
|
||||
CodeHash []byte
|
||||
}
|
||||
|
||||
// NewObject creates a state object.
|
||||
func NewObject(address common.Address, data Account, onDirty func(addr common.Address)) *StateObject {
|
||||
if data.Balance == nil {
|
||||
data.Balance = new(big.Int)
|
||||
}
|
||||
if data.CodeHash == nil {
|
||||
data.CodeHash = emptyCodeHash
|
||||
}
|
||||
return &StateObject{address: address, data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), onDirty: onDirty}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *StateObject) EncodeRLP(w io.Writer) error {
|
||||
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
|
||||
}
|
||||
object.trie, _ = trie.NewSecure(common.Hash{}, db)
|
||||
return object
|
||||
}
|
||||
|
||||
func (self *StateObject) MarkForDeletion() {
|
||||
self.remove = true
|
||||
self.dirty = true
|
||||
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
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 {
|
||||
var ret []byte
|
||||
rlp.DecodeBytes(c.trie.Get(addr[:]), &ret)
|
||||
return common.BytesToHash(ret)
|
||||
}
|
||||
|
||||
func (c *StateObject) setAddr(addr, value common.Hash) {
|
||||
v, err := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
|
||||
if err != nil {
|
||||
// if RLPing failed we better panic and not fail silently. This would be considered a consensus issue
|
||||
panic(err)
|
||||
}
|
||||
c.trie.Update(addr[:], v)
|
||||
}
|
||||
|
||||
func (self *StateObject) Storage() Storage {
|
||||
return self.storage
|
||||
}
|
||||
|
||||
func (self *StateObject) GetState(key common.Hash) common.Hash {
|
||||
value, exists := self.storage[key]
|
||||
if !exists {
|
||||
value = self.getAddr(key)
|
||||
if (value != common.Hash{}) {
|
||||
self.storage[key] = value
|
||||
func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie {
|
||||
if c.trie == nil {
|
||||
var err error
|
||||
c.trie, err = trie.NewSecure(c.data.Root, db)
|
||||
if err != nil {
|
||||
c.trie, _ = trie.NewSecure(common.Hash{}, db)
|
||||
c.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
}
|
||||
}
|
||||
return c.trie
|
||||
}
|
||||
|
||||
// GetState returns a value in account storage.
|
||||
func (self *StateObject) GetState(db trie.Database, key common.Hash) common.Hash {
|
||||
value, exists := self.cachedStorage[key]
|
||||
if exists {
|
||||
return value
|
||||
}
|
||||
// Load from DB in case it is missing.
|
||||
tr := self.getTrie(db)
|
||||
var ret []byte
|
||||
rlp.DecodeBytes(tr.Get(key[:]), &ret)
|
||||
value = common.BytesToHash(ret)
|
||||
if (value != common.Hash{}) {
|
||||
self.cachedStorage[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// SetState updates a value in account storage.
|
||||
func (self *StateObject) SetState(key, value common.Hash) {
|
||||
self.storage[key] = value
|
||||
self.dirty = true
|
||||
}
|
||||
self.cachedStorage[key] = value
|
||||
self.dirtyStorage[key] = value
|
||||
|
||||
// Update updates the current cached storage to the trie
|
||||
func (self *StateObject) Update() {
|
||||
for key, value := range self.storage {
|
||||
if (value == common.Hash{}) {
|
||||
self.trie.Delete(key[:])
|
||||
continue
|
||||
}
|
||||
self.setAddr(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{}) {
|
||||
tr.Delete(key[:])
|
||||
continue
|
||||
}
|
||||
// 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 {
|
||||
fmt.Println("dbErr:", self.dbErr)
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
c.balance = amount
|
||||
c.dirty = true
|
||||
}
|
||||
|
||||
func (c *StateObject) St() Storage {
|
||||
return c.storage
|
||||
func (self *StateObject) SetBalance(amount *big.Int) {
|
||||
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
|
||||
func (c *StateObject) ReturnGas(gas, price *big.Int) {}
|
||||
|
||||
func (self *StateObject) Copy() *StateObject {
|
||||
stateObject := NewStateObject(self.Address(), self.db)
|
||||
stateObject.balance.Set(self.balance)
|
||||
stateObject.codeHash = common.CopyBytes(self.codeHash)
|
||||
stateObject.nonce = self.nonce
|
||||
func (self *StateObject) Copy(db trie.Database, onDirty func(addr common.Address)) *StateObject {
|
||||
stateObject := NewObject(self.address, self.data, onDirty)
|
||||
stateObject.trie = self.trie
|
||||
stateObject.code = common.CopyBytes(self.code)
|
||||
stateObject.initCode = common.CopyBytes(self.initCode)
|
||||
stateObject.storage = self.storage.Copy()
|
||||
stateObject.code = self.code
|
||||
stateObject.dirtyStorage = self.dirtyStorage.Copy()
|
||||
stateObject.cachedStorage = self.dirtyStorage.Copy()
|
||||
stateObject.remove = self.remove
|
||||
stateObject.dirty = self.dirty
|
||||
stateObject.dirtyCode = self.dirtyCode
|
||||
stateObject.deleted = self.deleted
|
||||
|
||||
return stateObject
|
||||
}
|
||||
|
||||
@@ -201,40 +258,55 @@ func (self *StateObject) Copy() *StateObject {
|
||||
// Attribute accessors
|
||||
//
|
||||
|
||||
func (self *StateObject) Balance() *big.Int {
|
||||
return self.balance
|
||||
}
|
||||
|
||||
// Returns the address of the contract/account
|
||||
func (c *StateObject) Address() common.Address {
|
||||
return c.address
|
||||
}
|
||||
|
||||
func (self *StateObject) Trie() *trie.SecureTrie {
|
||||
return self.trie
|
||||
}
|
||||
|
||||
func (self *StateObject) Root() []byte {
|
||||
return self.trie.Root()
|
||||
}
|
||||
|
||||
func (self *StateObject) Code() []byte {
|
||||
return self.code
|
||||
}
|
||||
|
||||
func (self *StateObject) SetCode(code []byte) {
|
||||
// Code returns the contract code associated with this object, if any.
|
||||
func (self *StateObject) Code(db trie.Database) []byte {
|
||||
if self.code != nil {
|
||||
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
|
||||
self.codeHash = crypto.Keccak256(code)
|
||||
self.dirty = true
|
||||
return code
|
||||
}
|
||||
|
||||
func (self *StateObject) SetCode(codeHash common.Hash, code []byte) {
|
||||
self.code = code
|
||||
self.data.CodeHash = codeHash[:]
|
||||
self.dirtyCode = true
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StateObject) SetNonce(nonce uint64) {
|
||||
self.nonce = nonce
|
||||
self.dirty = true
|
||||
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 {
|
||||
return self.nonce
|
||||
return self.data.Nonce
|
||||
}
|
||||
|
||||
// Never called, but must be present to allow StateObject to be used
|
||||
@@ -246,7 +318,7 @@ func (self *StateObject) Value() *big.Int {
|
||||
|
||||
func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) {
|
||||
// When iterating over the storage check the cache first
|
||||
for h, value := range self.storage {
|
||||
for h, value := range self.cachedStorage {
|
||||
cb(h, value)
|
||||
}
|
||||
|
||||
@@ -254,44 +326,8 @@ func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) {
|
||||
for it.Next() {
|
||||
// ignore cached values
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import (
|
||||
checker "gopkg.in/check.v1"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func (s *StateSuite) TestDump(c *checker.C) {
|
||||
obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01}))
|
||||
obj1.AddBalance(big.NewInt(22))
|
||||
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.SetBalance(big.NewInt(44))
|
||||
|
||||
@@ -146,22 +147,23 @@ func TestSnapshot2(t *testing.T) {
|
||||
|
||||
// db, trie are already non-empty values
|
||||
so0 := state.GetStateObject(stateobjaddr0)
|
||||
so0.balance = big.NewInt(42)
|
||||
so0.nonce = 43
|
||||
so0.SetCode([]byte{'c', 'a', 'f', 'e'})
|
||||
so0.remove = true
|
||||
so0.SetBalance(big.NewInt(42))
|
||||
so0.SetNonce(43)
|
||||
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
||||
so0.remove = false
|
||||
so0.deleted = false
|
||||
so0.dirty = false
|
||||
state.SetStateObject(so0)
|
||||
|
||||
root, _ := state.Commit()
|
||||
state.Reset(root)
|
||||
|
||||
// and one with deleted == true
|
||||
so1 := state.GetStateObject(stateobjaddr1)
|
||||
so1.balance = big.NewInt(52)
|
||||
so1.nonce = 53
|
||||
so1.SetCode([]byte{'c', 'a', 'f', 'e', '2'})
|
||||
so1.SetBalance(big.NewInt(52))
|
||||
so1.SetNonce(53)
|
||||
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
|
||||
so1.remove = true
|
||||
so1.deleted = true
|
||||
so1.dirty = true
|
||||
state.SetStateObject(so1)
|
||||
|
||||
so1 = state.GetStateObject(stateobjaddr1)
|
||||
@@ -173,43 +175,50 @@ func TestSnapshot2(t *testing.T) {
|
||||
state.Set(snapshot)
|
||||
|
||||
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)
|
||||
compareStateObjects(so0Restored, so0, t)
|
||||
|
||||
// deleted should be nil, both before and after restore of state copy
|
||||
so1Restored := state.GetStateObject(stateobjaddr1)
|
||||
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) {
|
||||
if so0.address != so1.address {
|
||||
if so0.Address() != so1.Address() {
|
||||
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
|
||||
}
|
||||
if so0.balance.Cmp(so1.balance) != 0 {
|
||||
t.Fatalf("Balance mismatch: have %v, want %v", so0.balance, so1.balance)
|
||||
if so0.Balance().Cmp(so1.Balance()) != 0 {
|
||||
t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance())
|
||||
}
|
||||
if so0.nonce != so1.nonce {
|
||||
t.Fatalf("Nonce mismatch: have %v, want %v", so0.nonce, so1.nonce)
|
||||
if so0.Nonce() != so1.Nonce() {
|
||||
t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce())
|
||||
}
|
||||
if !bytes.Equal(so0.codeHash, so1.codeHash) {
|
||||
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.codeHash, so1.codeHash)
|
||||
if so0.data.Root != so1.data.Root {
|
||||
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) {
|
||||
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 so0.storage[k] != v {
|
||||
t.Fatalf("Storage key %s mismatch: have %v, want %v", k, so0.storage[k], v)
|
||||
if len(so1.cachedStorage) != len(so0.cachedStorage) {
|
||||
t.Errorf("Storage size mismatch: have %d, want %d", len(so1.cachedStorage), len(so0.cachedStorage))
|
||||
}
|
||||
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 {
|
||||
if so1.storage[k] != v {
|
||||
t.Fatalf("Storage key %s mismatch: have %v, want none.", k, v)
|
||||
for k, v := range so0.cachedStorage {
|
||||
if so1.cachedStorage[k] != v {
|
||||
t.Errorf("Storage key %x mismatch: have %v, want none.", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +228,4 @@ func compareStateObjects(so0, so1 *StateObject, t *testing.T) {
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
|
@@ -20,37 +20,56 @@ package state
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// The starting nonce determines the default nonce when new accounts are being
|
||||
// created.
|
||||
var StartingNonce uint64
|
||||
|
||||
const (
|
||||
// Number of past tries to keep. The arbitrarily chosen value here
|
||||
// is max uncle depth + 1.
|
||||
maxJournalLength = 8
|
||||
|
||||
// Number of codehash->size associations to keep.
|
||||
codeSizeCacheSize = 100000
|
||||
)
|
||||
|
||||
// StateDBs within the ethereum protocol are used to store anything
|
||||
// within the merkle trie. StateDBs take care of caching and storing
|
||||
// nested states. It's the general query interface to retrieve:
|
||||
// * Contracts
|
||||
// * Accounts
|
||||
type StateDB struct {
|
||||
db ethdb.Database
|
||||
trie *trie.SecureTrie
|
||||
db ethdb.Database
|
||||
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
|
||||
|
||||
thash, bhash common.Hash
|
||||
txIndex int
|
||||
logs map[common.Hash]vm.Logs
|
||||
logSize uint
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new state from a given trie
|
||||
@@ -59,35 +78,84 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csc, _ := lru.New(codeSizeCacheSize)
|
||||
return &StateDB{
|
||||
db: db,
|
||||
trie: tr,
|
||||
stateObjects: make(map[string]*StateObject),
|
||||
refund: new(big.Int),
|
||||
logs: make(map[common.Hash]vm.Logs),
|
||||
db: db,
|
||||
trie: tr,
|
||||
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),
|
||||
logs: make(map[common.Hash]vm.Logs),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (self *StateDB) Reset(root common.Hash) error {
|
||||
var (
|
||||
err error
|
||||
tr = self.trie
|
||||
)
|
||||
if self.trie.Hash() != root {
|
||||
if tr, err = trie.NewSecure(root, self.db); err != nil {
|
||||
return err
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
tr, err := self.openTrie(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.trie = tr
|
||||
self.stateObjects = make(map[common.Address]*StateObject)
|
||||
self.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
self.refund = new(big.Int)
|
||||
self.thash = common.Hash{}
|
||||
self.bhash = common.Hash{}
|
||||
self.txIndex = 0
|
||||
self.logs = make(map[common.Hash]vm.Logs)
|
||||
self.logSize = 0
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
*self = StateDB{
|
||||
db: self.db,
|
||||
trie: tr,
|
||||
stateObjects: make(map[string]*StateObject),
|
||||
refund: new(big.Int),
|
||||
logs: make(map[common.Hash]vm.Logs),
|
||||
return trie.NewSecure(root, self.db)
|
||||
}
|
||||
|
||||
func (self *StateDB) pushTrie(t *trie.SecureTrie) {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
if len(self.pastTries) >= maxJournalLength {
|
||||
copy(self.pastTries, self.pastTries[1:])
|
||||
self.pastTries[len(self.pastTries)-1] = t
|
||||
} else {
|
||||
self.pastTries = append(self.pastTries, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) {
|
||||
@@ -137,7 +205,7 @@ func (self *StateDB) GetAccount(addr common.Address) vm.Account {
|
||||
func (self *StateDB) GetBalance(addr common.Address) *big.Int {
|
||||
stateObject := self.GetStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.balance
|
||||
return stateObject.Balance()
|
||||
}
|
||||
|
||||
return common.Big0
|
||||
@@ -146,7 +214,7 @@ func (self *StateDB) GetBalance(addr common.Address) *big.Int {
|
||||
func (self *StateDB) GetNonce(addr common.Address) uint64 {
|
||||
stateObject := self.GetStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.nonce
|
||||
return stateObject.Nonce()
|
||||
}
|
||||
|
||||
return StartingNonce
|
||||
@@ -155,18 +223,43 @@ func (self *StateDB) GetNonce(addr common.Address) uint64 {
|
||||
func (self *StateDB) GetCode(addr common.Address) []byte {
|
||||
stateObject := self.GetStateObject(addr)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
stateObject := self.GetStateObject(a)
|
||||
if stateObject != nil {
|
||||
return stateObject.GetState(b)
|
||||
return stateObject.GetState(self.db, b)
|
||||
}
|
||||
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
@@ -199,7 +292,7 @@ func (self *StateDB) SetNonce(addr common.Address, nonce uint64) {
|
||||
func (self *StateDB) SetCode(addr common.Address, code []byte) {
|
||||
stateObject := self.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.SetCode(code)
|
||||
stateObject.SetCode(crypto.Keccak256Hash(code), code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +307,7 @@ func (self *StateDB) Delete(addr common.Address) bool {
|
||||
stateObject := self.GetStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.MarkForDeletion()
|
||||
stateObject.balance = new(big.Int)
|
||||
|
||||
stateObject.data.Balance = new(big.Int)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -242,35 +334,36 @@ func (self *StateDB) DeleteStateObject(stateObject *StateObject) {
|
||||
|
||||
addr := stateObject.Address()
|
||||
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) {
|
||||
stateObject = self.stateObjects[addr.Str()]
|
||||
if stateObject != nil {
|
||||
if stateObject.deleted {
|
||||
stateObject = nil
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateObjects[addr]; obj != nil {
|
||||
if obj.deleted {
|
||||
return nil
|
||||
}
|
||||
|
||||
return stateObject
|
||||
return obj
|
||||
}
|
||||
|
||||
data := self.trie.Get(addr[:])
|
||||
if len(data) == 0 {
|
||||
// Load the object from the database.
|
||||
enc := self.trie.Get(addr[:])
|
||||
if len(enc) == 0 {
|
||||
return nil
|
||||
}
|
||||
stateObject, err := DecodeObject(addr, self.db, data)
|
||||
if err != nil {
|
||||
var data Account
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
glog.Errorf("can't decode object at %x: %v", addr[:], err)
|
||||
return nil
|
||||
}
|
||||
self.SetStateObject(stateObject)
|
||||
return stateObject
|
||||
// Insert into the live set.
|
||||
obj := NewObject(addr, data, self.MarkStateObjectDirty)
|
||||
self.SetStateObject(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
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
|
||||
@@ -288,15 +381,19 @@ func (self *StateDB) newStateObject(addr common.Address) *StateObject {
|
||||
if glog.V(logger.Core) {
|
||||
glog.Infof("(+) %x\n", addr)
|
||||
}
|
||||
|
||||
stateObject := NewStateObject(addr, self.db)
|
||||
stateObject.SetNonce(StartingNonce)
|
||||
self.stateObjects[addr.Str()] = stateObject
|
||||
|
||||
return stateObject
|
||||
obj := NewObject(addr, Account{}, self.MarkStateObjectDirty)
|
||||
obj.SetNonce(StartingNonce) // sets the object to dirty
|
||||
self.stateObjects[addr] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
// Creates creates a new state object and takes ownership. This is different from "NewStateObject"
|
||||
// MarkStateObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *StateDB) MarkStateObjectDirty(addr common.Address) {
|
||||
self.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
|
||||
// Creates creates a new state object and takes ownership.
|
||||
func (self *StateDB) CreateStateObject(addr common.Address) *StateObject {
|
||||
// Get previous (if any)
|
||||
so := self.GetStateObject(addr)
|
||||
@@ -305,7 +402,7 @@ func (self *StateDB) CreateStateObject(addr common.Address) *StateObject {
|
||||
|
||||
// If it existed set the balance to the new account
|
||||
if so != nil {
|
||||
newSo.balance = so.balance
|
||||
newSo.data.Balance = so.data.Balance
|
||||
}
|
||||
|
||||
return newSo
|
||||
@@ -320,28 +417,43 @@ func (self *StateDB) CreateAccount(addr common.Address) vm.Account {
|
||||
//
|
||||
|
||||
func (self *StateDB) Copy() *StateDB {
|
||||
// ignore error - we assume state-to-be-copied always exists
|
||||
state, _ := New(common.Hash{}, self.db)
|
||||
state.trie = self.trie
|
||||
for k, stateObject := range self.stateObjects {
|
||||
state.stateObjects[k] = stateObject.Copy()
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
// Copy all the basic fields, initialize the memory ones
|
||||
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].Copy(self.db, state.MarkStateObjectDirty)
|
||||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
|
||||
state.refund.Set(self.refund)
|
||||
|
||||
for hash, logs := range self.logs {
|
||||
state.logs[hash] = make(vm.Logs, len(logs))
|
||||
copy(state.logs[hash], logs)
|
||||
}
|
||||
state.logSize = self.logSize
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func (self *StateDB) Set(state *StateDB) {
|
||||
self.trie = state.trie
|
||||
self.stateObjects = state.stateObjects
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
self.db = state.db
|
||||
self.trie = state.trie
|
||||
self.pastTries = state.pastTries
|
||||
self.stateObjects = state.stateObjects
|
||||
self.stateObjectsDirty = state.stateObjectsDirty
|
||||
self.codeSizeCache = state.codeSizeCache
|
||||
self.refund = state.refund
|
||||
self.logs = state.logs
|
||||
self.logSize = state.logSize
|
||||
@@ -356,15 +468,13 @@ func (self *StateDB) GetRefund() *big.Int {
|
||||
// goes into transaction receipts.
|
||||
func (s *StateDB) IntermediateRoot() common.Hash {
|
||||
s.refund = new(big.Int)
|
||||
for _, stateObject := range s.stateObjects {
|
||||
if stateObject.dirty {
|
||||
if stateObject.remove {
|
||||
s.DeleteStateObject(stateObject)
|
||||
} else {
|
||||
stateObject.Update()
|
||||
s.UpdateStateObject(stateObject)
|
||||
}
|
||||
stateObject.dirty = false
|
||||
for addr, _ := range s.stateObjectsDirty {
|
||||
stateObject := s.stateObjects[addr]
|
||||
if stateObject.remove {
|
||||
s.DeleteStateObject(stateObject)
|
||||
} else {
|
||||
stateObject.UpdateRoot(s.db)
|
||||
s.UpdateStateObject(stateObject)
|
||||
}
|
||||
}
|
||||
return s.trie.Hash()
|
||||
@@ -379,15 +489,15 @@ func (s *StateDB) DeleteSuicides() {
|
||||
// Reset refund so that any used-gas calculations can use
|
||||
// this method.
|
||||
s.refund = new(big.Int)
|
||||
for _, stateObject := range s.stateObjects {
|
||||
if stateObject.dirty {
|
||||
// If the object has been removed by a suicide
|
||||
// flag the object as deleted.
|
||||
if stateObject.remove {
|
||||
stateObject.deleted = true
|
||||
}
|
||||
stateObject.dirty = false
|
||||
for addr, _ := range s.stateObjectsDirty {
|
||||
stateObject := s.stateObjects[addr]
|
||||
|
||||
// If the object has been removed by a suicide
|
||||
// flag the object as deleted.
|
||||
if stateObject.remove {
|
||||
stateObject.deleted = true
|
||||
}
|
||||
delete(s.stateObjectsDirty, addr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,46 +516,40 @@ func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) {
|
||||
return root, batch
|
||||
}
|
||||
|
||||
func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) {
|
||||
func (s *StateDB) commit(dbw trie.DatabaseWriter) (root common.Hash, err error) {
|
||||
s.refund = new(big.Int)
|
||||
|
||||
for _, stateObject := range s.stateObjects {
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.stateObjects {
|
||||
if stateObject.remove {
|
||||
// If the object has been removed, don't bother syncing it
|
||||
// and just mark it for deletion in the trie.
|
||||
s.DeleteStateObject(stateObject)
|
||||
} else {
|
||||
} else if _, ok := s.stateObjectsDirty[addr]; ok {
|
||||
// Write any contract code associated with the state object
|
||||
if len(stateObject.code) > 0 {
|
||||
if err := db.Put(stateObject.codeHash, stateObject.code); err != nil {
|
||||
if stateObject.code != nil && stateObject.dirtyCode {
|
||||
if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
stateObject.dirtyCode = false
|
||||
}
|
||||
// Write any storage changes in the state object to its trie.
|
||||
stateObject.Update()
|
||||
|
||||
// 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 {
|
||||
// Write any storage changes in the state object to its storage trie.
|
||||
if err := stateObject.CommitTrie(s.db, dbw); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// Update the object in the account trie.
|
||||
// Update the object in the main account trie.
|
||||
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 {
|
||||
s.pushTrie(s.trie)
|
||||
}
|
||||
return root, err
|
||||
}
|
||||
|
||||
func (self *StateDB) Refunds() *big.Int {
|
||||
return self.refund
|
||||
}
|
||||
|
||||
// Debug stuff
|
||||
func (self *StateDB) CreateOutputForDiff() {
|
||||
for _, stateObject := range self.stateObjects {
|
||||
stateObject.CreateOutputForDiff()
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func TestUpdateLeaks(t *testing.T) {
|
||||
obj.SetState(common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
|
||||
}
|
||||
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})
|
||||
}
|
||||
state.UpdateStateObject(obj)
|
||||
}
|
||||
@@ -70,7 +71,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.BytesToHash([]byte{i, i, i, i, 0}))
|
||||
}
|
||||
if i%3 == 0 {
|
||||
obj.SetCode([]byte{i, i, i, i, i, 0})
|
||||
obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i, 0}), []byte{i, i, i, i, i, 0})
|
||||
}
|
||||
transState.UpdateStateObject(obj)
|
||||
|
||||
@@ -82,7 +83,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
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})
|
||||
obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i, 1}), []byte{i, i, i, i, i, 1})
|
||||
}
|
||||
transState.UpdateStateObject(obj)
|
||||
|
||||
@@ -94,7 +95,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
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})
|
||||
obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i, 1}), []byte{i, i, i, i, i, 1})
|
||||
}
|
||||
finalState.UpdateStateObject(obj)
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
|
||||
acc.nonce = uint64(42 * i)
|
||||
|
||||
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}
|
||||
}
|
||||
state.UpdateStateObject(obj)
|
||||
@@ -62,9 +62,6 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
|
||||
}
|
||||
root, _ := state.Commit()
|
||||
|
||||
// Remove any potentially cached data from the test state creation
|
||||
trie.ClearGlobalCache()
|
||||
|
||||
// Return the generated state
|
||||
return db, root, accounts
|
||||
}
|
||||
@@ -72,9 +69,6 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
|
||||
// checkStateAccounts cross references a reconstructed state with an expected
|
||||
// account array.
|
||||
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
|
||||
state, err := New(root, db)
|
||||
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.
|
||||
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
|
||||
if _, err := db.Get(root.Bytes()); err != nil {
|
||||
return nil // Consider a non existent state consistent
|
||||
|
@@ -27,7 +27,7 @@ type ContractRef interface {
|
||||
ReturnGas(*big.Int, *big.Int)
|
||||
Address() common.Address
|
||||
Value() *big.Int
|
||||
SetCode([]byte)
|
||||
SetCode(common.Hash, []byte)
|
||||
ForEachStorage(callback func(key, value common.Hash) bool)
|
||||
}
|
||||
|
||||
@@ -44,8 +44,9 @@ type Contract struct {
|
||||
jumpdests destinations // result of JUMPDEST analysis.
|
||||
|
||||
Code []byte
|
||||
Input []byte
|
||||
CodeHash common.Hash
|
||||
CodeAddr *common.Address
|
||||
Input []byte
|
||||
|
||||
value, Gas, UsedGas, Price *big.Int
|
||||
|
||||
@@ -143,14 +144,16 @@ func (c *Contract) Value() *big.Int {
|
||||
}
|
||||
|
||||
// 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.CodeHash = hash
|
||||
}
|
||||
|
||||
// SetCallCode sets the code of the contract and address of the backing data
|
||||
// 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.CodeHash = hash
|
||||
self.CodeAddr = addr
|
||||
}
|
||||
|
||||
|
@@ -95,7 +95,7 @@ func ecrecoverFunc(in []byte) []byte {
|
||||
|
||||
// tighter sig s values in homestead only apply to tx sigs
|
||||
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
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func ecrecoverFunc(in []byte) []byte {
|
||||
pubKey, err := crypto.Ecrecover(in[:32], rsv)
|
||||
// make sure the public key is a valid one
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("EC RECOVER FAIL: ", err)
|
||||
glog.V(logger.Detail).Infoln("ECRECOVER error: ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -94,6 +94,8 @@ type Database interface {
|
||||
GetNonce(common.Address) uint64
|
||||
SetNonce(common.Address, uint64)
|
||||
|
||||
GetCodeHash(common.Address) common.Hash
|
||||
GetCodeSize(common.Address) int
|
||||
GetCode(common.Address) []byte
|
||||
SetCode(common.Address, []byte)
|
||||
|
||||
@@ -117,7 +119,7 @@ type Account interface {
|
||||
Balance() *big.Int
|
||||
Address() common.Address
|
||||
ReturnGas(*big.Int, *big.Int)
|
||||
SetCode([]byte)
|
||||
SetCode(common.Hash, []byte)
|
||||
ForEachStorage(cb func(key, value common.Hash) bool)
|
||||
Value() *big.Int
|
||||
}
|
||||
|
@@ -363,7 +363,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) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@@ -135,7 +135,7 @@ func (account) SetNonce(uint64) {}
|
||||
func (account) Balance() *big.Int { return nil }
|
||||
func (account) Address() common.Address { return common.Address{} }
|
||||
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 runVmBench(test vmBench, b *testing.B) {
|
||||
|
@@ -30,7 +30,7 @@ type dummyContractRef struct {
|
||||
func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {}
|
||||
func (dummyContractRef) Address() common.Address { return common.Address{} }
|
||||
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) {
|
||||
d.calledForEach = true
|
||||
}
|
||||
|
@@ -104,7 +104,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||
receiver = cfg.State.CreateAccount(common.StringToAddress("contract"))
|
||||
)
|
||||
// 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.
|
||||
ret, err := vmenv.Call(
|
||||
|
@@ -79,10 +79,11 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
codehash = crypto.Keccak256Hash(contract.Code) // codehash is used when doing jump dest caching
|
||||
program *Program
|
||||
)
|
||||
codehash := contract.CodeHash // codehash is used when doing jump dest caching
|
||||
if codehash == (common.Hash{}) {
|
||||
codehash = crypto.Keccak256Hash(contract.Code)
|
||||
}
|
||||
var program *Program
|
||||
if evm.cfg.EnableJit {
|
||||
// If the JIT is enabled check the status of the JIT program,
|
||||
// if it doesn't exist compile a new program in a separate
|
||||
|
12
eth/api.go
12
eth/api.go
@@ -1575,14 +1575,14 @@ func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI {
|
||||
}
|
||||
|
||||
// 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.eth.BlockChain().GetBlockByNumber(number)
|
||||
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.eth.ChainDb())
|
||||
stateDb, err := api.eth.BlockChain().StateAt(block.Root())
|
||||
if err != nil {
|
||||
return state.World{}, err
|
||||
return state.Dump{}, err
|
||||
}
|
||||
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 {
|
||||
return false, collector.traces, err
|
||||
}
|
||||
statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.eth.ChainDb())
|
||||
statedb, err := blockchain.StateAt(blockchain.GetBlock(block.ParentHash()).Root())
|
||||
if err != nil {
|
||||
return false, collector.traces, err
|
||||
}
|
||||
@@ -1850,7 +1850,7 @@ func (api *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogC
|
||||
if parent == nil {
|
||||
return nil, fmt.Errorf("block parent %x not found", block.ParentHash())
|
||||
}
|
||||
stateDb, err := state.New(parent.Root(), api.eth.ChainDb())
|
||||
stateDb, err := api.eth.BlockChain().StateAt(parent.Root())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -48,23 +48,17 @@ var (
|
||||
MaxReceiptFetch = 256 // Amount of transaction receipts 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
|
||||
|
||||
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
|
||||
rttMaxEstimate = 20 * time.Second // Maximum rount-trip time to target for download requests
|
||||
rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value
|
||||
ttlScaling = 3 // Constant scaling factor for RTT -> TTL conversion
|
||||
ttlLimit = time.Minute // Maximum TTL allowance to prevent reaching crazy timeouts
|
||||
MaxForkAncestry = 3 * params.EpochDuration.Uint64() // Maximum chain reorganisation
|
||||
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
|
||||
rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value
|
||||
ttlScaling = 3 // Constant scaling factor for RTT -> TTL conversion
|
||||
ttlLimit = time.Minute // Maximum TTL allowance to prevent reaching crazy timeouts
|
||||
|
||||
qosTuningPeers = 5 // Number of peers to tune based on (best peers)
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
@@ -84,16 +78,13 @@ var (
|
||||
errStallingPeer = errors.New("peer is stalling")
|
||||
errNoPeers = errors.New("no peers to keep download active")
|
||||
errTimeout = errors.New("timeout")
|
||||
errEmptyHashSet = errors.New("empty hash set by peer")
|
||||
errEmptyHeaderSet = errors.New("empty header set by peer")
|
||||
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")
|
||||
errInvalidChain = errors.New("retrieved hash chain is invalid")
|
||||
errInvalidBlock = errors.New("retrieved block is invalid")
|
||||
errInvalidBody = errors.New("retrieved block body is invalid")
|
||||
errInvalidReceipt = errors.New("retrieved receipt is invalid")
|
||||
errCancelHashFetch = errors.New("hash download canceled (requested)")
|
||||
errCancelBlockFetch = errors.New("block download canceled (requested)")
|
||||
errCancelHeaderFetch = errors.New("block header download canceled (requested)")
|
||||
errCancelBodyFetch = errors.New("block body download canceled (requested)")
|
||||
@@ -102,6 +93,7 @@ var (
|
||||
errCancelHeaderProcessing = errors.New("header processing canceled (requested)")
|
||||
errCancelContentProcessing = errors.New("content processing canceled (requested)")
|
||||
errNoSyncActive = errors.New("no sync active")
|
||||
errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 62)")
|
||||
)
|
||||
|
||||
type Downloader struct {
|
||||
@@ -146,20 +138,19 @@ type Downloader struct {
|
||||
|
||||
// Channels
|
||||
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
|
||||
bodyCh chan dataPack // [eth/62] Channel receiving inbound block bodies
|
||||
receiptCh chan dataPack // [eth/63] Channel receiving inbound receipts
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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,
|
||||
dropPeer: dropPeer,
|
||||
newPeerCh: make(chan *peer, 1),
|
||||
hashCh: make(chan dataPack, 1),
|
||||
blockCh: make(chan dataPack, 1),
|
||||
headerCh: make(chan dataPack, 1),
|
||||
bodyCh: make(chan dataPack, 1),
|
||||
receiptCh: make(chan dataPack, 1),
|
||||
stateCh: make(chan dataPack, 1),
|
||||
blockWakeCh: make(chan bool, 1),
|
||||
bodyWakeCh: make(chan bool, 1),
|
||||
receiptWakeCh: 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
|
||||
// used for fetching hashes and blocks from.
|
||||
func (d *Downloader) RegisterPeer(id string, version int, head common.Hash,
|
||||
getRelHashes relativeHashFetcherFn, getAbsHashes absoluteHashFetcherFn, getBlocks blockFetcherFn, // eth/61 callbacks, remove when upgrading
|
||||
func (d *Downloader) RegisterPeer(id string, version int, currentHead currentHeadRetrievalFn,
|
||||
getRelHeaders relativeHeaderFetcherFn, getAbsHeaders absoluteHeaderFetcherFn, getBlockBodies blockBodyFetcherFn,
|
||||
getReceipts receiptFetcherFn, getNodeData stateFetcherFn) error {
|
||||
|
||||
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)
|
||||
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 queue.
|
||||
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)
|
||||
if err := d.peers.Unregister(id); err != nil {
|
||||
glog.V(logger.Error).Infoln("Unregister failed:", err)
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -291,7 +288,9 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode
|
||||
case errBusy:
|
||||
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)
|
||||
d.dropPeer(id)
|
||||
|
||||
@@ -323,13 +322,13 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
|
||||
d.queue.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 {
|
||||
case <-ch:
|
||||
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; {
|
||||
select {
|
||||
case <-ch:
|
||||
@@ -345,9 +344,10 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
|
||||
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.cancelCh = make(chan struct{})
|
||||
d.cancelPeer = id
|
||||
d.cancelLock.Unlock()
|
||||
|
||||
defer d.cancel() // No matter what, we can't leave the cancel channel open
|
||||
@@ -377,105 +377,73 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
|
||||
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)
|
||||
defer func(start time.Time) {
|
||||
glog.V(logger.Debug).Infof("Synchronisation terminated after %v", time.Since(start))
|
||||
}(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
|
||||
latest, err := d.fetchHeight(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
height := latest.Number.Uint64()
|
||||
|
||||
origin, err := d.findAncestor(p, height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.syncStatsLock.Lock()
|
||||
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin {
|
||||
d.syncStatsChainOrigin = origin
|
||||
}
|
||||
d.syncStatsChainHeight = height
|
||||
d.syncStatsLock.Unlock()
|
||||
|
||||
// Initiate the sync using a concurrent header and content retrieval algorithm
|
||||
pivot := uint64(0)
|
||||
switch d.mode {
|
||||
case LightSync:
|
||||
pivot = height
|
||||
case FastSync:
|
||||
// Calculate the new fast/slow sync pivot point
|
||||
if d.fsPivotLock == nil {
|
||||
pivotOffset, err := rand.Int(rand.Reader, big.NewInt(int64(fsPivotInterval)))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to access crypto random source: %v", err))
|
||||
}
|
||||
if height > uint64(fsMinFullBlocks)+pivotOffset.Uint64() {
|
||||
pivot = height - uint64(fsMinFullBlocks) - pivotOffset.Uint64()
|
||||
}
|
||||
} else {
|
||||
// Pivot point locked in, use this and do not pick a new one!
|
||||
pivot = d.fsPivotLock.Number.Uint64()
|
||||
}
|
||||
// If the point is below the origin, move origin back to ensure state download
|
||||
if pivot < origin {
|
||||
if pivot > 0 {
|
||||
origin = pivot - 1
|
||||
} else {
|
||||
origin = 0
|
||||
}
|
||||
}
|
||||
glog.V(logger.Debug).Infof("Fast syncing until pivot block #%d", pivot)
|
||||
}
|
||||
d.queue.Prepare(origin+1, d.mode, pivot, latest)
|
||||
if d.syncInitHook != nil {
|
||||
d.syncInitHook(origin, height)
|
||||
}
|
||||
return d.spawnSync(origin+1,
|
||||
func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved
|
||||
func() error { return d.processHeaders(origin+1, td) }, // Headers are always retrieved
|
||||
func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and 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
|
||||
)
|
||||
|
||||
default:
|
||||
// Something very wrong, stop right here
|
||||
glog.V(logger.Error).Infof("Unsupported eth protocol: %d", p.version)
|
||||
return errBadPeer
|
||||
// Look up the sync boundaries: the common ancestor and the target block
|
||||
latest, err := d.fetchHeight(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
height := latest.Number.Uint64()
|
||||
|
||||
origin, err := d.findAncestor(p, height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.syncStatsLock.Lock()
|
||||
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin {
|
||||
d.syncStatsChainOrigin = origin
|
||||
}
|
||||
d.syncStatsChainHeight = height
|
||||
d.syncStatsLock.Unlock()
|
||||
|
||||
// Initiate the sync using a concurrent header and content retrieval algorithm
|
||||
pivot := uint64(0)
|
||||
switch d.mode {
|
||||
case LightSync:
|
||||
pivot = height
|
||||
case FastSync:
|
||||
// Calculate the new fast/slow sync pivot point
|
||||
if d.fsPivotLock == nil {
|
||||
pivotOffset, err := rand.Int(rand.Reader, big.NewInt(int64(fsPivotInterval)))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to access crypto random source: %v", err))
|
||||
}
|
||||
if height > uint64(fsMinFullBlocks)+pivotOffset.Uint64() {
|
||||
pivot = height - uint64(fsMinFullBlocks) - pivotOffset.Uint64()
|
||||
}
|
||||
} else {
|
||||
// Pivot point locked in, use this and do not pick a new one!
|
||||
pivot = d.fsPivotLock.Number.Uint64()
|
||||
}
|
||||
// If the point is below the origin, move origin back to ensure state download
|
||||
if pivot < origin {
|
||||
if pivot > 0 {
|
||||
origin = pivot - 1
|
||||
} else {
|
||||
origin = 0
|
||||
}
|
||||
}
|
||||
glog.V(logger.Debug).Infof("Fast syncing until pivot block #%d", pivot)
|
||||
}
|
||||
d.queue.Prepare(origin+1, d.mode, pivot, latest)
|
||||
if d.syncInitHook != nil {
|
||||
d.syncInitHook(origin, height)
|
||||
}
|
||||
return d.spawnSync(origin+1,
|
||||
func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved
|
||||
func() error { return d.processHeaders(origin+1, td) }, // Headers are always retrieved
|
||||
func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and 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
|
||||
)
|
||||
}
|
||||
|
||||
// spawnSync runs d.process and all given fetcher functions to completion in
|
||||
@@ -540,459 +508,14 @@ func (d *Downloader) Terminate() {
|
||||
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
|
||||
// the total time a pending synchronisation would take.
|
||||
func (d *Downloader) fetchHeight(p *peer) (*types.Header, 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.getRelHeaders(p.head, 1, 0, false)
|
||||
head, _ := p.currentHead()
|
||||
go p.getRelHeaders(head, 1, 0, false)
|
||||
|
||||
timeout := time.After(d.requestTTL())
|
||||
for {
|
||||
@@ -1022,11 +545,6 @@ func (d *Downloader) fetchHeight(p *peer) (*types.Header, error) {
|
||||
case <-d.stateCh:
|
||||
case <-d.receiptCh:
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1037,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
|
||||
// the head links match), we do a binary search to find the common ancestor.
|
||||
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
|
||||
floor, ceil := int64(-1), d.headHeader().Number.Uint64()
|
||||
@@ -1054,11 +572,17 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
if head > height {
|
||||
head = height
|
||||
}
|
||||
from := int64(head) - int64(MaxHeaderFetch) + 1
|
||||
from := int64(head) - int64(MaxHeaderFetch)
|
||||
if 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
|
||||
number, hash := uint64(0), common.Hash{}
|
||||
@@ -1067,7 +591,7 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
for finished := false; !finished; {
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
return 0, errCancelHashFetch
|
||||
return 0, errCancelHeaderFetch
|
||||
|
||||
case packet := <-d.headerCh:
|
||||
// Discard anything not from the origin peer
|
||||
@@ -1083,12 +607,8 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
}
|
||||
// Make sure the peer's reply conforms to the request
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if number := headers[i].Number.Int64(); number != from+int64(i) {
|
||||
glog.V(logger.Warn).Infof("%v: head header set (item %d) broke chain ordering: requested %d, got %d", p, i, from+int64(i), 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])
|
||||
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)*16, number)
|
||||
return 0, errInvalidChain
|
||||
}
|
||||
}
|
||||
@@ -1096,12 +616,18 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
finished = true
|
||||
for i := len(headers) - 1; i >= 0; i-- {
|
||||
// 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
|
||||
}
|
||||
// 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())) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1114,11 +640,6 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
case <-d.stateCh:
|
||||
case <-d.receiptCh:
|
||||
// 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
|
||||
@@ -1146,7 +667,7 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
for arrived := false; !arrived; {
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
return 0, errCancelHashFetch
|
||||
return 0, errCancelHeaderFetch
|
||||
|
||||
case packer := <-d.headerCh:
|
||||
// Discard anything not from the origin peer
|
||||
@@ -1182,11 +703,6 @@ func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
|
||||
case <-d.stateCh:
|
||||
case <-d.receiptCh:
|
||||
// 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 +821,6 @@ func (d *Downloader) fetchHeaders(p *peer, from uint64) error {
|
||||
case <-d.cancelCh:
|
||||
}
|
||||
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 +1066,14 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv
|
||||
// Check for fetch request timeouts and demote the responsible peers
|
||||
for pid, fails := range expire() {
|
||||
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))
|
||||
setIdle(peer, 0)
|
||||
} else {
|
||||
@@ -1623,11 +1141,6 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv
|
||||
if !progressed && !throttled && !running && len(idles) == total && pending() > 0 {
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1867,19 +1380,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
|
||||
// node into the download schedule.
|
||||
func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) {
|
||||
|
@@ -399,14 +399,12 @@ func (dl *downloadTester) newSlowPeer(id string, version int, hashes []common.Ha
|
||||
|
||||
var err error
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// peerGetRelHashesFn constructs a GetHashes function associated with a specific
|
||||
// peer in the download tester. The returned function can be used to retrieve
|
||||
// batches of hashes from the particularly requested peer.
|
||||
func (dl *downloadTester) peerGetRelHashesFn(id string, delay time.Duration) func(head common.Hash) error {
|
||||
return func(head common.Hash) error {
|
||||
time.Sleep(delay)
|
||||
|
||||
// peerCurrentHeadFn constructs a function to retrieve a peer's current head hash
|
||||
// and total difficulty.
|
||||
func (dl *downloadTester) peerCurrentHeadFn(id string) func() (common.Hash, *big.Int) {
|
||||
return func() (common.Hash, *big.Int) {
|
||||
dl.lock.RLock()
|
||||
defer dl.lock.RUnlock()
|
||||
|
||||
// Gather the next batch of hashes
|
||||
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
|
||||
return dl.peerHashes[id][0], 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.
|
||||
// In this test common ancestor lookup should be short circuited and not require
|
||||
// binary searching.
|
||||
func TestCanonicalSynchronisation61(t *testing.T) { testCanonicalSynchronisation(t, 61, FullSync) }
|
||||
func TestCanonicalSynchronisation62(t *testing.T) { testCanonicalSynchronisation(t, 62, FullSync) }
|
||||
func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) }
|
||||
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
|
||||
// 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 TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) }
|
||||
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
|
||||
// this test common ancestor lookup should *not* be short circuited, and a full
|
||||
// binary search should be executed.
|
||||
func TestForkedSync61(t *testing.T) { testForkedSync(t, 61, FullSync) }
|
||||
func TestForkedSync62(t *testing.T) { testForkedSync(t, 62, FullSync) }
|
||||
func TestForkedSync63Full(t *testing.T) { testForkedSync(t, 63, FullSync) }
|
||||
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
|
||||
// corrently and is not dropped.
|
||||
func TestHeavyForkedSync61(t *testing.T) { testHeavyForkedSync(t, 61, FullSync) }
|
||||
func TestHeavyForkedSync62(t *testing.T) { testHeavyForkedSync(t, 62, FullSync) }
|
||||
func TestHeavyForkedSync63Full(t *testing.T) { testHeavyForkedSync(t, 63, FullSync) }
|
||||
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})
|
||||
}
|
||||
|
||||
// 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
|
||||
// chain head, ensuring that malicious peers cannot waste resources by feeding
|
||||
// long dead chains.
|
||||
func TestBoundedForkedSync61(t *testing.T) { testBoundedForkedSync(t, 61, FullSync) }
|
||||
func TestBoundedForkedSync62(t *testing.T) { testBoundedForkedSync(t, 62, FullSync) }
|
||||
func TestBoundedForkedSync63Full(t *testing.T) { testBoundedForkedSync(t, 63, FullSync) }
|
||||
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
|
||||
// chain head for short but heavy forks too. These are a bit special because they
|
||||
// take different ancestor lookup paths.
|
||||
func TestBoundedHeavyForkedSync61(t *testing.T) { testBoundedHeavyForkedSync(t, 61, FullSync) }
|
||||
func TestBoundedHeavyForkedSync62(t *testing.T) { testBoundedHeavyForkedSync(t, 62, FullSync) }
|
||||
func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) }
|
||||
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.
|
||||
func TestCancel61(t *testing.T) { testCancel(t, 61, FullSync) }
|
||||
func TestCancel62(t *testing.T) { testCancel(t, 62, FullSync) }
|
||||
func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) }
|
||||
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).
|
||||
func TestMultiSynchronisation61(t *testing.T) { testMultiSynchronisation(t, 61, FullSync) }
|
||||
func TestMultiSynchronisation62(t *testing.T) { testMultiSynchronisation(t, 62, FullSync) }
|
||||
func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) }
|
||||
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
|
||||
// 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 TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) }
|
||||
func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) }
|
||||
@@ -1131,7 +1037,6 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
tester := newTester()
|
||||
defer tester.terminate()
|
||||
|
||||
tester.newPeer("peer 61", 61, hashes, nil, blocks, nil)
|
||||
tester.newPeer("peer 62", 62, hashes, headers, blocks, nil)
|
||||
tester.newPeer("peer 63", 63, 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)
|
||||
|
||||
// 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)
|
||||
if _, ok := tester.peerHashes[peer]; !ok {
|
||||
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
|
||||
// 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 TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) }
|
||||
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.
|
||||
func TestBlockHeaderAttackerDropping61(t *testing.T) { testBlockHeaderAttackerDropping(t, 61) }
|
||||
func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDropping(t, 62) }
|
||||
func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) }
|
||||
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
|
||||
{errNoPeers, false}, // No peers to download from, soft race, no issue
|
||||
{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
|
||||
{errPeersUnavailable, true}, // Nobody had the advertised blocks, drop the advertiser
|
||||
{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
|
||||
{errInvalidBody, 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
|
||||
{errCancelHeaderFetch, 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
|
||||
// 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 TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) }
|
||||
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
|
||||
// number) is tracked and updated correctly in case of a fork (or manual head
|
||||
// revertal).
|
||||
func TestForkedSyncProgress61(t *testing.T) { testForkedSyncProgress(t, 61, FullSync) }
|
||||
func TestForkedSyncProgress62(t *testing.T) { testForkedSyncProgress(t, 62, FullSync) }
|
||||
func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) }
|
||||
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
|
||||
// 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.
|
||||
func TestFailedSyncProgress61(t *testing.T) { testFailedSyncProgress(t, 61, FullSync) }
|
||||
func TestFailedSyncProgress62(t *testing.T) { testFailedSyncProgress(t, 62, FullSync) }
|
||||
func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) }
|
||||
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,
|
||||
// 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 TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) }
|
||||
func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) }
|
||||
|
@@ -23,16 +23,6 @@ import (
|
||||
)
|
||||
|
||||
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")
|
||||
headerReqTimer = metrics.NewTimer("eth/downloader/headers/req")
|
||||
headerDropMeter = metrics.NewMeter("eth/downloader/headers/drop")
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -37,10 +38,8 @@ const (
|
||||
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
|
||||
type relativeHashFetcherFn func(common.Hash) error
|
||||
type absoluteHashFetcherFn func(uint64, int) error
|
||||
type blockFetcherFn func([]common.Hash) error
|
||||
// Head hash and total difficulty retriever for
|
||||
type currentHeadRetrievalFn func() (common.Hash, *big.Int)
|
||||
|
||||
// Block header and body fetchers belonging to eth/62 and above
|
||||
type relativeHeaderFetcherFn func(common.Hash, int, int, bool) error
|
||||
@@ -57,8 +56,7 @@ var (
|
||||
|
||||
// peer represents an active peer from which hashes and blocks are retrieved.
|
||||
type peer struct {
|
||||
id string // Unique identifier of the peer
|
||||
head common.Hash // Hash of the peers latest known block
|
||||
id string // Unique identifier of the peer
|
||||
|
||||
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)
|
||||
@@ -79,9 +77,7 @@ type peer struct {
|
||||
|
||||
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
|
||||
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
|
||||
currentHead currentHeadRetrievalFn // Method to fetch the currently known head of the peer
|
||||
|
||||
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
|
||||
@@ -96,19 +92,14 @@ type peer struct {
|
||||
|
||||
// newPeer create a new downloader peer, with specific hash and block retrieval
|
||||
// mechanisms.
|
||||
func newPeer(id string, version int, head common.Hash,
|
||||
getRelHashes relativeHashFetcherFn, getAbsHashes absoluteHashFetcherFn, getBlocks blockFetcherFn, // eth/61 callbacks, remove when upgrading
|
||||
func newPeer(id string, version int, currentHead currentHeadRetrievalFn,
|
||||
getRelHeaders relativeHeaderFetcherFn, getAbsHeaders absoluteHeaderFetcherFn, getBlockBodies blockBodyFetcherFn,
|
||||
getReceipts receiptFetcherFn, getNodeData stateFetcherFn) *peer {
|
||||
return &peer{
|
||||
id: id,
|
||||
head: head,
|
||||
lacking: make(map[common.Hash]struct{}),
|
||||
|
||||
getRelHashes: getRelHashes,
|
||||
getAbsHashes: getAbsHashes,
|
||||
getBlocks: getBlocks,
|
||||
|
||||
currentHead: currentHead,
|
||||
getRelHeaders: getRelHeaders,
|
||||
getAbsHeaders: getAbsHeaders,
|
||||
getBlockBodies: getBlockBodies,
|
||||
@@ -138,28 +129,6 @@ func (p *peer) Reset() {
|
||||
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.
|
||||
func (p *peer) FetchHeaders(from uint64, count int) error {
|
||||
// Sanity check the protocol version
|
||||
@@ -481,20 +450,6 @@ func (ps *peerSet) AllPeers() []*peer {
|
||||
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
|
||||
// within the active peer set, ordered by their reputation.
|
||||
func (ps *peerSet) HeaderIdlePeers() ([]*peer, int) {
|
||||
|
@@ -45,7 +45,6 @@ var (
|
||||
|
||||
var (
|
||||
errNoFetchesPending = errors.New("no fetches pending")
|
||||
errStateSyncPending = errors.New("state trie sync already scheduled")
|
||||
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
|
||||
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
|
||||
|
||||
// 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
|
||||
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
|
||||
headerDonePool map[uint64]struct{} // [eth/62] Set of the completed header fetches
|
||||
headerResults []*types.Header // [eth/62] Result cache accumulating the completed headers
|
||||
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
|
||||
@@ -124,8 +118,6 @@ type queue struct {
|
||||
func newQueue(stateDb ethdb.Database) *queue {
|
||||
lock := new(sync.Mutex)
|
||||
return &queue{
|
||||
hashPool: make(map[common.Hash]int),
|
||||
hashQueue: prque.New(),
|
||||
headerPendPool: make(map[string]*fetchRequest),
|
||||
headerContCh: make(chan bool),
|
||||
blockTaskPool: make(map[common.Hash]*types.Header),
|
||||
@@ -158,10 +150,6 @@ func (q *queue) Reset() {
|
||||
q.mode = FullSync
|
||||
q.fastSyncPivot = 0
|
||||
|
||||
q.hashPool = make(map[common.Hash]int)
|
||||
q.hashQueue.Reset()
|
||||
q.hashCounter = 0
|
||||
|
||||
q.headerHead = common.Hash{}
|
||||
|
||||
q.headerPendPool = make(map[string]*fetchRequest)
|
||||
@@ -208,7 +196,7 @@ func (q *queue) PendingBlocks() int {
|
||||
q.lock.Lock()
|
||||
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.
|
||||
@@ -272,7 +260,7 @@ func (q *queue) Idle() bool {
|
||||
q.lock.Lock()
|
||||
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)
|
||||
cached := len(q.blockDonePool) + len(q.receiptDonePool)
|
||||
|
||||
@@ -323,34 +311,6 @@ func (q *queue) ShouldThrottleReceipts() bool {
|
||||
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
|
||||
// up an already retrieved header skeleton.
|
||||
func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) {
|
||||
@@ -550,15 +510,6 @@ func (q *queue) ReserveHeaders(p *peer, count int) *fetchRequest {
|
||||
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
|
||||
// any previously failed download.
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// task queue.
|
||||
func (q *queue) CancelBodies(request *fetchRequest) {
|
||||
@@ -801,9 +747,6 @@ func (q *queue) Revoke(peerId string) {
|
||||
defer q.lock.Unlock()
|
||||
|
||||
if request, ok := q.blockPendPool[peerId]; ok {
|
||||
for hash, index := range request.Hashes {
|
||||
q.hashQueue.Push(hash, float32(index))
|
||||
}
|
||||
for _, header := range request.Headers {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// allowance, canceling them and returning the responsible peers for penalisation.
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// cache. This method either accepts all headers it received, or none of them
|
||||
// if they do not map correctly to the skeleton.
|
||||
|
@@ -73,26 +73,6 @@ type dataPack interface {
|
||||
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.
|
||||
type headerPack struct {
|
||||
peerId string
|
||||
|
@@ -48,9 +48,6 @@ var (
|
||||
// blockRetrievalFn is a callback type for retrieving a block from the local chain.
|
||||
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.
|
||||
type headerRequesterFn func(common.Hash) error
|
||||
|
||||
@@ -82,7 +79,6 @@ type announce struct {
|
||||
|
||||
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
|
||||
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
|
||||
// the network.
|
||||
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 {
|
||||
block := &announce{
|
||||
hash: hash,
|
||||
number: number,
|
||||
time: time,
|
||||
origin: peer,
|
||||
fetch61: blockFetcher,
|
||||
fetchHeader: headerFetcher,
|
||||
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,
|
||||
// returning those that should be handled differently.
|
||||
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 {
|
||||
if glog.V(logger.Detail) && len(hashes) > 0 {
|
||||
list := "["
|
||||
@@ -421,29 +387,17 @@ func (f *Fetcher) loop() {
|
||||
list += fmt.Sprintf("%x…, ", hash[:4])
|
||||
}
|
||||
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
|
||||
fetchBlocks, fetchHeader, hashes := f.fetching[hashes[0]].fetch61, f.fetching[hashes[0]].fetchHeader, hashes
|
||||
fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes
|
||||
go func() {
|
||||
if f.fetchingHook != nil {
|
||||
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 {
|
||||
headerFetchMeter.Mark(1)
|
||||
fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
headerFetchMeter.Mark(1)
|
||||
fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -486,46 +440,6 @@ func (f *Fetcher) loop() {
|
||||
// Schedule the next fetch if blocks are still pending
|
||||
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:
|
||||
// Headers arrived from a remote peer. Extract those that were explicitly
|
||||
// requested by the fetcher, and return everything else so it's delivered
|
||||
|
@@ -151,28 +151,6 @@ func (f *fetcherTester) dropPeer(peer string) {
|
||||
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.
|
||||
func (f *fetcherTester) makeHeaderFetcher(blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn {
|
||||
closure := make(map[common.Hash]*types.Block)
|
||||
@@ -293,7 +271,6 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
|
||||
|
||||
// Tests that a fetcher accepts block announcements and initiates retrievals for
|
||||
// them, successfully importing into the local chain.
|
||||
func TestSequentialAnnouncements61(t *testing.T) { testSequentialAnnouncements(t, 61) }
|
||||
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
|
||||
func TestSequentialAnnouncements63(t *testing.T) { testSequentialAnnouncements(t, 63) }
|
||||
func TestSequentialAnnouncements64(t *testing.T) { testSequentialAnnouncements(t, 64) }
|
||||
@@ -304,7 +281,6 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
|
||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
@@ -313,11 +289,7 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
|
||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||
|
||||
for i := len(hashes) - 2; i >= 0; i-- {
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
verifyImportEvent(t, imported, true)
|
||||
}
|
||||
verifyImportDone(t, imported)
|
||||
@@ -325,7 +297,6 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
|
||||
|
||||
// Tests that if blocks are announced by multiple peers (or even the same buggy
|
||||
// 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 TestConcurrentAnnouncements63(t *testing.T) { testConcurrentAnnouncements(t, 63) }
|
||||
func TestConcurrentAnnouncements64(t *testing.T) { testConcurrentAnnouncements(t, 64) }
|
||||
@@ -337,15 +308,10 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
|
||||
|
||||
// Assemble a tester with a built in counter for the requests
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 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 {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
return headerFetcher(hash)
|
||||
@@ -355,15 +321,9 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
|
||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||
|
||||
for i := len(hashes) - 2; i >= 0; i-- {
|
||||
if protocol < 62 {
|
||||
tester.fetcher.Notify("first", hashes[i], 0, time.Now().Add(-arriveTimeout), blockWrapper, nil, nil)
|
||||
tester.fetcher.Notify("second", hashes[i], 0, time.Now().Add(-arriveTimeout+time.Millisecond), blockWrapper, nil, nil)
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
|
||||
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], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), headerWrapper, bodyFetcher)
|
||||
verifyImportEvent(t, imported, true)
|
||||
}
|
||||
verifyImportDone(t, imported)
|
||||
@@ -376,7 +336,6 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
|
||||
|
||||
// Tests that announcements arriving while a previous is being fetched still
|
||||
// results in a valid import.
|
||||
func TestOverlappingAnnouncements61(t *testing.T) { testOverlappingAnnouncements(t, 61) }
|
||||
func TestOverlappingAnnouncements62(t *testing.T) { testOverlappingAnnouncements(t, 62) }
|
||||
func TestOverlappingAnnouncements63(t *testing.T) { testOverlappingAnnouncements(t, 63) }
|
||||
func TestOverlappingAnnouncements64(t *testing.T) { testOverlappingAnnouncements(t, 64) }
|
||||
@@ -387,7 +346,6 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
|
||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
@@ -400,11 +358,7 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
|
||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||
|
||||
for i := len(hashes) - 2; i >= 0; i-- {
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
select {
|
||||
case <-imported:
|
||||
case <-time.After(time.Second):
|
||||
@@ -416,7 +370,6 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
|
||||
}
|
||||
|
||||
// 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 TestPendingDeduplication63(t *testing.T) { testPendingDeduplication(t, 63) }
|
||||
func TestPendingDeduplication64(t *testing.T) { testPendingDeduplication(t, 64) }
|
||||
@@ -427,22 +380,11 @@ func testPendingDeduplication(t *testing.T, protocol int) {
|
||||
|
||||
// Assemble a tester with a built in counter and delayed fetcher
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
delay := 50 * time.Millisecond
|
||||
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 {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
|
||||
@@ -455,11 +397,7 @@ func testPendingDeduplication(t *testing.T, protocol int) {
|
||||
}
|
||||
// Announce the same block many times until it's fetched (wait for any pending ops)
|
||||
for tester.getBlock(hashes[0]) == nil {
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
time.Sleep(delay)
|
||||
@@ -475,7 +413,6 @@ func testPendingDeduplication(t *testing.T, protocol int) {
|
||||
|
||||
// Tests that announcements retrieved in a random order are cached and eventually
|
||||
// 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 TestRandomArrivalImport63(t *testing.T) { testRandomArrivalImport(t, 63) }
|
||||
func TestRandomArrivalImport64(t *testing.T) { testRandomArrivalImport(t, 64) }
|
||||
@@ -487,7 +424,6 @@ func testRandomArrivalImport(t *testing.T, protocol int) {
|
||||
skip := targetBlocks / 2
|
||||
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
@@ -497,26 +433,17 @@ func testRandomArrivalImport(t *testing.T, protocol int) {
|
||||
|
||||
for i := len(hashes) - 1; i >= 0; i-- {
|
||||
if i != skip {
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
// Finally announce the skipped entry and check full import
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
verifyImportCount(t, imported, len(hashes)-1)
|
||||
}
|
||||
|
||||
// Tests that direct block enqueues (due to block propagation vs. hash announce)
|
||||
// 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 TestQueueGapFill63(t *testing.T) { testQueueGapFill(t, 63) }
|
||||
func TestQueueGapFill64(t *testing.T) { testQueueGapFill(t, 64) }
|
||||
@@ -528,7 +455,6 @@ func testQueueGapFill(t *testing.T, protocol int) {
|
||||
skip := targetBlocks / 2
|
||||
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
@@ -538,11 +464,7 @@ func testQueueGapFill(t *testing.T, protocol int) {
|
||||
|
||||
for i := len(hashes) - 1; i >= 0; i-- {
|
||||
if i != skip {
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -553,7 +475,6 @@ func testQueueGapFill(t *testing.T, protocol int) {
|
||||
|
||||
// Tests that blocks arriving from various sources (multiple propagations, hash
|
||||
// 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 TestImportDeduplication63(t *testing.T) { testImportDeduplication(t, 63) }
|
||||
func TestImportDeduplication64(t *testing.T) { testImportDeduplication(t, 64) }
|
||||
@@ -564,7 +485,6 @@ func testImportDeduplication(t *testing.T, protocol int) {
|
||||
|
||||
// Create the tester and wrap the importer with a counter
|
||||
tester := newTester()
|
||||
blockFetcher := tester.makeBlockFetcher(blocks)
|
||||
headerFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
@@ -580,11 +500,7 @@ func testImportDeduplication(t *testing.T, protocol int) {
|
||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||
|
||||
// Announce the duplicating block, wait for retrieval, and also propagate directly
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
<-fetching
|
||||
|
||||
tester.fetcher.Enqueue("valid", blocks[hashes[0]])
|
||||
@@ -660,14 +576,14 @@ func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
|
||||
tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} }
|
||||
|
||||
// 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 {
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
case <-fetching:
|
||||
t.Fatalf("fetcher requested stale header")
|
||||
}
|
||||
// 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 {
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
case <-fetching:
|
||||
@@ -693,7 +609,7 @@ func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
|
||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||
|
||||
// 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)
|
||||
|
||||
tester.lock.RLock()
|
||||
@@ -704,7 +620,7 @@ func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
|
||||
t.Fatalf("peer with invalid numbered announcement not dropped")
|
||||
}
|
||||
// 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)
|
||||
|
||||
tester.lock.RLock()
|
||||
@@ -743,7 +659,7 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
|
||||
|
||||
// Iteratively announce blocks until all are imported
|
||||
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
|
||||
verifyFetchingEvent(t, fetching, true)
|
||||
@@ -760,7 +676,6 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
|
||||
// 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,
|
||||
// the fetcher remains operational.
|
||||
func TestHashMemoryExhaustionAttack61(t *testing.T) { testHashMemoryExhaustionAttack(t, 61) }
|
||||
func TestHashMemoryExhaustionAttack62(t *testing.T) { testHashMemoryExhaustionAttack(t, 62) }
|
||||
func TestHashMemoryExhaustionAttack63(t *testing.T) { testHashMemoryExhaustionAttack(t, 63) }
|
||||
func TestHashMemoryExhaustionAttack64(t *testing.T) { testHashMemoryExhaustionAttack(t, 64) }
|
||||
@@ -781,29 +696,19 @@ func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
|
||||
// Create a valid chain and an infinite junk chain
|
||||
targetBlocks := hashLimit + 2*maxQueueDist
|
||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||
validBlockFetcher := tester.makeBlockFetcher(blocks)
|
||||
validHeaderFetcher := tester.makeHeaderFetcher(blocks, -gatherSlack)
|
||||
validBodyFetcher := tester.makeBodyFetcher(blocks, 0)
|
||||
|
||||
attack, _ := makeChain(targetBlocks, 0, unknownBlock)
|
||||
attackerBlockFetcher := tester.makeBlockFetcher(nil)
|
||||
attackerHeaderFetcher := tester.makeHeaderFetcher(nil, -gatherSlack)
|
||||
attackerBodyFetcher := tester.makeBodyFetcher(nil, 0)
|
||||
|
||||
// Feed the tester a huge hashset from the attacker, and a limited from the valid peer
|
||||
for i := 0; i < len(attack); i++ {
|
||||
if i < maxQueueDist {
|
||||
if protocol < 62 {
|
||||
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("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher)
|
||||
}
|
||||
tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher)
|
||||
}
|
||||
if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist {
|
||||
t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist)
|
||||
@@ -813,11 +718,7 @@ func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
|
||||
|
||||
// Feed the remaining valid hashes to ensure DOS protection state remains clean
|
||||
for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- {
|
||||
if protocol < 62 {
|
||||
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)
|
||||
}
|
||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher)
|
||||
verifyImportEvent(t, imported, true)
|
||||
}
|
||||
verifyImportDone(t, imported)
|
||||
|
@@ -33,12 +33,9 @@ var (
|
||||
propBroadcastDropMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/drop")
|
||||
propBroadcastDOSMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/dos")
|
||||
|
||||
blockFetchMeter = metrics.NewMeter("eth/fetcher/fetch/blocks")
|
||||
headerFetchMeter = metrics.NewMeter("eth/fetcher/fetch/headers")
|
||||
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")
|
||||
headerFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/headers/out")
|
||||
bodyFilterInMeter = metrics.NewMeter("eth/fetcher/filter/bodies/in")
|
||||
|
@@ -68,8 +68,6 @@ type PublicFilterAPI struct {
|
||||
|
||||
transactionMu sync.RWMutex
|
||||
transactionQueue map[int]*hashQueue
|
||||
|
||||
transactMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewPublicFilterAPI returns a new PublicFilterAPI instance.
|
||||
@@ -100,6 +98,7 @@ done:
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
s.filterManager.Lock() // lock order like filterLoop()
|
||||
s.logMu.Lock()
|
||||
for id, filter := range s.logQueue {
|
||||
if time.Since(filter.timeout) > filterTickerTime {
|
||||
@@ -126,6 +125,7 @@ done:
|
||||
}
|
||||
}
|
||||
s.transactionMu.Unlock()
|
||||
s.filterManager.Unlock()
|
||||
case <-s.quit:
|
||||
break done
|
||||
}
|
||||
@@ -135,19 +135,24 @@ done:
|
||||
|
||||
// NewBlockFilter create a new filter that returns blocks that are included into the canonical chain.
|
||||
func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
|
||||
// protect filterManager.Add() and setting of filter fields
|
||||
s.filterManager.Lock()
|
||||
defer s.filterManager.Unlock()
|
||||
|
||||
externalId, err := newFilterId()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.blockMu.Lock()
|
||||
filter := New(s.chainDb)
|
||||
id, err := s.filterManager.Add(filter, ChainFilter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.blockMu.Lock()
|
||||
s.blockQueue[id] = &hashQueue{timeout: time.Now()}
|
||||
s.blockMu.Unlock()
|
||||
|
||||
filter.BlockCallback = func(block *types.Block, logs vm.Logs) {
|
||||
s.blockMu.Lock()
|
||||
@@ -158,8 +163,6 @@ func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
defer s.blockMu.Unlock()
|
||||
|
||||
s.filterMapMu.Lock()
|
||||
s.filterMapping[externalId] = id
|
||||
s.filterMapMu.Unlock()
|
||||
@@ -169,21 +172,24 @@ func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
|
||||
|
||||
// NewPendingTransactionFilter creates a filter that returns new pending transactions.
|
||||
func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) {
|
||||
// protect filterManager.Add() and setting of filter fields
|
||||
s.filterManager.Lock()
|
||||
defer s.filterManager.Unlock()
|
||||
|
||||
externalId, err := newFilterId()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.transactionMu.Lock()
|
||||
defer s.transactionMu.Unlock()
|
||||
|
||||
filter := New(s.chainDb)
|
||||
id, err := s.filterManager.Add(filter, PendingTxFilter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.transactionMu.Lock()
|
||||
s.transactionQueue[id] = &hashQueue{timeout: time.Now()}
|
||||
s.transactionMu.Unlock()
|
||||
|
||||
filter.TransactionCallback = func(tx *types.Transaction) {
|
||||
s.transactionMu.Lock()
|
||||
@@ -203,8 +209,9 @@ func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) {
|
||||
|
||||
// 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) {
|
||||
s.logMu.Lock()
|
||||
defer s.logMu.Unlock()
|
||||
// protect filterManager.Add() and setting of filter fields
|
||||
s.filterManager.Lock()
|
||||
defer s.filterManager.Unlock()
|
||||
|
||||
filter := New(s.chainDb)
|
||||
id, err := s.filterManager.Add(filter, LogFilter)
|
||||
@@ -212,7 +219,9 @@ func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []commo
|
||||
return 0, err
|
||||
}
|
||||
|
||||
s.logMu.Lock()
|
||||
s.logQueue[id] = &logQueue{timeout: time.Now()}
|
||||
s.logMu.Unlock()
|
||||
|
||||
filter.SetBeginBlock(earliest)
|
||||
filter.SetEndBlock(latest)
|
||||
@@ -443,35 +452,43 @@ func (s *PublicFilterAPI) GetLogs(args NewFilterArgs) []vmlog {
|
||||
|
||||
// UninstallFilter removes the filter with the given filter id.
|
||||
func (s *PublicFilterAPI) UninstallFilter(filterId string) bool {
|
||||
s.filterMapMu.Lock()
|
||||
defer s.filterMapMu.Unlock()
|
||||
s.filterManager.Lock()
|
||||
defer s.filterManager.Unlock()
|
||||
|
||||
s.filterMapMu.Lock()
|
||||
id, ok := s.filterMapping[filterId]
|
||||
if !ok {
|
||||
s.filterMapMu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
defer s.filterManager.Remove(id)
|
||||
delete(s.filterMapping, filterId)
|
||||
s.filterMapMu.Unlock()
|
||||
|
||||
s.filterManager.Remove(id)
|
||||
|
||||
s.logMu.Lock()
|
||||
if _, ok := s.logQueue[id]; ok {
|
||||
s.logMu.Lock()
|
||||
defer s.logMu.Unlock()
|
||||
delete(s.logQueue, id)
|
||||
s.logMu.Unlock()
|
||||
return true
|
||||
}
|
||||
s.logMu.Unlock()
|
||||
|
||||
s.blockMu.Lock()
|
||||
if _, ok := s.blockQueue[id]; ok {
|
||||
s.blockMu.Lock()
|
||||
defer s.blockMu.Unlock()
|
||||
delete(s.blockQueue, id)
|
||||
s.blockMu.Unlock()
|
||||
return true
|
||||
}
|
||||
s.blockMu.Unlock()
|
||||
|
||||
s.transactionMu.Lock()
|
||||
if _, ok := s.transactionQueue[id]; ok {
|
||||
s.transactionMu.Lock()
|
||||
defer s.transactionMu.Unlock()
|
||||
delete(s.transactionQueue, id)
|
||||
s.transactionMu.Unlock()
|
||||
return true
|
||||
}
|
||||
s.transactionMu.Unlock()
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -525,7 +542,9 @@ func (s *PublicFilterAPI) logFilterChanged(id int) []vmlog {
|
||||
|
||||
// GetFilterLogs returns the logs for the filter with the given id.
|
||||
func (s *PublicFilterAPI) GetFilterLogs(filterId string) []vmlog {
|
||||
s.filterMapMu.RLock()
|
||||
id, ok := s.filterMapping[filterId]
|
||||
s.filterMapMu.RUnlock()
|
||||
if !ok {
|
||||
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.
|
||||
// This can be used for polling.
|
||||
func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} {
|
||||
s.filterMapMu.Lock()
|
||||
s.filterMapMu.RLock()
|
||||
id, ok := s.filterMapping[filterId]
|
||||
s.filterMapMu.Unlock()
|
||||
s.filterMapMu.RUnlock()
|
||||
|
||||
if !ok { // filter not found
|
||||
return []interface{}{}
|
||||
|
@@ -72,7 +72,11 @@ func (self *Filter) SetTopics(topics [][]common.Hash) {
|
||||
|
||||
// Run filters logs with the current parameters set
|
||||
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)
|
||||
if self.begin == -1 {
|
||||
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) {
|
||||
var block *types.Block
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
var block *types.Block
|
||||
hash := core.GetCanonicalHash(self.db, i)
|
||||
if hash != (common.Hash{}) {
|
||||
block = core.GetBlock(self.db, hash)
|
||||
} else { // block not found
|
||||
}
|
||||
if block == nil { // block not found/written
|
||||
return logs
|
||||
}
|
||||
|
||||
|
@@ -82,11 +82,20 @@ func (fs *FilterSystem) Stop() {
|
||||
fs.sub.Unsubscribe()
|
||||
}
|
||||
|
||||
// Add adds a filter to the filter manager
|
||||
func (fs *FilterSystem) Add(filter *Filter, filterType FilterType) (int, error) {
|
||||
// Acquire filter system maps lock, required to force lock acquisition
|
||||
// sequence with filterMu acquired first to avoid deadlocks by callbacks
|
||||
func (fs *FilterSystem) 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
|
||||
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
|
||||
// Expects filterMu to be locked.
|
||||
func (fs *FilterSystem) Remove(id int) {
|
||||
fs.filterMu.Lock()
|
||||
defer fs.filterMu.Unlock()
|
||||
|
||||
delete(fs.chainFilters, id)
|
||||
delete(fs.pendingTxFilters, id)
|
||||
delete(fs.logFilters, id)
|
||||
|
160
eth/handler.go
160
eth/handler.go
@@ -57,9 +57,6 @@ func errResp(code errCode, format string, v ...interface{}) error {
|
||||
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
type hashFetcherFn func(common.Hash) error
|
||||
type blockFetcherFn func([]common.Hash) error
|
||||
|
||||
type ProtocolManager struct {
|
||||
networkId int
|
||||
|
||||
@@ -275,9 +272,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||
defer pm.removePeer(p.id)
|
||||
|
||||
// Register the peer in the downloader. If the downloader considers it banned, we disconnect
|
||||
if err := pm.downloader.RegisterPeer(p.id, p.version, p.Head(),
|
||||
p.RequestHashes, p.RequestHashesFromNumber, p.RequestBlocks, p.RequestHeadersByHash,
|
||||
p.RequestHeadersByNumber, p.RequestBodies, p.RequestReceipts, p.RequestNodeData); err != nil {
|
||||
if err := pm.downloader.RegisterPeer(p.id, p.version, p.Head, p.RequestHeadersByHash, p.RequestHeadersByNumber, p.RequestBodies, p.RequestReceipts, p.RequestNodeData); err != nil {
|
||||
return err
|
||||
}
|
||||
// Propagate existing transactions. new transactions appearing
|
||||
@@ -295,6 +290,13 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||
glog.V(logger.Warn).Infof("%v: timed out DAO fork-check, dropping", p)
|
||||
pm.removePeer(p.id)
|
||||
})
|
||||
// Make sure it's cleaned up if the peer dies off
|
||||
defer func() {
|
||||
if p.forkDrop != nil {
|
||||
p.forkDrop.Stop()
|
||||
p.forkDrop = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
// main loop. handle incoming messages.
|
||||
for {
|
||||
@@ -324,108 +326,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
// Status messages should never arrive after the handshake
|
||||
return errResp(ErrExtraStatusMsg, "uncontrolled status message")
|
||||
|
||||
case p.version < eth62 && msg.Code == GetBlockHashesMsg:
|
||||
// Retrieve the number of hashes to return and from which origin hash
|
||||
var request getBlockHashesData
|
||||
if err := msg.Decode(&request); err != nil {
|
||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||
}
|
||||
if request.Amount > uint64(downloader.MaxHashFetch) {
|
||||
request.Amount = uint64(downloader.MaxHashFetch)
|
||||
}
|
||||
// Retrieve the hashes from the block chain and return them
|
||||
hashes := pm.blockchain.GetBlockHashesFromHash(request.Hash, request.Amount)
|
||||
if len(hashes) == 0 {
|
||||
glog.V(logger.Debug).Infof("invalid block hash %x", request.Hash.Bytes()[:4])
|
||||
}
|
||||
return p.SendBlockHashes(hashes)
|
||||
|
||||
case p.version < eth62 && msg.Code == GetBlockHashesFromNumberMsg:
|
||||
// Retrieve and decode the number of hashes to return and from which origin number
|
||||
var request getBlockHashesFromNumberData
|
||||
if err := msg.Decode(&request); err != nil {
|
||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||
}
|
||||
if request.Amount > uint64(downloader.MaxHashFetch) {
|
||||
request.Amount = uint64(downloader.MaxHashFetch)
|
||||
}
|
||||
// Calculate the last block that should be retrieved, and short circuit if unavailable
|
||||
last := pm.blockchain.GetBlockByNumber(request.Number + request.Amount - 1)
|
||||
if last == nil {
|
||||
last = pm.blockchain.CurrentBlock()
|
||||
request.Amount = last.NumberU64() - request.Number + 1
|
||||
}
|
||||
if last.NumberU64() < request.Number {
|
||||
return p.SendBlockHashes(nil)
|
||||
}
|
||||
// Retrieve the hashes from the last block backwards, reverse and return
|
||||
hashes := []common.Hash{last.Hash()}
|
||||
hashes = append(hashes, pm.blockchain.GetBlockHashesFromHash(last.Hash(), request.Amount-1)...)
|
||||
|
||||
for i := 0; i < len(hashes)/2; i++ {
|
||||
hashes[i], hashes[len(hashes)-1-i] = hashes[len(hashes)-1-i], hashes[i]
|
||||
}
|
||||
return p.SendBlockHashes(hashes)
|
||||
|
||||
case p.version < eth62 && msg.Code == BlockHashesMsg:
|
||||
// A batch of hashes arrived to one of our previous requests
|
||||
var hashes []common.Hash
|
||||
if err := msg.Decode(&hashes); err != nil {
|
||||
break
|
||||
}
|
||||
// Deliver them all to the downloader for queuing
|
||||
err := pm.downloader.DeliverHashes(p.id, hashes)
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infoln(err)
|
||||
}
|
||||
|
||||
case p.version < eth62 && msg.Code == GetBlocksMsg:
|
||||
// Decode the retrieval message
|
||||
msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
|
||||
if _, err := msgStream.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Gather blocks until the fetch or network limits is reached
|
||||
var (
|
||||
hash common.Hash
|
||||
bytes common.StorageSize
|
||||
blocks []*types.Block
|
||||
)
|
||||
for len(blocks) < downloader.MaxBlockFetch && bytes < softResponseLimit {
|
||||
//Retrieve the hash of the next block
|
||||
err := msgStream.Decode(&hash)
|
||||
if err == rlp.EOL {
|
||||
break
|
||||
} else if err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
// Retrieve the requested block, stopping if enough was found
|
||||
if block := pm.blockchain.GetBlock(hash); block != nil {
|
||||
blocks = append(blocks, block)
|
||||
bytes += block.Size()
|
||||
}
|
||||
}
|
||||
return p.SendBlocks(blocks)
|
||||
|
||||
case p.version < eth62 && msg.Code == BlocksMsg:
|
||||
// Decode the arrived block message
|
||||
var blocks []*types.Block
|
||||
if err := msg.Decode(&blocks); err != nil {
|
||||
glog.V(logger.Detail).Infoln("Decode error", err)
|
||||
blocks = nil
|
||||
}
|
||||
// Update the receive timestamp of each block
|
||||
for _, block := range blocks {
|
||||
block.ReceivedAt = msg.ReceivedAt
|
||||
block.ReceivedFrom = p
|
||||
}
|
||||
// Filter out any explicitly requested blocks, deliver the rest to the downloader
|
||||
if blocks := pm.fetcher.FilterBlocks(blocks); len(blocks) > 0 {
|
||||
pm.downloader.DeliverBlocks(p.id, blocks)
|
||||
}
|
||||
|
||||
// Block header query, collect the requested headers and reply
|
||||
case p.version >= eth62 && msg.Code == GetBlockHeadersMsg:
|
||||
case msg.Code == GetBlockHeadersMsg:
|
||||
// Decode the complex header query
|
||||
var query getBlockHeadersData
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
@@ -491,7 +393,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
return p.SendBlockHeaders(headers)
|
||||
|
||||
case p.version >= eth62 && msg.Code == BlockHeadersMsg:
|
||||
case msg.Code == BlockHeadersMsg:
|
||||
// A batch of headers arrived to one of our previous requests
|
||||
var headers []*types.Header
|
||||
if err := msg.Decode(&headers); err != nil {
|
||||
@@ -505,7 +407,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
// If we already have a DAO header, we can check the peer's TD against it. If
|
||||
// the peer's ahead of this, it too must have a reply to the DAO check
|
||||
if daoHeader := pm.blockchain.GetHeaderByNumber(pm.chainconfig.DAOForkBlock.Uint64()); daoHeader != nil {
|
||||
if p.Td().Cmp(pm.blockchain.GetTd(daoHeader.Hash())) >= 0 {
|
||||
if _, td := p.Head(); td.Cmp(pm.blockchain.GetTd(daoHeader.Hash())) >= 0 {
|
||||
verifyDAO = false
|
||||
}
|
||||
}
|
||||
@@ -532,6 +434,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
return err
|
||||
}
|
||||
glog.V(logger.Debug).Infof("%v: verified to be on the same side of the DAO fork", p)
|
||||
return nil
|
||||
}
|
||||
// Irrelevant of the fork checks, send the header to the fetcher just in case
|
||||
headers = pm.fetcher.FilterHeaders(headers, time.Now())
|
||||
@@ -543,7 +446,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
}
|
||||
|
||||
case p.version >= eth62 && msg.Code == GetBlockBodiesMsg:
|
||||
case msg.Code == GetBlockBodiesMsg:
|
||||
// Decode the retrieval message
|
||||
msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
|
||||
if _, err := msgStream.List(); err != nil {
|
||||
@@ -570,7 +473,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
return p.SendBlockBodiesRLP(bodies)
|
||||
|
||||
case p.version >= eth62 && msg.Code == BlockBodiesMsg:
|
||||
case msg.Code == BlockBodiesMsg:
|
||||
// A batch of block bodies arrived to one of our previous requests
|
||||
var request blockBodiesData
|
||||
if err := msg.Decode(&request); err != nil {
|
||||
@@ -711,7 +614,6 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
// Mark the hashes as present at the remote node
|
||||
for _, block := range announces {
|
||||
p.MarkBlock(block.Hash)
|
||||
p.SetHead(block.Hash)
|
||||
}
|
||||
// Schedule all the unknown hashes for retrieval
|
||||
unknown := make([]announce, 0, len(announces))
|
||||
@@ -721,11 +623,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
}
|
||||
for _, block := range unknown {
|
||||
if p.version < eth62 {
|
||||
pm.fetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestBlocks, nil, nil)
|
||||
} else {
|
||||
pm.fetcher.Notify(p.id, block.Hash, block.Number, time.Now(), nil, p.RequestOneHeader, p.RequestBodies)
|
||||
}
|
||||
pm.fetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies)
|
||||
}
|
||||
|
||||
case msg.Code == NewBlockMsg:
|
||||
@@ -742,15 +640,23 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
|
||||
// Mark the peer as owning the block and schedule it for import
|
||||
p.MarkBlock(request.Block.Hash())
|
||||
p.SetHead(request.Block.Hash())
|
||||
|
||||
pm.fetcher.Enqueue(p.id, request.Block)
|
||||
|
||||
// Update the peers total difficulty if needed, schedule a download if gapped
|
||||
if request.TD.Cmp(p.Td()) > 0 {
|
||||
p.SetTd(request.TD)
|
||||
td := pm.blockchain.GetTd(pm.blockchain.CurrentBlock().Hash())
|
||||
if request.TD.Cmp(new(big.Int).Add(td, request.Block.Difficulty())) > 0 {
|
||||
// Assuming the block is importable by the peer, but possibly not yet done so,
|
||||
// calculate the head hash and TD that the peer truly must have.
|
||||
var (
|
||||
trueHead = request.Block.ParentHash()
|
||||
trueTD = new(big.Int).Sub(request.TD, request.Block.Difficulty())
|
||||
)
|
||||
// Update the peers total difficulty if better than the previous
|
||||
if _, td := p.Head(); trueTD.Cmp(td) > 0 {
|
||||
p.SetHead(trueHead, trueTD)
|
||||
|
||||
// Schedule a sync if above ours. Note, this will not fire a sync for a gap of
|
||||
// a singe block (as the true TD is below the propagated block), however this
|
||||
// scenario should easily be covered by the fetcher.
|
||||
currentBlock := pm.blockchain.CurrentBlock()
|
||||
if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash())) > 0 {
|
||||
go pm.synchronise(p)
|
||||
}
|
||||
}
|
||||
@@ -806,11 +712,7 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
|
||||
// Otherwise if the block is indeed in out own chain, announce it
|
||||
if pm.blockchain.HasBlock(hash) {
|
||||
for _, peer := range peers {
|
||||
if peer.version < eth62 {
|
||||
peer.SendNewBlockHashes61([]common.Hash{hash})
|
||||
} else {
|
||||
peer.SendNewBlockHashes([]common.Hash{hash}, []uint64{block.NumberU64()})
|
||||
}
|
||||
peer.SendNewBlockHashes([]common.Hash{hash}, []uint64{block.NumberU64()})
|
||||
}
|
||||
glog.V(logger.Detail).Infof("announced block %x to %d peers in %v", hash[:4], len(peers), time.Since(block.ReceivedAt))
|
||||
}
|
||||
|
@@ -63,160 +63,6 @@ func TestProtocolCompatibility(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that hashes can be retrieved from a remote chain by hashes in reverse
|
||||
// order.
|
||||
func TestGetBlockHashes61(t *testing.T) { testGetBlockHashes(t, 61) }
|
||||
|
||||
func testGetBlockHashes(t *testing.T, protocol int) {
|
||||
pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := downloader.MaxHashFetch
|
||||
tests := []struct {
|
||||
origin common.Hash
|
||||
number int
|
||||
result int
|
||||
}{
|
||||
{common.Hash{}, 1, 0}, // Make sure non existent hashes don't return results
|
||||
{pm.blockchain.Genesis().Hash(), 1, 0}, // There are no hashes to retrieve up from the genesis
|
||||
{pm.blockchain.GetBlockByNumber(5).Hash(), 5, 5}, // All the hashes including the genesis requested
|
||||
{pm.blockchain.GetBlockByNumber(5).Hash(), 10, 5}, // More hashes than available till the genesis requested
|
||||
{pm.blockchain.GetBlockByNumber(100).Hash(), 10, 10}, // All hashes available from the middle of the chain
|
||||
{pm.blockchain.CurrentBlock().Hash(), 10, 10}, // All hashes available from the head of the chain
|
||||
{pm.blockchain.CurrentBlock().Hash(), limit, limit}, // Request the maximum allowed hash count
|
||||
{pm.blockchain.CurrentBlock().Hash(), limit + 1, limit}, // Request more than the maximum allowed hash count
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Assemble the hash response we would like to receive
|
||||
resp := make([]common.Hash, tt.result)
|
||||
if len(resp) > 0 {
|
||||
from := pm.blockchain.GetBlock(tt.origin).NumberU64() - 1
|
||||
for j := 0; j < len(resp); j++ {
|
||||
resp[j] = pm.blockchain.GetBlockByNumber(uint64(int(from) - j)).Hash()
|
||||
}
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x03, getBlockHashesData{tt.origin, uint64(tt.number)})
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, resp); err != nil {
|
||||
t.Errorf("test %d: block hashes mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that hashes can be retrieved from a remote chain by numbers in forward
|
||||
// order.
|
||||
func TestGetBlockHashesFromNumber61(t *testing.T) { testGetBlockHashesFromNumber(t, 61) }
|
||||
|
||||
func testGetBlockHashesFromNumber(t *testing.T, protocol int) {
|
||||
pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := downloader.MaxHashFetch
|
||||
tests := []struct {
|
||||
origin uint64
|
||||
number int
|
||||
result int
|
||||
}{
|
||||
{pm.blockchain.CurrentBlock().NumberU64() + 1, 1, 0}, // Out of bounds requests should return empty
|
||||
{pm.blockchain.CurrentBlock().NumberU64(), 1, 1}, // Make sure the head hash can be retrieved
|
||||
{pm.blockchain.CurrentBlock().NumberU64() - 4, 5, 5}, // All hashes, including the head hash requested
|
||||
{pm.blockchain.CurrentBlock().NumberU64() - 4, 10, 5}, // More hashes requested than available till the head
|
||||
{pm.blockchain.CurrentBlock().NumberU64() - 100, 10, 10}, // All hashes available from the middle of the chain
|
||||
{0, 10, 10}, // All hashes available from the root of the chain
|
||||
{0, limit, limit}, // Request the maximum allowed hash count
|
||||
{0, limit + 1, limit}, // Request more than the maximum allowed hash count
|
||||
{0, 1, 1}, // Make sure the genesis hash can be retrieved
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Assemble the hash response we would like to receive
|
||||
resp := make([]common.Hash, tt.result)
|
||||
for j := 0; j < len(resp); j++ {
|
||||
resp[j] = pm.blockchain.GetBlockByNumber(tt.origin + uint64(j)).Hash()
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x08, getBlockHashesFromNumberData{tt.origin, uint64(tt.number)})
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, resp); err != nil {
|
||||
t.Errorf("test %d: block hashes mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that blocks can be retrieved from a remote chain based on their hashes.
|
||||
func TestGetBlocks61(t *testing.T) { testGetBlocks(t, 61) }
|
||||
|
||||
func testGetBlocks(t *testing.T, protocol int) {
|
||||
pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := downloader.MaxBlockFetch
|
||||
tests := []struct {
|
||||
random int // Number of blocks to fetch randomly from the chain
|
||||
explicit []common.Hash // Explicitly requested blocks
|
||||
available []bool // Availability of explicitly requested blocks
|
||||
expected int // Total number of existing blocks to expect
|
||||
}{
|
||||
{1, nil, nil, 1}, // A single random block should be retrievable
|
||||
{10, nil, nil, 10}, // Multiple random blocks should be retrievable
|
||||
{limit, nil, nil, limit}, // The maximum possible blocks should be retrievable
|
||||
{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
|
||||
{0, []common.Hash{pm.blockchain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable
|
||||
{0, []common.Hash{pm.blockchain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
|
||||
{0, []common.Hash{common.Hash{}}, []bool{false}, 0}, // A non existent block should not be returned
|
||||
|
||||
// Existing and non-existing blocks interleaved should not cause problems
|
||||
{0, []common.Hash{
|
||||
common.Hash{},
|
||||
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||
common.Hash{},
|
||||
pm.blockchain.GetBlockByNumber(10).Hash(),
|
||||
common.Hash{},
|
||||
pm.blockchain.GetBlockByNumber(100).Hash(),
|
||||
common.Hash{},
|
||||
}, []bool{false, true, false, true, false, true, false}, 3},
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Collect the hashes to request, and the response to expect
|
||||
hashes, seen := []common.Hash{}, make(map[int64]bool)
|
||||
blocks := []*types.Block{}
|
||||
|
||||
for j := 0; j < tt.random; j++ {
|
||||
for {
|
||||
num := rand.Int63n(int64(pm.blockchain.CurrentBlock().NumberU64()))
|
||||
if !seen[num] {
|
||||
seen[num] = true
|
||||
|
||||
block := pm.blockchain.GetBlockByNumber(uint64(num))
|
||||
hashes = append(hashes, block.Hash())
|
||||
if len(blocks) < tt.expected {
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for j, hash := range tt.explicit {
|
||||
hashes = append(hashes, hash)
|
||||
if tt.available[j] && len(blocks) < tt.expected {
|
||||
blocks = append(blocks, pm.blockchain.GetBlock(hash))
|
||||
}
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x05, hashes)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x06, blocks); err != nil {
|
||||
t.Errorf("test %d: blocks mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that block headers can be retrieved from a remote chain based on user queries.
|
||||
func TestGetBlockHeaders62(t *testing.T) { testGetBlockHeaders(t, 62) }
|
||||
func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) }
|
||||
|
@@ -34,14 +34,6 @@ var (
|
||||
propBlockInTrafficMeter = metrics.NewMeter("eth/prop/blocks/in/traffic")
|
||||
propBlockOutPacketsMeter = metrics.NewMeter("eth/prop/blocks/out/packets")
|
||||
propBlockOutTrafficMeter = metrics.NewMeter("eth/prop/blocks/out/traffic")
|
||||
reqHashInPacketsMeter = metrics.NewMeter("eth/req/hashes/in/packets")
|
||||
reqHashInTrafficMeter = metrics.NewMeter("eth/req/hashes/in/traffic")
|
||||
reqHashOutPacketsMeter = metrics.NewMeter("eth/req/hashes/out/packets")
|
||||
reqHashOutTrafficMeter = metrics.NewMeter("eth/req/hashes/out/traffic")
|
||||
reqBlockInPacketsMeter = metrics.NewMeter("eth/req/blocks/in/packets")
|
||||
reqBlockInTrafficMeter = metrics.NewMeter("eth/req/blocks/in/traffic")
|
||||
reqBlockOutPacketsMeter = metrics.NewMeter("eth/req/blocks/out/packets")
|
||||
reqBlockOutTrafficMeter = metrics.NewMeter("eth/req/blocks/out/traffic")
|
||||
reqHeaderInPacketsMeter = metrics.NewMeter("eth/req/headers/in/packets")
|
||||
reqHeaderInTrafficMeter = metrics.NewMeter("eth/req/headers/in/traffic")
|
||||
reqHeaderOutPacketsMeter = metrics.NewMeter("eth/req/headers/out/packets")
|
||||
@@ -95,14 +87,9 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) {
|
||||
// Account for the data traffic
|
||||
packets, traffic := miscInPacketsMeter, miscInTrafficMeter
|
||||
switch {
|
||||
case rw.version < eth62 && msg.Code == BlockHashesMsg:
|
||||
packets, traffic = reqHashInPacketsMeter, reqHashInTrafficMeter
|
||||
case rw.version < eth62 && msg.Code == BlocksMsg:
|
||||
packets, traffic = reqBlockInPacketsMeter, reqBlockInTrafficMeter
|
||||
|
||||
case rw.version >= eth62 && msg.Code == BlockHeadersMsg:
|
||||
case msg.Code == BlockHeadersMsg:
|
||||
packets, traffic = reqHeaderInPacketsMeter, reqHeaderInTrafficMeter
|
||||
case rw.version >= eth62 && msg.Code == BlockBodiesMsg:
|
||||
case msg.Code == BlockBodiesMsg:
|
||||
packets, traffic = reqBodyInPacketsMeter, reqBodyInTrafficMeter
|
||||
|
||||
case rw.version >= eth63 && msg.Code == NodeDataMsg:
|
||||
@@ -127,14 +114,9 @@ func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error {
|
||||
// Account for the data traffic
|
||||
packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter
|
||||
switch {
|
||||
case rw.version < eth62 && msg.Code == BlockHashesMsg:
|
||||
packets, traffic = reqHashOutPacketsMeter, reqHashOutTrafficMeter
|
||||
case rw.version < eth62 && msg.Code == BlocksMsg:
|
||||
packets, traffic = reqBlockOutPacketsMeter, reqBlockOutTrafficMeter
|
||||
|
||||
case rw.version >= eth62 && msg.Code == BlockHeadersMsg:
|
||||
case msg.Code == BlockHeadersMsg:
|
||||
packets, traffic = reqHeaderOutPacketsMeter, reqHeaderOutTrafficMeter
|
||||
case rw.version >= eth62 && msg.Code == BlockBodiesMsg:
|
||||
case msg.Code == BlockBodiesMsg:
|
||||
packets, traffic = reqBodyOutPacketsMeter, reqBodyOutTrafficMeter
|
||||
|
||||
case rw.version >= eth63 && msg.Code == NodeDataMsg:
|
||||
|
74
eth/peer.go
74
eth/peer.go
@@ -25,7 +25,6 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
@@ -85,43 +84,31 @@ func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||
|
||||
// Info gathers and returns a collection of metadata known about a peer.
|
||||
func (p *peer) Info() *PeerInfo {
|
||||
hash, td := p.Head()
|
||||
|
||||
return &PeerInfo{
|
||||
Version: p.version,
|
||||
Difficulty: p.Td(),
|
||||
Head: fmt.Sprintf("%x", p.Head()),
|
||||
Difficulty: td,
|
||||
Head: hash.Hex(),
|
||||
}
|
||||
}
|
||||
|
||||
// Head retrieves a copy of the current head (most recent) hash of the peer.
|
||||
func (p *peer) Head() (hash common.Hash) {
|
||||
// Head retrieves a copy of the current head hash and total difficulty of the
|
||||
// peer.
|
||||
func (p *peer) Head() (hash common.Hash, td *big.Int) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
copy(hash[:], p.head[:])
|
||||
return hash
|
||||
return hash, new(big.Int).Set(p.td)
|
||||
}
|
||||
|
||||
// SetHead updates the head (most recent) hash of the peer.
|
||||
func (p *peer) SetHead(hash common.Hash) {
|
||||
// SetHead updates the head hash and total difficulty of the peer.
|
||||
func (p *peer) SetHead(hash common.Hash, td *big.Int) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
copy(p.head[:], hash[:])
|
||||
}
|
||||
|
||||
// Td retrieves the current total difficulty of a peer.
|
||||
func (p *peer) Td() *big.Int {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return new(big.Int).Set(p.td)
|
||||
}
|
||||
|
||||
// SetTd updates the current total difficulty of a peer.
|
||||
func (p *peer) SetTd(td *big.Int) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.td.Set(td)
|
||||
}
|
||||
|
||||
@@ -154,25 +141,6 @@ func (p *peer) SendTransactions(txs types.Transactions) error {
|
||||
return p2p.Send(p.rw, TxMsg, txs)
|
||||
}
|
||||
|
||||
// SendBlockHashes sends a batch of known hashes to the remote peer.
|
||||
func (p *peer) SendBlockHashes(hashes []common.Hash) error {
|
||||
return p2p.Send(p.rw, BlockHashesMsg, hashes)
|
||||
}
|
||||
|
||||
// SendBlocks sends a batch of blocks to the remote peer.
|
||||
func (p *peer) SendBlocks(blocks []*types.Block) error {
|
||||
return p2p.Send(p.rw, BlocksMsg, blocks)
|
||||
}
|
||||
|
||||
// SendNewBlockHashes61 announces the availability of a number of blocks through
|
||||
// a hash notification.
|
||||
func (p *peer) SendNewBlockHashes61(hashes []common.Hash) error {
|
||||
for _, hash := range hashes {
|
||||
p.knownBlocks.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, NewBlockHashesMsg, hashes)
|
||||
}
|
||||
|
||||
// SendNewBlockHashes announces the availability of a number of blocks through
|
||||
// a hash notification.
|
||||
func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
|
||||
@@ -221,26 +189,6 @@ func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
|
||||
return p2p.Send(p.rw, ReceiptsMsg, receipts)
|
||||
}
|
||||
|
||||
// RequestHashes fetches a batch of hashes from a peer, starting at from, going
|
||||
// towards the genesis block.
|
||||
func (p *peer) RequestHashes(from common.Hash) error {
|
||||
glog.V(logger.Debug).Infof("%v fetching hashes (%d) from %x...", p, downloader.MaxHashFetch, from[:4])
|
||||
return p2p.Send(p.rw, GetBlockHashesMsg, getBlockHashesData{from, uint64(downloader.MaxHashFetch)})
|
||||
}
|
||||
|
||||
// RequestHashesFromNumber fetches a batch of hashes from a peer, starting at
|
||||
// the requested block number, going upwards towards the genesis block.
|
||||
func (p *peer) RequestHashesFromNumber(from uint64, count int) error {
|
||||
glog.V(logger.Debug).Infof("%v fetching hashes (%d) from #%d...", p, count, from)
|
||||
return p2p.Send(p.rw, GetBlockHashesFromNumberMsg, getBlockHashesFromNumberData{from, uint64(count)})
|
||||
}
|
||||
|
||||
// RequestBlocks fetches a batch of blocks corresponding to the specified hashes.
|
||||
func (p *peer) RequestBlocks(hashes []common.Hash) error {
|
||||
glog.V(logger.Debug).Infof("%v fetching %v blocks", p, len(hashes))
|
||||
return p2p.Send(p.rw, GetBlocksMsg, hashes)
|
||||
}
|
||||
|
||||
// RequestHeaders is a wrapper around the header query functions to fetch a
|
||||
// single header. It is used solely by the fetcher.
|
||||
func (p *peer) RequestOneHeader(hash common.Hash) error {
|
||||
@@ -451,7 +399,7 @@ func (ps *peerSet) BestPeer() *peer {
|
||||
bestTd *big.Int
|
||||
)
|
||||
for _, p := range ps.peers {
|
||||
if td := p.Td(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
||||
if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
||||
bestPeer, bestTd = p, td
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,6 @@ import (
|
||||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
eth61 = 61
|
||||
eth62 = 62
|
||||
eth63 = 63
|
||||
)
|
||||
@@ -49,26 +48,15 @@ const (
|
||||
|
||||
// eth protocol message codes
|
||||
const (
|
||||
// Protocol messages belonging to eth/61
|
||||
StatusMsg = 0x00
|
||||
NewBlockHashesMsg = 0x01
|
||||
TxMsg = 0x02
|
||||
GetBlockHashesMsg = 0x03
|
||||
BlockHashesMsg = 0x04
|
||||
GetBlocksMsg = 0x05
|
||||
BlocksMsg = 0x06
|
||||
NewBlockMsg = 0x07
|
||||
GetBlockHashesFromNumberMsg = 0x08
|
||||
|
||||
// Protocol messages belonging to eth/62 (new protocol from scratch)
|
||||
// StatusMsg = 0x00 (uncomment after eth/61 deprecation)
|
||||
// NewBlockHashesMsg = 0x01 (uncomment after eth/61 deprecation)
|
||||
// TxMsg = 0x02 (uncomment after eth/61 deprecation)
|
||||
// Protocol messages belonging to eth/62
|
||||
StatusMsg = 0x00
|
||||
NewBlockHashesMsg = 0x01
|
||||
TxMsg = 0x02
|
||||
GetBlockHeadersMsg = 0x03
|
||||
BlockHeadersMsg = 0x04
|
||||
GetBlockBodiesMsg = 0x05
|
||||
BlockBodiesMsg = 0x06
|
||||
// NewBlockMsg = 0x07 (uncomment after eth/61 deprecation)
|
||||
NewBlockMsg = 0x07
|
||||
|
||||
// Protocol messages belonging to eth/63
|
||||
GetNodeDataMsg = 0x0d
|
||||
@@ -117,12 +105,6 @@ type txPool interface {
|
||||
GetTransactions() types.Transactions
|
||||
}
|
||||
|
||||
type chainManager interface {
|
||||
GetBlockHashesFromHash(hash common.Hash, amount uint64) (hashes []common.Hash)
|
||||
GetBlock(hash common.Hash) (block *types.Block)
|
||||
Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash)
|
||||
}
|
||||
|
||||
// statusData is the network packet for the status message.
|
||||
type statusData struct {
|
||||
ProtocolVersion uint32
|
||||
@@ -138,19 +120,6 @@ type newBlockHashesData []struct {
|
||||
Number uint64 // Number of one particular block being announced
|
||||
}
|
||||
|
||||
// getBlockHashesData is the network packet for the hash based hash retrieval.
|
||||
type getBlockHashesData struct {
|
||||
Hash common.Hash
|
||||
Amount uint64
|
||||
}
|
||||
|
||||
// getBlockHashesFromNumberData is the network packet for the number based hash
|
||||
// retrieval.
|
||||
type getBlockHashesFromNumberData struct {
|
||||
Number uint64
|
||||
Amount uint64
|
||||
}
|
||||
|
||||
// getBlockHeadersData represents a block header query.
|
||||
type getBlockHeadersData struct {
|
||||
Origin hashOrNumber // Block from which to retrieve headers
|
||||
@@ -209,8 +178,3 @@ type blockBody struct {
|
||||
|
||||
// blockBodiesData is the network packet for block content distribution.
|
||||
type blockBodiesData []*blockBody
|
||||
|
||||
// nodeDataData is the network response packet for a node data retrieval.
|
||||
type nodeDataData []struct {
|
||||
Value []byte
|
||||
}
|
||||
|
@@ -37,7 +37,6 @@ func init() {
|
||||
var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
// Tests that handshake failures are detected and reported correctly.
|
||||
func TestStatusMsgErrors61(t *testing.T) { testStatusMsgErrors(t, 61) }
|
||||
func TestStatusMsgErrors62(t *testing.T) { testStatusMsgErrors(t, 62) }
|
||||
func TestStatusMsgErrors63(t *testing.T) { testStatusMsgErrors(t, 63) }
|
||||
|
||||
@@ -90,7 +89,6 @@ func testStatusMsgErrors(t *testing.T, protocol int) {
|
||||
}
|
||||
|
||||
// This test checks that received transactions are added to the local pool.
|
||||
func TestRecvTransactions61(t *testing.T) { testRecvTransactions(t, 61) }
|
||||
func TestRecvTransactions62(t *testing.T) { testRecvTransactions(t, 62) }
|
||||
func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) }
|
||||
|
||||
@@ -119,7 +117,6 @@ func testRecvTransactions(t *testing.T, protocol int) {
|
||||
}
|
||||
|
||||
// This test checks that pending transactions are sent.
|
||||
func TestSendTransactions61(t *testing.T) { testSendTransactions(t, 61) }
|
||||
func TestSendTransactions62(t *testing.T) { testSendTransactions(t, 62) }
|
||||
func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
|
||||
|
||||
|
11
eth/sync.go
11
eth/sync.go
@@ -161,9 +161,12 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
|
||||
if peer == nil {
|
||||
return
|
||||
}
|
||||
// Make sure the peer's TD is higher than our own. If not drop.
|
||||
td := pm.blockchain.GetTd(pm.blockchain.CurrentBlock().Hash())
|
||||
if peer.Td().Cmp(td) <= 0 {
|
||||
// Make sure the peer's TD is higher than our own
|
||||
currentBlock := pm.blockchain.CurrentBlock()
|
||||
td := pm.blockchain.GetTd(currentBlock.Hash())
|
||||
|
||||
pHead, pTd := peer.Head()
|
||||
if pTd.Cmp(td) <= 0 {
|
||||
return
|
||||
}
|
||||
// Otherwise try to sync with the downloader
|
||||
@@ -171,7 +174,7 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
|
||||
if atomic.LoadUint32(&pm.fastSync) == 1 {
|
||||
mode = downloader.FastSync
|
||||
}
|
||||
if err := pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), mode); err != nil {
|
||||
if err := pm.downloader.Synchronise(peer.id, pHead, pTd, mode); err != nil {
|
||||
return
|
||||
}
|
||||
atomic.StoreUint32(&pm.synced, 1) // Mark initial sync done
|
||||
|
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
|
||||
@@ -86,6 +87,7 @@ func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) {
|
||||
OpenFilesCacheCapacity: handles,
|
||||
BlockCacheCapacity: cache / 2 * opt.MiB,
|
||||
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
|
||||
Filter: filter.NewBloomFilter(10),
|
||||
})
|
||||
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
|
||||
db, err = leveldb.RecoverFile(file, nil)
|
||||
|
177
internal/build/archive.go
Normal file
177
internal/build/archive.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// 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 build
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Archive interface {
|
||||
// Directory adds a new directory entry to the archive and sets the
|
||||
// directory for subsequent calls to Header.
|
||||
Directory(name string) error
|
||||
|
||||
// Header adds a new file to the archive. The file is added to the directory
|
||||
// set by Directory. The content of the file must be written to the returned
|
||||
// writer.
|
||||
Header(os.FileInfo) (io.Writer, error)
|
||||
|
||||
// Close flushes the archive and closes the underlying file.
|
||||
Close() error
|
||||
}
|
||||
|
||||
func NewArchive(file *os.File) Archive {
|
||||
switch {
|
||||
case strings.HasSuffix(file.Name(), ".zip"):
|
||||
return NewZipArchive(file)
|
||||
case strings.HasSuffix(file.Name(), ".tar.gz"):
|
||||
return NewTarballArchive(file)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddFile appends an existing file to an archive.
|
||||
func AddFile(a Archive, file string) error {
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
fi, err := fd.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := a.Header(fi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(w, fd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteArchive creates an archive containing the given files.
|
||||
func WriteArchive(basename, ext string, files []string) error {
|
||||
archfd, err := os.Create(basename + ext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archfd.Close()
|
||||
archive := NewArchive(archfd)
|
||||
if archive == nil {
|
||||
return fmt.Errorf("unknown archive extension: %s", ext)
|
||||
}
|
||||
fmt.Println(basename + ext)
|
||||
if err := archive.Directory(basename); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
fmt.Println(" +", filepath.Base(file))
|
||||
if err := AddFile(archive, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return archive.Close()
|
||||
}
|
||||
|
||||
type ZipArchive struct {
|
||||
dir string
|
||||
zipw *zip.Writer
|
||||
file io.Closer
|
||||
}
|
||||
|
||||
func NewZipArchive(w io.WriteCloser) Archive {
|
||||
return &ZipArchive{"", zip.NewWriter(w), w}
|
||||
}
|
||||
|
||||
func (a *ZipArchive) Directory(name string) error {
|
||||
a.dir = name + "/"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) {
|
||||
head, err := zip.FileInfoHeader(fi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't make zip header: %v", err)
|
||||
}
|
||||
head.Name = a.dir + head.Name
|
||||
w, err := a.zipw.CreateHeader(head)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't add zip header: %v", err)
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (a *ZipArchive) Close() error {
|
||||
if err := a.zipw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.file.Close()
|
||||
}
|
||||
|
||||
type TarballArchive struct {
|
||||
dir string
|
||||
tarw *tar.Writer
|
||||
gzw *gzip.Writer
|
||||
file io.Closer
|
||||
}
|
||||
|
||||
func NewTarballArchive(w io.WriteCloser) Archive {
|
||||
gzw := gzip.NewWriter(w)
|
||||
tarw := tar.NewWriter(gzw)
|
||||
return &TarballArchive{"", tarw, gzw, w}
|
||||
}
|
||||
|
||||
func (a *TarballArchive) Directory(name string) error {
|
||||
a.dir = name + "/"
|
||||
return a.tarw.WriteHeader(&tar.Header{
|
||||
Name: a.dir,
|
||||
Mode: 0755,
|
||||
Typeflag: tar.TypeDir,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) {
|
||||
head, err := tar.FileInfoHeader(fi, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't make tar header: %v", err)
|
||||
}
|
||||
head.Name = a.dir + head.Name
|
||||
if err := a.tarw.WriteHeader(head); err != nil {
|
||||
return nil, fmt.Errorf("can't add tar header: %v", err)
|
||||
}
|
||||
return a.tarw, nil
|
||||
}
|
||||
|
||||
func (a *TarballArchive) Close() error {
|
||||
if err := a.tarw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.gzw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.file.Close()
|
||||
}
|
116
internal/build/env.go
Normal file
116
internal/build/env.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 build
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// These flags override values in build env.
|
||||
GitCommitFlag = flag.String("git-commit", "", `Overrides git commit hash embedded into executables`)
|
||||
GitBranchFlag = flag.String("git-branch", "", `Overrides git branch being built`)
|
||||
GitTagFlag = flag.String("git-tag", "", `Overrides git tag being built`)
|
||||
BuildnumFlag = flag.String("buildnum", "", `Overrides CI build number`)
|
||||
PullRequestFlag = flag.Bool("pull-request", false, `Overrides pull request status of the build`)
|
||||
)
|
||||
|
||||
// Environment contains metadata provided by the build environment.
|
||||
type Environment struct {
|
||||
Name string // name of the environment
|
||||
Repo string // name of GitHub repo
|
||||
Commit, Branch, Tag string // Git info
|
||||
Buildnum string
|
||||
IsPullRequest bool
|
||||
}
|
||||
|
||||
func (env Environment) String() string {
|
||||
return fmt.Sprintf("%s env (commit:%s branch:%s tag:%s buildnum:%s pr:%t)",
|
||||
env.Name, env.Commit, env.Branch, env.Tag, env.Buildnum, env.IsPullRequest)
|
||||
}
|
||||
|
||||
// Env returns metadata about the current CI environment, falling back to LocalEnv
|
||||
// if not running on CI.
|
||||
func Env() Environment {
|
||||
switch {
|
||||
case os.Getenv("CI") == "true" && os.Getenv("TRAVIS") == "true":
|
||||
return Environment{
|
||||
Name: "travis",
|
||||
Repo: os.Getenv("TRAVIS_REPO_SLUG"),
|
||||
Commit: os.Getenv("TRAVIS_COMMIT"),
|
||||
Branch: os.Getenv("TRAVIS_BRANCH"),
|
||||
Tag: os.Getenv("TRAVIS_TAG"),
|
||||
Buildnum: os.Getenv("TRAVIS_BUILD_NUMBER"),
|
||||
IsPullRequest: os.Getenv("TRAVIS_PULL_REQUEST") != "false",
|
||||
}
|
||||
case os.Getenv("CI") == "True" && os.Getenv("APPVEYOR") == "True":
|
||||
return Environment{
|
||||
Name: "appveyor",
|
||||
Repo: os.Getenv("APPVEYOR_REPO_NAME"),
|
||||
Commit: os.Getenv("APPVEYOR_REPO_COMMIT"),
|
||||
Branch: os.Getenv("APPVEYOR_REPO_BRANCH"),
|
||||
Tag: os.Getenv("APPVEYOR_REPO_TAG"),
|
||||
Buildnum: os.Getenv("APPVEYOR_BUILD_NUMBER"),
|
||||
IsPullRequest: os.Getenv("APPVEYOR_PULL_REQUEST_NUMBER") != "",
|
||||
}
|
||||
default:
|
||||
return LocalEnv()
|
||||
}
|
||||
}
|
||||
|
||||
// LocalEnv returns build environment metadata gathered from git.
|
||||
func LocalEnv() Environment {
|
||||
env := applyEnvFlags(Environment{Name: "local", Repo: "ethereum/go-ethereum"})
|
||||
if _, err := os.Stat(".git"); err != nil {
|
||||
return env
|
||||
}
|
||||
if env.Commit == "" {
|
||||
env.Commit = RunGit("rev-parse", "HEAD")
|
||||
}
|
||||
if env.Branch == "" {
|
||||
if b := RunGit("rev-parse", "--abbrev-ref", "HEAD"); b != "HEAD" {
|
||||
env.Branch = b
|
||||
}
|
||||
}
|
||||
// Note that we don't get the current git tag. It would slow down
|
||||
// builds and isn't used by anything.
|
||||
return env
|
||||
}
|
||||
|
||||
func applyEnvFlags(env Environment) Environment {
|
||||
if !flag.Parsed() {
|
||||
panic("you need to call flag.Parse before Env or LocalEnv")
|
||||
}
|
||||
if *GitCommitFlag != "" {
|
||||
env.Commit = *GitCommitFlag
|
||||
}
|
||||
if *GitBranchFlag != "" {
|
||||
env.Branch = *GitBranchFlag
|
||||
}
|
||||
if *GitTagFlag != "" {
|
||||
env.Tag = *GitTagFlag
|
||||
}
|
||||
if *BuildnumFlag != "" {
|
||||
env.Buildnum = *BuildnumFlag
|
||||
}
|
||||
if *PullRequestFlag {
|
||||
env.IsPullRequest = true
|
||||
}
|
||||
return env
|
||||
}
|
120
internal/build/util.go
Normal file
120
internal/build/util.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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 build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands")
|
||||
|
||||
// MustRun executes the given command and exits the host process for
|
||||
// any error.
|
||||
func MustRun(cmd *exec.Cmd) {
|
||||
fmt.Println(">>>", strings.Join(cmd.Args, " "))
|
||||
if !*DryRunFlag {
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MustRunCommand(cmd string, args ...string) {
|
||||
MustRun(exec.Command(cmd, args...))
|
||||
}
|
||||
|
||||
// GOPATH returns the value that the GOPATH environment
|
||||
// variable should be set to.
|
||||
func GOPATH() string {
|
||||
path := filepath.SplitList(os.Getenv("GOPATH"))
|
||||
if len(path) == 0 {
|
||||
log.Fatal("GOPATH is not set")
|
||||
}
|
||||
// Ensure Godeps workspace is present in the path.
|
||||
godeps, _ := filepath.Abs(filepath.Join("Godeps", "_workspace"))
|
||||
for _, dir := range path {
|
||||
if dir == godeps {
|
||||
return strings.Join(path, string(filepath.ListSeparator))
|
||||
}
|
||||
}
|
||||
newpath := append(path[:1], godeps)
|
||||
newpath = append(newpath, path[1:]...)
|
||||
return strings.Join(newpath, string(filepath.ListSeparator))
|
||||
}
|
||||
|
||||
// VERSION returns the content of the VERSION file.
|
||||
func VERSION() string {
|
||||
version, err := ioutil.ReadFile("VERSION")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return string(bytes.TrimSpace(version))
|
||||
}
|
||||
|
||||
// RunGit runs a git subcommand and returns its output.
|
||||
// The command must complete successfully.
|
||||
func RunGit(args ...string) string {
|
||||
cmd := exec.Command("git", args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout, cmd.Stderr = &stdout, &stderr
|
||||
if err := cmd.Run(); err == exec.ErrNotFound {
|
||||
log.Println("no git in PATH")
|
||||
return ""
|
||||
} else if err != nil {
|
||||
log.Fatal(strings.Join(cmd.Args, " "), ": ", err, "\n", stderr.String())
|
||||
}
|
||||
return strings.TrimSpace(stdout.String())
|
||||
}
|
||||
|
||||
// Render renders the given template file into outputFile.
|
||||
func Render(templateFile, outputFile string, outputPerm os.FileMode, x interface{}) {
|
||||
tpl := template.Must(template.ParseFiles(templateFile))
|
||||
render(tpl, outputFile, outputPerm, x)
|
||||
}
|
||||
|
||||
// RenderString renders the given template string into outputFile.
|
||||
func RenderString(templateContent, outputFile string, outputPerm os.FileMode, x interface{}) {
|
||||
tpl := template.Must(template.New("").Parse(templateContent))
|
||||
render(tpl, outputFile, outputPerm, x)
|
||||
}
|
||||
|
||||
func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x interface{}) {
|
||||
if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_EXCL, outputPerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := tpl.Execute(out, x); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := out.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"golang.org/x/net/context"
|
||||
@@ -42,7 +43,6 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
|
||||
case *TrieRequest:
|
||||
t, _ := trie.New(req.root, odr.sdb)
|
||||
req.proof = t.Prove(req.key)
|
||||
trie.ClearGlobalCache()
|
||||
case *NodeDataRequest:
|
||||
req.data, _ = odr.sdb.Get(req.hash[:])
|
||||
}
|
||||
@@ -61,8 +61,8 @@ func makeTestState() (common.Hash, ethdb.Database) {
|
||||
so.SetNonce(100)
|
||||
}
|
||||
so.AddBalance(big.NewInt(int64(i)))
|
||||
so.SetCode([]byte{i, i, i})
|
||||
so.Update()
|
||||
so.SetCode(crypto.Keccak256Hash([]byte{i, i, i}), []byte{i, i, i})
|
||||
so.UpdateRoot(sdb)
|
||||
st.UpdateStateObject(so)
|
||||
}
|
||||
root, _ := st.Commit()
|
||||
@@ -75,7 +75,6 @@ func TestLightStateOdr(t *testing.T) {
|
||||
odr := &testOdr{sdb: sdb, ldb: ldb}
|
||||
ls := NewLightState(root, odr)
|
||||
ctx := context.Background()
|
||||
trie.ClearGlobalCache()
|
||||
|
||||
for i := byte(0); i < 100; i++ {
|
||||
addr := common.Address{i}
|
||||
@@ -160,7 +159,6 @@ func TestLightStateSetCopy(t *testing.T) {
|
||||
odr := &testOdr{sdb: sdb, ldb: ldb}
|
||||
ls := NewLightState(root, odr)
|
||||
ctx := context.Background()
|
||||
trie.ClearGlobalCache()
|
||||
|
||||
for i := byte(0); i < 100; i++ {
|
||||
addr := common.Address{i}
|
||||
@@ -237,7 +235,6 @@ func TestLightStateDelete(t *testing.T) {
|
||||
odr := &testOdr{sdb: sdb, ldb: ldb}
|
||||
ls := NewLightState(root, odr)
|
||||
ctx := context.Background()
|
||||
trie.ClearGlobalCache()
|
||||
|
||||
addr := common.Address{42}
|
||||
|
||||
|
@@ -358,7 +358,7 @@ func (self *worker) push(work *Work) {
|
||||
|
||||
// makeCurrent creates a new environment for the current cycle.
|
||||
func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error {
|
||||
state, err := state.New(parent.Root(), self.eth.ChainDb())
|
||||
state, err := self.chain.StateAt(parent.Root())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -514,12 +514,32 @@ func TestAPIGather(t *testing.T) {
|
||||
}{
|
||||
"Zero APIs": {[]rpc.API{}, InstrumentedServiceMakerA},
|
||||
"Single API": {[]rpc.API{
|
||||
{"single", "1", &OneMethodApi{fun: func() { calls <- "single.v1" }}, true},
|
||||
{
|
||||
Namespace: "single",
|
||||
Version: "1",
|
||||
Service: &OneMethodApi{fun: func() { calls <- "single.v1" }},
|
||||
Public: true,
|
||||
},
|
||||
}, InstrumentedServiceMakerB},
|
||||
"Many APIs": {[]rpc.API{
|
||||
{"multi", "1", &OneMethodApi{fun: func() { calls <- "multi.v1" }}, true},
|
||||
{"multi.v2", "2", &OneMethodApi{fun: func() { calls <- "multi.v2" }}, true},
|
||||
{"multi.v2.nested", "2", &OneMethodApi{fun: func() { calls <- "multi.v2.nested" }}, true},
|
||||
{
|
||||
Namespace: "multi",
|
||||
Version: "1",
|
||||
Service: &OneMethodApi{fun: func() { calls <- "multi.v1" }},
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
Namespace: "multi.v2",
|
||||
Version: "2",
|
||||
Service: &OneMethodApi{fun: func() { calls <- "multi.v2" }},
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
Namespace: "multi.v2.nested",
|
||||
Version: "2",
|
||||
Service: &OneMethodApi{fun: func() { calls <- "multi.v2.nested" }},
|
||||
Public: true,
|
||||
},
|
||||
}, InstrumentedServiceMakerC},
|
||||
}
|
||||
for id, config := range services {
|
||||
|
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
@@ -219,7 +220,7 @@ func (t *BlockTest) InsertPreState(db ethdb.Database) (*state.StateDB, error) {
|
||||
return nil, err
|
||||
}
|
||||
obj := statedb.CreateAccount(common.HexToAddress(addrString))
|
||||
obj.SetCode(code)
|
||||
obj.SetCode(crypto.Keccak256Hash(code), code)
|
||||
obj.SetBalance(balance)
|
||||
obj.SetNonce(nonce)
|
||||
for k, v := range acct.Storage {
|
||||
|
@@ -97,7 +97,7 @@ func benchStateTest(ruleSet RuleSet, test VmTest, env map[string]string, b *test
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, db)
|
||||
for addr, account := range test.Pre {
|
||||
obj := StateObjectFromAccount(db, addr, account)
|
||||
obj := StateObjectFromAccount(db, addr, account, statedb.MarkStateObjectDirty)
|
||||
statedb.SetStateObject(obj)
|
||||
for a, v := range account.Storage {
|
||||
obj.SetState(common.HexToHash(a), common.HexToHash(v))
|
||||
@@ -136,7 +136,7 @@ func runStateTest(ruleSet RuleSet, test VmTest) error {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, db)
|
||||
for addr, account := range test.Pre {
|
||||
obj := StateObjectFromAccount(db, addr, account)
|
||||
obj := StateObjectFromAccount(db, addr, account, statedb.MarkStateObjectDirty)
|
||||
statedb.SetStateObject(obj)
|
||||
for a, v := range account.Storage {
|
||||
obj.SetState(common.HexToHash(a), common.HexToHash(v))
|
||||
@@ -187,7 +187,7 @@ func runStateTest(ruleSet RuleSet, test VmTest) error {
|
||||
}
|
||||
|
||||
for addr, value := range account.Storage {
|
||||
v := obj.GetState(common.HexToHash(addr))
|
||||
v := statedb.GetState(obj.Address(), common.HexToHash(addr))
|
||||
vexp := common.HexToHash(value)
|
||||
|
||||
if v != vexp {
|
||||
|
@@ -103,16 +103,18 @@ func (self Log) Topics() [][]byte {
|
||||
return t
|
||||
}
|
||||
|
||||
func StateObjectFromAccount(db ethdb.Database, addr string, account Account) *state.StateObject {
|
||||
obj := state.NewStateObject(common.HexToAddress(addr), db)
|
||||
obj.SetBalance(common.Big(account.Balance))
|
||||
|
||||
func StateObjectFromAccount(db ethdb.Database, addr string, account Account, onDirty func(common.Address)) *state.StateObject {
|
||||
if common.IsHex(account.Code) {
|
||||
account.Code = account.Code[2:]
|
||||
}
|
||||
obj.SetCode(common.Hex2Bytes(account.Code))
|
||||
obj.SetNonce(common.Big(account.Nonce).Uint64())
|
||||
|
||||
code := common.Hex2Bytes(account.Code)
|
||||
codeHash := crypto.Keccak256Hash(code)
|
||||
obj := state.NewObject(common.HexToAddress(addr), state.Account{
|
||||
Balance: common.Big(account.Balance),
|
||||
CodeHash: codeHash[:],
|
||||
Nonce: common.Big(account.Nonce).Uint64(),
|
||||
}, onDirty)
|
||||
obj.SetCode(codeHash, code)
|
||||
return obj
|
||||
}
|
||||
|
||||
|
@@ -103,7 +103,7 @@ func benchVmTest(test VmTest, env map[string]string, b *testing.B) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, db)
|
||||
for addr, account := range test.Pre {
|
||||
obj := StateObjectFromAccount(db, addr, account)
|
||||
obj := StateObjectFromAccount(db, addr, account, statedb.MarkStateObjectDirty)
|
||||
statedb.SetStateObject(obj)
|
||||
for a, v := range account.Storage {
|
||||
obj.SetState(common.HexToHash(a), common.HexToHash(v))
|
||||
@@ -154,7 +154,7 @@ func runVmTest(test VmTest) error {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, db)
|
||||
for addr, account := range test.Pre {
|
||||
obj := StateObjectFromAccount(db, addr, account)
|
||||
obj := StateObjectFromAccount(db, addr, account, statedb.MarkStateObjectDirty)
|
||||
statedb.SetStateObject(obj)
|
||||
for a, v := range account.Storage {
|
||||
obj.SetState(common.HexToHash(a), common.HexToHash(v))
|
||||
@@ -205,11 +205,9 @@ func runVmTest(test VmTest) error {
|
||||
if obj == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for addr, value := range account.Storage {
|
||||
v := obj.GetState(common.HexToHash(addr))
|
||||
v := statedb.GetState(obj.Address(), common.HexToHash(addr))
|
||||
vexp := common.HexToHash(value)
|
||||
|
||||
if v != vexp {
|
||||
return fmt.Errorf("(%x: %s) storage failed. Expected %x, got %x (%v %v)\n", obj.Address().Bytes()[0:4], addr, vexp, v, vexp.Big(), v.Big())
|
||||
}
|
||||
|
206
trie/arc.go
206
trie/arc.go
@@ -1,206 +0,0 @@
|
||||
// Copyright (c) 2015 Hans Alexander Gugel <alexander.gugel@gmail.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// This file contains a modified version of package arc from
|
||||
// https://github.com/alexanderGugel/arc
|
||||
//
|
||||
// It implements the ARC (Adaptive Replacement Cache) algorithm as detailed in
|
||||
// https://www.usenix.org/legacy/event/fast03/tech/full_papers/megiddo/megiddo.pdf
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type arc struct {
|
||||
p int
|
||||
c int
|
||||
t1 *list.List
|
||||
b1 *list.List
|
||||
t2 *list.List
|
||||
b2 *list.List
|
||||
cache map[string]*entry
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
key hashNode
|
||||
value node
|
||||
ll *list.List
|
||||
el *list.Element
|
||||
}
|
||||
|
||||
// newARC returns a new Adaptive Replacement Cache with the
|
||||
// given capacity.
|
||||
func newARC(c int) *arc {
|
||||
return &arc{
|
||||
c: c,
|
||||
t1: list.New(),
|
||||
b1: list.New(),
|
||||
t2: list.New(),
|
||||
b2: list.New(),
|
||||
cache: make(map[string]*entry, c),
|
||||
}
|
||||
}
|
||||
|
||||
// Clear clears the cache
|
||||
func (a *arc) Clear() {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
a.p = 0
|
||||
a.t1 = list.New()
|
||||
a.b1 = list.New()
|
||||
a.t2 = list.New()
|
||||
a.b2 = list.New()
|
||||
a.cache = make(map[string]*entry, a.c)
|
||||
}
|
||||
|
||||
// Put inserts a new key-value pair into the cache.
|
||||
// This optimizes future access to this entry (side effect).
|
||||
func (a *arc) Put(key hashNode, value node) bool {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
ent, ok := a.cache[string(key)]
|
||||
if ok != true {
|
||||
ent = &entry{key: key, value: value}
|
||||
a.req(ent)
|
||||
a.cache[string(key)] = ent
|
||||
} else {
|
||||
ent.value = value
|
||||
a.req(ent)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get retrieves a previously via Set inserted entry.
|
||||
// This optimizes future access to this entry (side effect).
|
||||
func (a *arc) Get(key hashNode) (value node, ok bool) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
ent, ok := a.cache[string(key)]
|
||||
if ok {
|
||||
a.req(ent)
|
||||
return ent.value, ent.value != nil
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (a *arc) req(ent *entry) {
|
||||
if ent.ll == a.t1 || ent.ll == a.t2 {
|
||||
// Case I
|
||||
ent.setMRU(a.t2)
|
||||
} else if ent.ll == a.b1 {
|
||||
// Case II
|
||||
// Cache Miss in t1 and t2
|
||||
|
||||
// Adaptation
|
||||
var d int
|
||||
if a.b1.Len() >= a.b2.Len() {
|
||||
d = 1
|
||||
} else {
|
||||
d = a.b2.Len() / a.b1.Len()
|
||||
}
|
||||
a.p = a.p + d
|
||||
if a.p > a.c {
|
||||
a.p = a.c
|
||||
}
|
||||
|
||||
a.replace(ent)
|
||||
ent.setMRU(a.t2)
|
||||
} else if ent.ll == a.b2 {
|
||||
// Case III
|
||||
// Cache Miss in t1 and t2
|
||||
|
||||
// Adaptation
|
||||
var d int
|
||||
if a.b2.Len() >= a.b1.Len() {
|
||||
d = 1
|
||||
} else {
|
||||
d = a.b1.Len() / a.b2.Len()
|
||||
}
|
||||
a.p = a.p - d
|
||||
if a.p < 0 {
|
||||
a.p = 0
|
||||
}
|
||||
|
||||
a.replace(ent)
|
||||
ent.setMRU(a.t2)
|
||||
} else if ent.ll == nil {
|
||||
// Case IV
|
||||
|
||||
if a.t1.Len()+a.b1.Len() == a.c {
|
||||
// Case A
|
||||
if a.t1.Len() < a.c {
|
||||
a.delLRU(a.b1)
|
||||
a.replace(ent)
|
||||
} else {
|
||||
a.delLRU(a.t1)
|
||||
}
|
||||
} else if a.t1.Len()+a.b1.Len() < a.c {
|
||||
// Case B
|
||||
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c {
|
||||
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c {
|
||||
a.delLRU(a.b2)
|
||||
}
|
||||
a.replace(ent)
|
||||
}
|
||||
}
|
||||
|
||||
ent.setMRU(a.t1)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *arc) delLRU(list *list.List) {
|
||||
lru := list.Back()
|
||||
list.Remove(lru)
|
||||
delete(a.cache, string(lru.Value.(*entry).key))
|
||||
}
|
||||
|
||||
func (a *arc) replace(ent *entry) {
|
||||
if a.t1.Len() > 0 && ((a.t1.Len() > a.p) || (ent.ll == a.b2 && a.t1.Len() == a.p)) {
|
||||
lru := a.t1.Back().Value.(*entry)
|
||||
lru.value = nil
|
||||
lru.setMRU(a.b1)
|
||||
} else {
|
||||
lru := a.t2.Back().Value.(*entry)
|
||||
lru.value = nil
|
||||
lru.setMRU(a.b2)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *entry) setLRU(list *list.List) {
|
||||
e.detach()
|
||||
e.ll = list
|
||||
e.el = e.ll.PushBack(e)
|
||||
}
|
||||
|
||||
func (e *entry) setMRU(list *list.List) {
|
||||
e.detach()
|
||||
e.ll = list
|
||||
e.el = e.ll.PushFront(e)
|
||||
}
|
||||
|
||||
func (e *entry) detach() {
|
||||
if e.ll != nil {
|
||||
e.ll.Remove(e.el)
|
||||
}
|
||||
}
|
157
trie/hasher.go
Normal file
157
trie/hasher.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// 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 trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
type hasher struct {
|
||||
tmp *bytes.Buffer
|
||||
sha hash.Hash
|
||||
}
|
||||
|
||||
// hashers live in a global pool.
|
||||
var hasherPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &hasher{tmp: new(bytes.Buffer), sha: sha3.NewKeccak256()}
|
||||
},
|
||||
}
|
||||
|
||||
func newHasher() *hasher {
|
||||
return hasherPool.Get().(*hasher)
|
||||
}
|
||||
|
||||
func returnHasherToPool(h *hasher) {
|
||||
hasherPool.Put(h)
|
||||
}
|
||||
|
||||
// hash collapses a node down into a hash node, also returning a copy of the
|
||||
// original node initialzied with the computed hash to replace the original one.
|
||||
func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) {
|
||||
// If we're not storing the node, just hashing, use avaialble cached data
|
||||
if hash, dirty := n.cache(); hash != nil && (db == nil || !dirty) {
|
||||
return hash, n, nil
|
||||
}
|
||||
// Trie not processed yet or needs storage, walk the children
|
||||
collapsed, cached, err := h.hashChildren(n, db)
|
||||
if err != nil {
|
||||
return hashNode{}, n, err
|
||||
}
|
||||
hashed, err := h.store(collapsed, db, force)
|
||||
if err != nil {
|
||||
return hashNode{}, n, err
|
||||
}
|
||||
// Cache the hash and RLP blob of the ndoe for later reuse
|
||||
if hash, ok := hashed.(hashNode); ok && !force {
|
||||
switch cached := cached.(type) {
|
||||
case shortNode:
|
||||
cached.hash = hash
|
||||
if db != nil {
|
||||
cached.dirty = false
|
||||
}
|
||||
return hashed, cached, nil
|
||||
case fullNode:
|
||||
cached.hash = hash
|
||||
if db != nil {
|
||||
cached.dirty = false
|
||||
}
|
||||
return hashed, cached, nil
|
||||
}
|
||||
}
|
||||
return hashed, cached, nil
|
||||
}
|
||||
|
||||
// hashChildren replaces the children of a node with their hashes if the encoded
|
||||
// size of the child is larger than a hash, returning the collapsed node as well
|
||||
// as a replacement for the original node with the child hashes cached in.
|
||||
func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, error) {
|
||||
var err error
|
||||
|
||||
switch n := original.(type) {
|
||||
case shortNode:
|
||||
// Hash the short node's child, caching the newly hashed subtree
|
||||
cached := n
|
||||
cached.Key = common.CopyBytes(cached.Key)
|
||||
|
||||
n.Key = compactEncode(n.Key)
|
||||
if _, ok := n.Val.(valueNode); !ok {
|
||||
if n.Val, cached.Val, err = h.hash(n.Val, db, false); err != nil {
|
||||
return n, original, err
|
||||
}
|
||||
}
|
||||
if n.Val == nil {
|
||||
n.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings.
|
||||
}
|
||||
return n, cached, nil
|
||||
|
||||
case fullNode:
|
||||
// Hash the full node's children, caching the newly hashed subtrees
|
||||
cached := fullNode{dirty: n.dirty}
|
||||
|
||||
for i := 0; i < 16; i++ {
|
||||
if n.Children[i] != nil {
|
||||
if n.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false); err != nil {
|
||||
return n, original, err
|
||||
}
|
||||
} else {
|
||||
n.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings.
|
||||
}
|
||||
}
|
||||
cached.Children[16] = n.Children[16]
|
||||
if n.Children[16] == nil {
|
||||
n.Children[16] = valueNode(nil)
|
||||
}
|
||||
return n, cached, nil
|
||||
|
||||
default:
|
||||
// Value and hash nodes don't have children so they're left as were
|
||||
return n, original, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hasher) store(n node, db DatabaseWriter, force bool) (node, error) {
|
||||
// Don't store hashes or empty nodes.
|
||||
if _, isHash := n.(hashNode); n == nil || isHash {
|
||||
return n, nil
|
||||
}
|
||||
// Generate the RLP encoding of the node
|
||||
h.tmp.Reset()
|
||||
if err := rlp.Encode(h.tmp, n); err != nil {
|
||||
panic("encode error: " + err.Error())
|
||||
}
|
||||
if h.tmp.Len() < 32 && !force {
|
||||
return n, nil // Nodes smaller than 32 bytes are stored inside their parent
|
||||
}
|
||||
// Larger nodes are replaced by their hash and stored in the database.
|
||||
hash, _ := n.cache()
|
||||
if hash == nil {
|
||||
h.sha.Reset()
|
||||
h.sha.Write(h.tmp.Bytes())
|
||||
hash = hashNode(h.sha.Sum(nil))
|
||||
}
|
||||
if db != nil {
|
||||
return hash, db.Put(hash, h.tmp.Bytes())
|
||||
}
|
||||
return hash, nil
|
||||
}
|
190
trie/iterator.go
190
trie/iterator.go
@@ -16,18 +16,13 @@
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// Iterator is a key-value trie iterator to traverse the data contents.
|
||||
// Iterator is a key-value trie iterator that traverses a Trie.
|
||||
type Iterator struct {
|
||||
trie *Trie
|
||||
trie *Trie
|
||||
nodeIt *NodeIterator
|
||||
keyBuf []byte
|
||||
|
||||
Key []byte // Current data key on which the iterator is positioned on
|
||||
Value []byte // Current data value on which the iterator is positioned on
|
||||
@@ -35,119 +30,45 @@ type Iterator struct {
|
||||
|
||||
// NewIterator creates a new key-value iterator.
|
||||
func NewIterator(trie *Trie) *Iterator {
|
||||
return &Iterator{trie: trie, Key: nil}
|
||||
return &Iterator{
|
||||
trie: trie,
|
||||
nodeIt: NewNodeIterator(trie),
|
||||
keyBuf: make([]byte, 0, 64),
|
||||
Key: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Next moves the iterator forward with one key-value entry.
|
||||
func (self *Iterator) Next() bool {
|
||||
isIterStart := false
|
||||
if self.Key == nil {
|
||||
isIterStart = true
|
||||
self.Key = make([]byte, 32)
|
||||
// Next moves the iterator forward one key-value entry.
|
||||
func (it *Iterator) Next() bool {
|
||||
for it.nodeIt.Next() {
|
||||
if it.nodeIt.Leaf {
|
||||
it.Key = it.makeKey()
|
||||
it.Value = it.nodeIt.LeafBlob
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
key := remTerm(compactHexDecode(self.Key))
|
||||
k := self.next(self.trie.root, key, isIterStart)
|
||||
|
||||
self.Key = []byte(decodeCompact(k))
|
||||
|
||||
return len(k) > 0
|
||||
it.Key = nil
|
||||
it.Value = nil
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Iterator) next(node interface{}, key []byte, isIterStart bool) []byte {
|
||||
if node == nil {
|
||||
return nil
|
||||
func (it *Iterator) makeKey() []byte {
|
||||
key := it.keyBuf[:0]
|
||||
for _, se := range it.nodeIt.stack {
|
||||
switch node := se.node.(type) {
|
||||
case fullNode:
|
||||
if se.child <= 16 {
|
||||
key = append(key, byte(se.child))
|
||||
}
|
||||
case shortNode:
|
||||
if hasTerm(node.Key) {
|
||||
key = append(key, node.Key[:len(node.Key)-1]...)
|
||||
} else {
|
||||
key = append(key, node.Key...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch node := node.(type) {
|
||||
case fullNode:
|
||||
if len(key) > 0 {
|
||||
k := self.next(node.Children[key[0]], key[1:], isIterStart)
|
||||
if k != nil {
|
||||
return append([]byte{key[0]}, k...)
|
||||
}
|
||||
}
|
||||
|
||||
var r byte
|
||||
if len(key) > 0 {
|
||||
r = key[0] + 1
|
||||
}
|
||||
|
||||
for i := r; i < 16; i++ {
|
||||
k := self.key(node.Children[i])
|
||||
if k != nil {
|
||||
return append([]byte{i}, k...)
|
||||
}
|
||||
}
|
||||
|
||||
case shortNode:
|
||||
k := remTerm(node.Key)
|
||||
if vnode, ok := node.Val.(valueNode); ok {
|
||||
switch bytes.Compare([]byte(k), key) {
|
||||
case 0:
|
||||
if isIterStart {
|
||||
self.Value = vnode
|
||||
return k
|
||||
}
|
||||
case 1:
|
||||
self.Value = vnode
|
||||
return k
|
||||
}
|
||||
} else {
|
||||
cnode := node.Val
|
||||
|
||||
var ret []byte
|
||||
skey := key[len(k):]
|
||||
if bytes.HasPrefix(key, k) {
|
||||
ret = self.next(cnode, skey, isIterStart)
|
||||
} else if bytes.Compare(k, key[:len(k)]) > 0 {
|
||||
return self.key(node)
|
||||
}
|
||||
|
||||
if ret != nil {
|
||||
return append(k, ret...)
|
||||
}
|
||||
}
|
||||
|
||||
case hashNode:
|
||||
rn, err := self.trie.resolveHash(node, nil, nil)
|
||||
if err != nil && glog.V(logger.Error) {
|
||||
glog.Errorf("Unhandled trie error: %v", err)
|
||||
}
|
||||
return self.next(rn, key, isIterStart)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Iterator) key(node interface{}) []byte {
|
||||
switch node := node.(type) {
|
||||
case shortNode:
|
||||
// Leaf node
|
||||
k := remTerm(node.Key)
|
||||
if vnode, ok := node.Val.(valueNode); ok {
|
||||
self.Value = vnode
|
||||
return k
|
||||
}
|
||||
return append(k, self.key(node.Val)...)
|
||||
case fullNode:
|
||||
if node.Children[16] != nil {
|
||||
self.Value = node.Children[16].(valueNode)
|
||||
return []byte{16}
|
||||
}
|
||||
for i := 0; i < 16; i++ {
|
||||
k := self.key(node.Children[i])
|
||||
if k != nil {
|
||||
return append([]byte{byte(i)}, k...)
|
||||
}
|
||||
}
|
||||
case hashNode:
|
||||
rn, err := self.trie.resolveHash(node, nil, nil)
|
||||
if err != nil && glog.V(logger.Error) {
|
||||
glog.Errorf("Unhandled trie error: %v", err)
|
||||
}
|
||||
return self.key(rn)
|
||||
}
|
||||
return nil
|
||||
return decodeCompact(key)
|
||||
}
|
||||
|
||||
// nodeIteratorState represents the iteration state at one particular node of the
|
||||
@@ -199,25 +120,27 @@ func (it *NodeIterator) Next() bool {
|
||||
|
||||
// step moves the iterator to the next node of the trie.
|
||||
func (it *NodeIterator) step() error {
|
||||
// Abort if we reached the end of the iteration
|
||||
if it.trie == nil {
|
||||
// Abort if we reached the end of the iteration
|
||||
return nil
|
||||
}
|
||||
// Initialize the iterator if we've just started, or pop off the old node otherwise
|
||||
if len(it.stack) == 0 {
|
||||
// Always start with a collapsed root
|
||||
// Initialize the iterator if we've just started.
|
||||
root := it.trie.Hash()
|
||||
it.stack = append(it.stack, &nodeIteratorState{node: hashNode(root[:]), child: -1})
|
||||
if it.stack[0].node == nil {
|
||||
return fmt.Errorf("root node missing: %x", it.trie.Hash())
|
||||
state := &nodeIteratorState{node: it.trie.root, child: -1}
|
||||
if root != emptyRoot {
|
||||
state.hash = root
|
||||
}
|
||||
it.stack = append(it.stack, state)
|
||||
} else {
|
||||
// Continue iterating at the previous node otherwise.
|
||||
it.stack = it.stack[:len(it.stack)-1]
|
||||
if len(it.stack) == 0 {
|
||||
it.trie = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Continue iteration to the next child
|
||||
for {
|
||||
parent := it.stack[len(it.stack)-1]
|
||||
@@ -232,7 +155,12 @@ func (it *NodeIterator) step() error {
|
||||
}
|
||||
for parent.child++; parent.child < len(node.Children); parent.child++ {
|
||||
if current := node.Children[parent.child]; current != nil {
|
||||
it.stack = append(it.stack, &nodeIteratorState{node: current, parent: ancestor, child: -1})
|
||||
it.stack = append(it.stack, &nodeIteratorState{
|
||||
hash: common.BytesToHash(node.hash),
|
||||
node: current,
|
||||
parent: ancestor,
|
||||
child: -1,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -242,7 +170,12 @@ func (it *NodeIterator) step() error {
|
||||
break
|
||||
}
|
||||
parent.child++
|
||||
it.stack = append(it.stack, &nodeIteratorState{node: node.Val, parent: ancestor, child: -1})
|
||||
it.stack = append(it.stack, &nodeIteratorState{
|
||||
hash: common.BytesToHash(node.hash),
|
||||
node: node.Val,
|
||||
parent: ancestor,
|
||||
child: -1,
|
||||
})
|
||||
} else if hash, ok := parent.node.(hashNode); ok {
|
||||
// Hash node, resolve the hash child from the database, then the node itself
|
||||
if parent.child >= 0 {
|
||||
@@ -254,7 +187,12 @@ func (it *NodeIterator) step() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.stack = append(it.stack, &nodeIteratorState{hash: common.BytesToHash(hash), node: node, parent: ancestor, child: -1})
|
||||
it.stack = append(it.stack, &nodeIteratorState{
|
||||
hash: common.BytesToHash(hash),
|
||||
node: node,
|
||||
parent: ancestor,
|
||||
child: -1,
|
||||
})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
@@ -34,21 +34,60 @@ func TestIterator(t *testing.T) {
|
||||
{"dog", "puppy"},
|
||||
{"somethingveryoddindeedthis is", "myothernodedata"},
|
||||
}
|
||||
v := make(map[string]bool)
|
||||
all := make(map[string]string)
|
||||
for _, val := range vals {
|
||||
v[val.k] = false
|
||||
all[val.k] = val.v
|
||||
trie.Update([]byte(val.k), []byte(val.v))
|
||||
}
|
||||
trie.Commit()
|
||||
|
||||
found := make(map[string]string)
|
||||
it := NewIterator(trie)
|
||||
for it.Next() {
|
||||
v[string(it.Key)] = true
|
||||
found[string(it.Key)] = string(it.Value)
|
||||
}
|
||||
|
||||
for k, found := range v {
|
||||
if !found {
|
||||
t.Error("iterator didn't find", k)
|
||||
for k, v := range all {
|
||||
if found[k] != v {
|
||||
t.Errorf("iterator value mismatch for %s: got %q want %q", k, found[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k, v []byte
|
||||
t bool
|
||||
}
|
||||
|
||||
func TestIteratorLargeData(t *testing.T) {
|
||||
trie := newEmpty()
|
||||
vals := make(map[string]*kv)
|
||||
|
||||
for i := byte(0); i < 255; i++ {
|
||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||
value2 := &kv{common.LeftPadBytes([]byte{10, i}, 32), []byte{i}, false}
|
||||
trie.Update(value.k, value.v)
|
||||
trie.Update(value2.k, value2.v)
|
||||
vals[string(value.k)] = value
|
||||
vals[string(value2.k)] = value2
|
||||
}
|
||||
|
||||
it := NewIterator(trie)
|
||||
for it.Next() {
|
||||
vals[string(it.Key)].t = true
|
||||
}
|
||||
|
||||
var untouched []*kv
|
||||
for _, value := range vals {
|
||||
if !value.t {
|
||||
untouched = append(untouched, value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(untouched) > 0 {
|
||||
t.Errorf("Missed %d nodes", len(untouched))
|
||||
for _, value := range untouched {
|
||||
t.Error(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -70,15 +70,13 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue {
|
||||
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
|
||||
}
|
||||
}
|
||||
if t.hasher == nil {
|
||||
t.hasher = newHasher()
|
||||
}
|
||||
hasher := newHasher()
|
||||
proof := make([]rlp.RawValue, 0, len(nodes))
|
||||
for i, n := range nodes {
|
||||
// Don't bother checking for errors here since hasher panics
|
||||
// if encoding doesn't work and we're not writing to any database.
|
||||
n, _, _ = t.hasher.hashChildren(n, nil)
|
||||
hn, _ := t.hasher.store(n, nil, false)
|
||||
n, _, _ = hasher.hashChildren(n, nil)
|
||||
hn, _ := hasher.store(n, nil, false)
|
||||
if _, ok := hn.(hashNode); ok || i == 0 {
|
||||
// If the node's database encoding is a hash (or is the
|
||||
// root node), it becomes a proof element.
|
||||
|
@@ -17,16 +17,15 @@
|
||||
package trie
|
||||
|
||||
import (
|
||||
"hash"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
var secureKeyPrefix = []byte("secure-key-")
|
||||
|
||||
const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash
|
||||
|
||||
// SecureTrie wraps a trie with key hashing. In a secure trie, all
|
||||
// access operations hash the key using keccak256. This prevents
|
||||
// calling code from creating long chains of nodes that
|
||||
@@ -38,12 +37,11 @@ var secureKeyPrefix = []byte("secure-key-")
|
||||
//
|
||||
// SecureTrie is not safe for concurrent use.
|
||||
type SecureTrie struct {
|
||||
*Trie
|
||||
|
||||
hash hash.Hash
|
||||
hashKeyBuf []byte
|
||||
secKeyBuf []byte
|
||||
secKeyCache map[string][]byte
|
||||
trie Trie
|
||||
hashKeyBuf [secureKeyLength]byte
|
||||
secKeyBuf [200]byte
|
||||
secKeyCache map[string][]byte
|
||||
secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch
|
||||
}
|
||||
|
||||
// NewSecure creates a trie with an existing root node from db.
|
||||
@@ -61,8 +59,7 @@ func NewSecure(root common.Hash, db Database) (*SecureTrie, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &SecureTrie{
|
||||
Trie: trie,
|
||||
secKeyCache: make(map[string][]byte),
|
||||
trie: *trie,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -80,7 +77,7 @@ func (t *SecureTrie) Get(key []byte) []byte {
|
||||
// The value bytes must not be modified by the caller.
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *SecureTrie) TryGet(key []byte) ([]byte, error) {
|
||||
return t.Trie.TryGet(t.hashKey(key))
|
||||
return t.trie.TryGet(t.hashKey(key))
|
||||
}
|
||||
|
||||
// Update associates key with value in the trie. Subsequent calls to
|
||||
@@ -105,11 +102,11 @@ func (t *SecureTrie) Update(key, value []byte) {
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *SecureTrie) TryUpdate(key, value []byte) error {
|
||||
hk := t.hashKey(key)
|
||||
err := t.Trie.TryUpdate(hk, value)
|
||||
err := t.trie.TryUpdate(hk, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.secKeyCache[string(hk)] = common.CopyBytes(key)
|
||||
t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -124,17 +121,17 @@ func (t *SecureTrie) Delete(key []byte) {
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *SecureTrie) TryDelete(key []byte) error {
|
||||
hk := t.hashKey(key)
|
||||
delete(t.secKeyCache, string(hk))
|
||||
return t.Trie.TryDelete(hk)
|
||||
delete(t.getSecKeyCache(), string(hk))
|
||||
return t.trie.TryDelete(hk)
|
||||
}
|
||||
|
||||
// GetKey returns the sha3 preimage of a hashed key that was
|
||||
// previously used to store a value.
|
||||
func (t *SecureTrie) GetKey(shaKey []byte) []byte {
|
||||
if key, ok := t.secKeyCache[string(shaKey)]; ok {
|
||||
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
|
||||
return key
|
||||
}
|
||||
key, _ := t.Trie.db.Get(t.secKey(shaKey))
|
||||
key, _ := t.trie.db.Get(t.secKey(shaKey))
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -144,7 +141,23 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
|
||||
// Committing flushes nodes from memory. Subsequent Get calls will load nodes
|
||||
// from the database.
|
||||
func (t *SecureTrie) Commit() (root common.Hash, err error) {
|
||||
return t.CommitTo(t.db)
|
||||
return t.CommitTo(t.trie.db)
|
||||
}
|
||||
|
||||
func (t *SecureTrie) Hash() common.Hash {
|
||||
return t.trie.Hash()
|
||||
}
|
||||
|
||||
func (t *SecureTrie) Root() []byte {
|
||||
return t.trie.Root()
|
||||
}
|
||||
|
||||
func (t *SecureTrie) Iterator() *Iterator {
|
||||
return t.trie.Iterator()
|
||||
}
|
||||
|
||||
func (t *SecureTrie) NodeIterator() *NodeIterator {
|
||||
return NewNodeIterator(&t.trie)
|
||||
}
|
||||
|
||||
// CommitTo writes all nodes and the secure hash pre-images to the given database.
|
||||
@@ -154,7 +167,7 @@ func (t *SecureTrie) Commit() (root common.Hash, err error) {
|
||||
// the trie's database. Calling code must ensure that the changes made to db are
|
||||
// written back to the trie's attached database before using the trie.
|
||||
func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
|
||||
if len(t.secKeyCache) > 0 {
|
||||
if len(t.getSecKeyCache()) > 0 {
|
||||
for hk, key := range t.secKeyCache {
|
||||
if err := db.Put(t.secKey([]byte(hk)), key); err != nil {
|
||||
return common.Hash{}, err
|
||||
@@ -162,27 +175,37 @@ func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
|
||||
}
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
n, clean, err := t.hashRoot(db)
|
||||
if err != nil {
|
||||
return (common.Hash{}), err
|
||||
}
|
||||
t.root = clean
|
||||
return common.BytesToHash(n.(hashNode)), nil
|
||||
return t.trie.CommitTo(db)
|
||||
}
|
||||
|
||||
// secKey returns the database key for the preimage of key, as an ephemeral buffer.
|
||||
// The caller must not hold onto the return value because it will become
|
||||
// invalid on the next call to hashKey or secKey.
|
||||
func (t *SecureTrie) secKey(key []byte) []byte {
|
||||
t.secKeyBuf = append(t.secKeyBuf[:0], secureKeyPrefix...)
|
||||
t.secKeyBuf = append(t.secKeyBuf, key...)
|
||||
return t.secKeyBuf
|
||||
buf := append(t.secKeyBuf[:0], secureKeyPrefix...)
|
||||
buf = append(buf, key...)
|
||||
return buf
|
||||
}
|
||||
|
||||
// hashKey returns the hash of key as an ephemeral buffer.
|
||||
// The caller must not hold onto the return value because it will become
|
||||
// invalid on the next call to hashKey or secKey.
|
||||
func (t *SecureTrie) hashKey(key []byte) []byte {
|
||||
if t.hash == nil {
|
||||
t.hash = sha3.NewKeccak256()
|
||||
t.hashKeyBuf = make([]byte, 32)
|
||||
}
|
||||
t.hash.Reset()
|
||||
t.hash.Write(key)
|
||||
t.hashKeyBuf = t.hash.Sum(t.hashKeyBuf[:0])
|
||||
return t.hashKeyBuf
|
||||
h := newHasher()
|
||||
h.sha.Reset()
|
||||
h.sha.Write(key)
|
||||
buf := h.sha.Sum(t.hashKeyBuf[:0])
|
||||
returnHasherToPool(h)
|
||||
return buf
|
||||
}
|
||||
|
||||
// getSecKeyCache returns the current secure key cache, creating a new one if
|
||||
// ownership changed (i.e. the current secure trie is a copy of another owning
|
||||
// the actual cache).
|
||||
func (t *SecureTrie) getSecKeyCache() map[string][]byte {
|
||||
if t != t.secKeyCacheOwner {
|
||||
t.secKeyCacheOwner = t
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
return t.secKeyCache
|
||||
}
|
||||
|
@@ -18,6 +18,8 @@ package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -31,6 +33,37 @@ func newEmptySecure() *SecureTrie {
|
||||
return trie
|
||||
}
|
||||
|
||||
// makeTestSecureTrie creates a large enough secure trie for testing.
|
||||
func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) {
|
||||
// Create an empty trie
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
trie, _ := NewSecure(common.Hash{}, db)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
content := make(map[string][]byte)
|
||||
for i := byte(0); i < 255; i++ {
|
||||
// Map the same data under multiple keys
|
||||
key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i}
|
||||
content[string(key)] = val
|
||||
trie.Update(key, val)
|
||||
|
||||
key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i}
|
||||
content[string(key)] = val
|
||||
trie.Update(key, val)
|
||||
|
||||
// Add some other data to inflate th trie
|
||||
for j := byte(3); j < 13; j++ {
|
||||
key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i}
|
||||
content[string(key)] = val
|
||||
trie.Update(key, val)
|
||||
}
|
||||
}
|
||||
trie.Commit()
|
||||
|
||||
// Return the generated trie
|
||||
return db, trie, content
|
||||
}
|
||||
|
||||
func TestSecureDelete(t *testing.T) {
|
||||
trie := newEmptySecure()
|
||||
vals := []struct{ k, v string }{
|
||||
@@ -72,3 +105,41 @@ func TestSecureGetKey(t *testing.T) {
|
||||
t.Errorf("GetKey returned %q, want %q", k, key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecureTrieConcurrency(t *testing.T) {
|
||||
// Create an initial trie and copy if for concurrent access
|
||||
_, trie, _ := makeTestSecureTrie()
|
||||
|
||||
threads := runtime.NumCPU()
|
||||
tries := make([]*SecureTrie, threads)
|
||||
for i := 0; i < threads; i++ {
|
||||
cpy := *trie
|
||||
tries[i] = &cpy
|
||||
}
|
||||
// Start a batch of goroutines interactng with the trie
|
||||
pend := new(sync.WaitGroup)
|
||||
pend.Add(threads)
|
||||
for i := 0; i < threads; i++ {
|
||||
go func(index int) {
|
||||
defer pend.Done()
|
||||
|
||||
for j := byte(0); j < 255; j++ {
|
||||
// Map the same data under multiple keys
|
||||
key, val := common.LeftPadBytes([]byte{byte(index), 1, j}, 32), []byte{j}
|
||||
tries[index].Update(key, val)
|
||||
|
||||
key, val = common.LeftPadBytes([]byte{byte(index), 2, j}, 32), []byte{j}
|
||||
tries[index].Update(key, val)
|
||||
|
||||
// Add some other data to inflate the trie
|
||||
for k := byte(3); k < 13; k++ {
|
||||
key, val = common.LeftPadBytes([]byte{byte(index), k, j}, 32), []byte{k, j}
|
||||
tries[index].Update(key, val)
|
||||
}
|
||||
}
|
||||
tries[index].Commit()
|
||||
}(i)
|
||||
}
|
||||
// Wait for all threads to finish
|
||||
pend.Wait()
|
||||
}
|
||||
|
@@ -51,9 +51,6 @@ func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) {
|
||||
}
|
||||
trie.Commit()
|
||||
|
||||
// Remove any potentially cached data from the test trie creation
|
||||
globalCache.Clear()
|
||||
|
||||
// Return the generated trie
|
||||
return db, trie, content
|
||||
}
|
||||
@@ -61,9 +58,6 @@ func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) {
|
||||
// checkTrieContents cross references a reconstructed trie with an expected data
|
||||
// content map.
|
||||
func checkTrieContents(t *testing.T, db Database, root []byte, content map[string][]byte) {
|
||||
// Remove any potentially cached data from the trie synchronisation
|
||||
globalCache.Clear()
|
||||
|
||||
// Check root availability and trie contents
|
||||
trie, err := New(common.BytesToHash(root), db)
|
||||
if err != nil {
|
||||
@@ -81,9 +75,6 @@ func checkTrieContents(t *testing.T, db Database, root []byte, content map[strin
|
||||
|
||||
// checkTrieConsistency checks that all nodes in a trie are indeed present.
|
||||
func checkTrieConsistency(db Database, root common.Hash) error {
|
||||
// Remove any potentially cached data from the test trie creation or previous checks
|
||||
globalCache.Clear()
|
||||
|
||||
// Create and iterate a trie rooted in a subnode
|
||||
trie, err := New(root, db)
|
||||
if err != nil {
|
||||
|
217
trie/trie.go
217
trie/trie.go
@@ -20,22 +20,14 @@ package trie
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const defaultCacheCapacity = 800
|
||||
|
||||
var (
|
||||
// The global cache stores decoded trie nodes by hash as they get loaded.
|
||||
globalCache = newARC(defaultCacheCapacity)
|
||||
|
||||
// This is the known root hash of an empty trie.
|
||||
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
|
||||
@@ -43,11 +35,6 @@ var (
|
||||
emptyState = crypto.Keccak256Hash(nil)
|
||||
)
|
||||
|
||||
// ClearGlobalCache clears the global trie cache
|
||||
func ClearGlobalCache() {
|
||||
globalCache.Clear()
|
||||
}
|
||||
|
||||
// Database must be implemented by backing stores for the trie.
|
||||
type Database interface {
|
||||
DatabaseWriter
|
||||
@@ -72,7 +59,6 @@ type Trie struct {
|
||||
root node
|
||||
db Database
|
||||
originalRoot common.Hash
|
||||
*hasher
|
||||
}
|
||||
|
||||
// New creates a trie with an existing root node from db.
|
||||
@@ -118,32 +104,50 @@ func (t *Trie) Get(key []byte) []byte {
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *Trie) TryGet(key []byte) ([]byte, error) {
|
||||
key = compactHexDecode(key)
|
||||
pos := 0
|
||||
tn := t.root
|
||||
for pos < len(key) {
|
||||
switch n := tn.(type) {
|
||||
case shortNode:
|
||||
if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) {
|
||||
return nil, nil
|
||||
}
|
||||
tn = n.Val
|
||||
pos += len(n.Key)
|
||||
case fullNode:
|
||||
tn = n.Children[key[pos]]
|
||||
pos++
|
||||
case nil:
|
||||
return nil, nil
|
||||
case hashNode:
|
||||
var err error
|
||||
tn, err = t.resolveHash(n, key[:pos], key[pos:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
|
||||
}
|
||||
value, newroot, didResolve, err := t.tryGet(t.root, key, 0)
|
||||
if err == nil && didResolve {
|
||||
t.root = newroot
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) {
|
||||
switch n := (origNode).(type) {
|
||||
case nil:
|
||||
return nil, nil, false, nil
|
||||
case valueNode:
|
||||
return n, n, false, nil
|
||||
case shortNode:
|
||||
if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) {
|
||||
// key not found in trie
|
||||
return nil, n, false, nil
|
||||
}
|
||||
value, newnode, didResolve, err = t.tryGet(n.Val, key, pos+len(n.Key))
|
||||
if err == nil && didResolve {
|
||||
n.Val = newnode
|
||||
return value, n, didResolve, err
|
||||
} else {
|
||||
return value, origNode, didResolve, err
|
||||
}
|
||||
case fullNode:
|
||||
child := n.Children[key[pos]]
|
||||
value, newnode, didResolve, err = t.tryGet(child, key, pos+1)
|
||||
if err == nil && didResolve {
|
||||
n.Children[key[pos]] = newnode
|
||||
return value, n, didResolve, err
|
||||
} else {
|
||||
return value, origNode, didResolve, err
|
||||
}
|
||||
case hashNode:
|
||||
child, err := t.resolveHash(n, key[:pos], key[pos:])
|
||||
if err != nil {
|
||||
return nil, n, true, err
|
||||
}
|
||||
value, newnode, _, err := t.tryGet(child, key, pos)
|
||||
return value, newnode, true, err
|
||||
default:
|
||||
panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode))
|
||||
}
|
||||
return tn.(valueNode), nil
|
||||
}
|
||||
|
||||
// Update associates key with value in the trie. Subsequent calls to
|
||||
@@ -370,6 +374,9 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||
// n still contains at least two values and cannot be reduced.
|
||||
return true, n, nil
|
||||
|
||||
case valueNode:
|
||||
return true, nil, nil
|
||||
|
||||
case nil:
|
||||
return false, nil, nil
|
||||
|
||||
@@ -410,9 +417,6 @@ func (t *Trie) resolve(n node, prefix, suffix []byte) (node, error) {
|
||||
}
|
||||
|
||||
func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) {
|
||||
if v, ok := globalCache.Get(n); ok {
|
||||
return v, nil
|
||||
}
|
||||
enc, err := t.db.Get(n)
|
||||
if err != nil || enc == nil {
|
||||
return nil, &MissingNodeError{
|
||||
@@ -424,9 +428,6 @@ func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) {
|
||||
}
|
||||
}
|
||||
dec := mustDecodeNode(n, enc)
|
||||
if dec != nil {
|
||||
globalCache.Put(n, dec)
|
||||
}
|
||||
return dec, nil
|
||||
}
|
||||
|
||||
@@ -474,127 +475,7 @@ func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) {
|
||||
if t.root == nil {
|
||||
return hashNode(emptyRoot.Bytes()), nil, nil
|
||||
}
|
||||
if t.hasher == nil {
|
||||
t.hasher = newHasher()
|
||||
}
|
||||
return t.hasher.hash(t.root, db, true)
|
||||
}
|
||||
|
||||
type hasher struct {
|
||||
tmp *bytes.Buffer
|
||||
sha hash.Hash
|
||||
}
|
||||
|
||||
func newHasher() *hasher {
|
||||
return &hasher{tmp: new(bytes.Buffer), sha: sha3.NewKeccak256()}
|
||||
}
|
||||
|
||||
// hash collapses a node down into a hash node, also returning a copy of the
|
||||
// original node initialzied with the computed hash to replace the original one.
|
||||
func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) {
|
||||
// If we're not storing the node, just hashing, use avaialble cached data
|
||||
if hash, dirty := n.cache(); hash != nil && (db == nil || !dirty) {
|
||||
return hash, n, nil
|
||||
}
|
||||
// Trie not processed yet or needs storage, walk the children
|
||||
collapsed, cached, err := h.hashChildren(n, db)
|
||||
if err != nil {
|
||||
return hashNode{}, n, err
|
||||
}
|
||||
hashed, err := h.store(collapsed, db, force)
|
||||
if err != nil {
|
||||
return hashNode{}, n, err
|
||||
}
|
||||
// Cache the hash and RLP blob of the ndoe for later reuse
|
||||
if hash, ok := hashed.(hashNode); ok && !force {
|
||||
switch cached := cached.(type) {
|
||||
case shortNode:
|
||||
cached.hash = hash
|
||||
if db != nil {
|
||||
cached.dirty = false
|
||||
}
|
||||
return hashed, cached, nil
|
||||
case fullNode:
|
||||
cached.hash = hash
|
||||
if db != nil {
|
||||
cached.dirty = false
|
||||
}
|
||||
return hashed, cached, nil
|
||||
}
|
||||
}
|
||||
return hashed, cached, nil
|
||||
}
|
||||
|
||||
// hashChildren replaces the children of a node with their hashes if the encoded
|
||||
// size of the child is larger than a hash, returning the collapsed node as well
|
||||
// as a replacement for the original node with the child hashes cached in.
|
||||
func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, error) {
|
||||
var err error
|
||||
|
||||
switch n := original.(type) {
|
||||
case shortNode:
|
||||
// Hash the short node's child, caching the newly hashed subtree
|
||||
cached := n
|
||||
cached.Key = common.CopyBytes(cached.Key)
|
||||
|
||||
n.Key = compactEncode(n.Key)
|
||||
if _, ok := n.Val.(valueNode); !ok {
|
||||
if n.Val, cached.Val, err = h.hash(n.Val, db, false); err != nil {
|
||||
return n, original, err
|
||||
}
|
||||
}
|
||||
if n.Val == nil {
|
||||
n.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings.
|
||||
}
|
||||
return n, cached, nil
|
||||
|
||||
case fullNode:
|
||||
// Hash the full node's children, caching the newly hashed subtrees
|
||||
cached := fullNode{dirty: n.dirty}
|
||||
|
||||
for i := 0; i < 16; i++ {
|
||||
if n.Children[i] != nil {
|
||||
if n.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false); err != nil {
|
||||
return n, original, err
|
||||
}
|
||||
} else {
|
||||
n.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings.
|
||||
}
|
||||
}
|
||||
cached.Children[16] = n.Children[16]
|
||||
if n.Children[16] == nil {
|
||||
n.Children[16] = valueNode(nil)
|
||||
}
|
||||
return n, cached, nil
|
||||
|
||||
default:
|
||||
// Value and hash nodes don't have children so they're left as were
|
||||
return n, original, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hasher) store(n node, db DatabaseWriter, force bool) (node, error) {
|
||||
// Don't store hashes or empty nodes.
|
||||
if _, isHash := n.(hashNode); n == nil || isHash {
|
||||
return n, nil
|
||||
}
|
||||
// Generate the RLP encoding of the node
|
||||
h.tmp.Reset()
|
||||
if err := rlp.Encode(h.tmp, n); err != nil {
|
||||
panic("encode error: " + err.Error())
|
||||
}
|
||||
if h.tmp.Len() < 32 && !force {
|
||||
return n, nil // Nodes smaller than 32 bytes are stored inside their parent
|
||||
}
|
||||
// Larger nodes are replaced by their hash and stored in the database.
|
||||
hash, _ := n.cache()
|
||||
if hash == nil {
|
||||
h.sha.Reset()
|
||||
h.sha.Write(h.tmp.Bytes())
|
||||
hash = hashNode(h.sha.Sum(nil))
|
||||
}
|
||||
if db != nil {
|
||||
return hash, db.Put(hash, h.tmp.Bytes())
|
||||
}
|
||||
return hash, nil
|
||||
h := newHasher()
|
||||
defer returnHasherToPool(h)
|
||||
return h.hash(t.root, db, true)
|
||||
}
|
||||
|
@@ -21,8 +21,11 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -76,8 +79,6 @@ func TestMissingNode(t *testing.T) {
|
||||
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
|
||||
root, _ := trie.Commit()
|
||||
|
||||
ClearGlobalCache()
|
||||
|
||||
trie, _ = New(root, db)
|
||||
_, err := trie.TryGet([]byte("120000"))
|
||||
if err != nil {
|
||||
@@ -109,7 +110,6 @@ func TestMissingNode(t *testing.T) {
|
||||
}
|
||||
|
||||
db.Delete(common.FromHex("e1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9"))
|
||||
ClearGlobalCache()
|
||||
|
||||
trie, _ = New(root, db)
|
||||
_, err = trie.TryGet([]byte("120000"))
|
||||
@@ -300,41 +300,6 @@ func TestReplication(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func paranoiaCheck(t1 *Trie) (bool, *Trie) {
|
||||
t2 := new(Trie)
|
||||
it := NewIterator(t1)
|
||||
for it.Next() {
|
||||
t2.Update(it.Key, it.Value)
|
||||
}
|
||||
return t2.Hash() == t1.Hash(), t2
|
||||
}
|
||||
|
||||
func TestParanoia(t *testing.T) {
|
||||
t.Skip()
|
||||
trie := newEmpty()
|
||||
|
||||
vals := []struct{ k, v string }{
|
||||
{"do", "verb"},
|
||||
{"ether", "wookiedoo"},
|
||||
{"horse", "stallion"},
|
||||
{"shaman", "horse"},
|
||||
{"doge", "coin"},
|
||||
{"ether", ""},
|
||||
{"dog", "puppy"},
|
||||
{"shaman", ""},
|
||||
{"somethingveryoddindeedthis is", "myothernodedata"},
|
||||
}
|
||||
for _, val := range vals {
|
||||
updateString(trie, val.k, val.v)
|
||||
}
|
||||
trie.Commit()
|
||||
|
||||
ok, t2 := paranoiaCheck(trie)
|
||||
if !ok {
|
||||
t.Errorf("trie paranoia check failed %x %x", trie.Hash(), t2.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Not an actual test
|
||||
func TestOutput(t *testing.T) {
|
||||
t.Skip()
|
||||
@@ -359,44 +324,127 @@ func TestLargeValue(t *testing.T) {
|
||||
trie.Update([]byte("key1"), []byte{99, 99, 99, 99})
|
||||
trie.Update([]byte("key2"), bytes.Repeat([]byte{1}, 32))
|
||||
trie.Hash()
|
||||
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k, v []byte
|
||||
t bool
|
||||
type randTestStep struct {
|
||||
op int
|
||||
key []byte // for opUpdate, opDelete, opGet
|
||||
value []byte // for opUpdate
|
||||
}
|
||||
|
||||
func TestLargeData(t *testing.T) {
|
||||
trie := newEmpty()
|
||||
vals := make(map[string]*kv)
|
||||
type randTest []randTestStep
|
||||
|
||||
for i := byte(0); i < 255; i++ {
|
||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||
value2 := &kv{common.LeftPadBytes([]byte{10, i}, 32), []byte{i}, false}
|
||||
trie.Update(value.k, value.v)
|
||||
trie.Update(value2.k, value2.v)
|
||||
vals[string(value.k)] = value
|
||||
vals[string(value2.k)] = value2
|
||||
const (
|
||||
opUpdate = iota
|
||||
opDelete
|
||||
opGet
|
||||
opCommit
|
||||
opHash
|
||||
opReset
|
||||
opItercheckhash
|
||||
opMax // boundary value, not an actual op
|
||||
)
|
||||
|
||||
func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
|
||||
var allKeys [][]byte
|
||||
genKey := func() []byte {
|
||||
if len(allKeys) < 2 || r.Intn(100) < 10 {
|
||||
// new key
|
||||
key := make([]byte, r.Intn(50))
|
||||
randRead(r, key)
|
||||
allKeys = append(allKeys, key)
|
||||
return key
|
||||
}
|
||||
// use existing key
|
||||
return allKeys[r.Intn(len(allKeys))]
|
||||
}
|
||||
|
||||
it := NewIterator(trie)
|
||||
for it.Next() {
|
||||
vals[string(it.Key)].t = true
|
||||
var steps randTest
|
||||
for i := 0; i < size; i++ {
|
||||
step := randTestStep{op: r.Intn(opMax)}
|
||||
switch step.op {
|
||||
case opUpdate:
|
||||
step.key = genKey()
|
||||
step.value = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(step.value, uint64(i))
|
||||
case opGet, opDelete:
|
||||
step.key = genKey()
|
||||
}
|
||||
steps = append(steps, step)
|
||||
}
|
||||
return reflect.ValueOf(steps)
|
||||
}
|
||||
|
||||
var untouched []*kv
|
||||
for _, value := range vals {
|
||||
if !value.t {
|
||||
untouched = append(untouched, value)
|
||||
// rand.Rand provides a Read method in Go 1.7 and later, but
|
||||
// we can't use it yet.
|
||||
func randRead(r *rand.Rand, b []byte) {
|
||||
pos := 0
|
||||
val := 0
|
||||
for n := 0; n < len(b); n++ {
|
||||
if pos == 0 {
|
||||
val = r.Int()
|
||||
pos = 7
|
||||
}
|
||||
b[n] = byte(val)
|
||||
val >>= 8
|
||||
pos--
|
||||
}
|
||||
}
|
||||
|
||||
func runRandTest(rt randTest) bool {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
tr, _ := New(common.Hash{}, db)
|
||||
values := make(map[string]string) // tracks content of the trie
|
||||
|
||||
for _, step := range rt {
|
||||
switch step.op {
|
||||
case opUpdate:
|
||||
tr.Update(step.key, step.value)
|
||||
values[string(step.key)] = string(step.value)
|
||||
case opDelete:
|
||||
tr.Delete(step.key)
|
||||
delete(values, string(step.key))
|
||||
case opGet:
|
||||
v := tr.Get(step.key)
|
||||
want := values[string(step.key)]
|
||||
if string(v) != want {
|
||||
fmt.Printf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want)
|
||||
return false
|
||||
}
|
||||
case opCommit:
|
||||
if _, err := tr.Commit(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case opHash:
|
||||
tr.Hash()
|
||||
case opReset:
|
||||
hash, err := tr.Commit()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newtr, err := New(hash, db)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tr = newtr
|
||||
case opItercheckhash:
|
||||
checktr, _ := New(common.Hash{}, nil)
|
||||
it := tr.Iterator()
|
||||
for it.Next() {
|
||||
checktr.Update(it.Key, it.Value)
|
||||
}
|
||||
if tr.Hash() != checktr.Hash() {
|
||||
fmt.Println("hashes not equal")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if len(untouched) > 0 {
|
||||
t.Errorf("Missed %d nodes", len(untouched))
|
||||
for _, value := range untouched {
|
||||
t.Error(value)
|
||||
}
|
||||
func TestRandom(t *testing.T) {
|
||||
if err := quick.Check(runRandTest, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user