Merge branch 'qa' of https://github.com/maticnetwork/bor into djpolygon/v0.3.0packaging

This commit is contained in:
Manav Darji 2022-11-24 00:03:41 +05:30
commit 3b6c9b05d7
192 changed files with 10343 additions and 3038 deletions

11
.github/matic-cli-config.yml vendored Normal file
View file

@ -0,0 +1,11 @@
defaultStake: 10000
defaultFee: 2000
borChainId: "15001"
heimdallChainId: heimdall-15001
contractsBranch: jc/v0.3.1-backport
numOfValidators: 3
numOfNonValidators: 0
ethURL: http://ganache:9545
devnetType: docker
borDockerBuildContext: "../../bor"
heimdallDockerBuildContext: "https://github.com/maticnetwork/heimdall.git#develop"

View file

@ -1,20 +1,183 @@
name: CI
on: [push, pull_request]
on:
push:
branches:
- "master"
- "qa"
- "develop"
pull_request:
branches:
- "**"
types: [opened, synchronize, edited]
concurrency:
group: build-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
tests:
if: (github.event.action != 'closed' || github.event.pull_request.merged == true)
strategy:
matrix:
os: [ ubuntu-20.04, macos-11 ] # list of os: https://github.com/actions/virtual-environments
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
- uses: actions/checkout@v3
- run: |
git submodule update --init --recursive --force
git fetch --no-tags --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master
- uses: actions/setup-go@v3
with:
go-version: 1.17
- name: "Build binaries"
go-version: 1.18.x
- name: Install dependencies on Linux
if: runner.os == 'Linux'
run: sudo apt update && sudo apt install build-essential
- name: Golang-ci install
if: runner.os == 'Linux'
run: make lintci-deps
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/Library/Caches/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Build
run: make all
- name: "Run tests"
- name: Lint
if: runner.os == 'Linux'
run: make lint
- name: Test
run: make test
#- name: Data race tests
# run: make test-race
- name: test-integration
run: make test-integration
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: ./cover.out
# # TODO: make it work
# - name: Reproducible build test
# run: |
# make geth
# shasum -a256 ./build/bin/geth > bor1.sha256
# make geth
# shasum -a256 ./build/bin/geth > bor2.sha256
# if ! cmp -s bor1.sha256 bor2.sha256; then
# echo >&2 "Reproducible build broken"; cat bor1.sha256; cat bor2.sha256; exit 1
# fi
integration-tests:
if: (github.event.action != 'closed' || github.event.pull_request.merged == true)
strategy:
matrix:
os: [ ubuntu-20.04 ] # list of os: https://github.com/actions/virtual-environments
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
path: bor
- name: Checkout submodules
run: |
cd bor
git submodule update --init --recursive --force
git fetch --no-tags --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master
- uses: actions/setup-go@v3
with:
go-version: 1.18.x
- name: Checkout matic-cli
uses: actions/checkout@v3
with:
repository: maticnetwork/matic-cli
ref: v0.3.0-dev
path: matic-cli
- name: Install dependencies on Linux
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install build-essential
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
sudo snap install solc
sudo apt install python2 jq curl
sudo ln -sf /usr/bin/python2 /usr/bin/python
- uses: actions/setup-node@v3
with:
node-version: '10.17.0'
cache: 'npm'
cache-dependency-path: |
matic-cli/package-lock.json
matic-cli/devnet/code/contracts/package-lock.json
matic-cli/devnet/code/genesis-contracts/package-lock.json
matic-cli/devnet/code/genesis-contracts/matic-contracts/package-lock.json
- name: Bootstrap devnet
run: |
cd matic-cli
npm install --prefer-offline --no-audit --progress=false
mkdir devnet
cd devnet
../bin/matic-cli setup devnet -c ../../bor/.github/matic-cli-config.yml
- name: Launch devnet
run: |
cd matic-cli/devnet
bash docker-ganache-start.sh
bash docker-heimdall-start-all.sh
bash docker-bor-setup.sh
bash docker-bor-start-all.sh
sleep 120 && bash ganache-deployment-bor.sh
sleep 120 && bash ganache-deployment-sync.sh
sleep 120
docker exec bor0 bash -c "bor attach /root/.bor/data/bor.ipc -exec 'admin.peers'"
docker exec bor0 bash -c "bor attach /root/.bor/data/bor.ipc -exec 'eth.blockNumber'"
- name: Run smoke tests
run: |
echo "Deposit 100 matic for each account to bor network"
cd matic-cli/devnet/code/contracts
npm run truffle exec scripts/deposit.js -- --network development $(jq -r .root.tokens.MaticToken contractAddresses.json) 100000000000000000000
cd -
bash bor/integration-tests/smoke_test.sh
- name: Upload logs
if: always()
uses: actions/upload-artifact@v3
with:
name: logs_${{ github.run_id }}
path: |
matic-cli/devnet/logs
- name: Package code and chain data
if: always()
run: |
cd matic-cli/devnet
docker compose down --remove-orphans
cd -
mkdir -p ${{ github.run_id }}/matic-cli
sudo mv bor ${{ github.run_id }}
sudo mv matic-cli/devnet ${{ github.run_id }}/matic-cli
sudo tar czf code.tar.gz ${{ github.run_id }}
- name: Upload code and chain data
if: always()
uses: actions/upload-artifact@v3
with:
name: code_${{ github.run_id }}
path: code.tar.gz

View file

@ -1,29 +0,0 @@
name: Bor Docker Image CI
on:
push:
branches-ignore:
- '**'
tags:
- 'v*.*.*'
# to be used by fork patch-releases ^^
- 'v*.*.*-*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Bor Docker image
env:
DOCKERHUB: ${{ secrets.DOCKERHUB }}
DOCKERHUB_KEY: ${{ secrets.DOCKERHUB_KEY }}
run: |
ls -l
echo "Docker login"
docker login -u $DOCKERHUB -p $DOCKERHUB_KEY
echo "running build"
docker build -f Dockerfile.classic -t maticnetwork/bor:${GITHUB_REF/refs\/tags\//} .
echo "pushing image"
docker push maticnetwork/bor:${GITHUB_REF/refs\/tags\//}
echo "DONE!"

31
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,31 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 0 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.'
stale-pr-message: 'This PR is stale because it has been open 21 days with no activity. Remove stale label or comment or this will be closed in 14 days.'
close-issue-message: 'This issue was closed because it has been stalled for 28 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 35 days with no activity.'
days-before-issue-stale: 14
days-before-pr-stale: 21
days-before-issue-close: 14
days-before-pr-close: 14

2
.gitignore vendored
View file

@ -15,6 +15,8 @@
*/**/*dapps*
build/_vendor/pkg
cover.out
#*
.#*
*#

View file

@ -1,6 +1,7 @@
# This file configures github.com/golangci/golangci-lint.
run:
go: '1.18'
timeout: 20m
tests: true
# default is true. Enables skipping of directories:
@ -8,28 +9,139 @@ run:
skip-dirs-use-default: true
skip-files:
- core/genesis_alloc.go
- gen_.*.go
- .*_gen.go
linters:
disable-all: true
enable:
- deadcode
- goconst
- goimports
- gosimple
- govet
- ineffassign
- misspell
# - staticcheck
- unconvert
# - unused
- varcheck
- bodyclose
- containedctx
- contextcheck
- decorder
- durationcheck
- errchkjson
- errname
- exhaustive
- exportloopref
- gocognit
- gofmt
# - gomnd
- gomoddirectives
- gosec
- makezero
- nestif
- nilerr
- nilnil
- noctx
#- nosprintfhostport # TODO: do we use IPv6?
- paralleltest
- prealloc
- predeclared
#- promlinter
#- revive
# - tagliatelle
- tenv
- thelper
- tparallel
- unconvert
- unparam
- wsl
#- errorlint causes stack overflow. TODO: recheck after each golangci update
linters-settings:
gofmt:
simplify: true
auto-fix: false
goconst:
min-len: 3 # minimum length of string constant
min-occurrences: 6 # minimum number of occurrences
min-occurrences: 2 # minimum number of occurrences
numbers: true
goimports:
local-prefixes: github.com/ethereum/go-ethereum
nestif:
min-complexity: 5
prealloc:
for-loops: true
gocritic:
# Which checks should be enabled; can't be combined with 'disabled-checks';
# See https://go-critic.github.io/overview#checks-overview
# To check which checks are enabled run `GL_DEBUG=gocritic ./build/bin/golangci-lint run`
# By default list of stable checks is used.
enabled-checks:
- badLock
- filepathJoin
- sortSlice
- sprintfQuotedString
- syncMapLoadAndDelete
- weakCond
- boolExprSimplify
- httpNoBody
- ioutilDeprecated
- nestingReduce
- preferFilepathJoin
- redundantSprint
- stringConcatSimplify
- timeExprSimplify
- typeAssertChain
- yodaStyleExpr
- truncateCmp
- equalFold
- preferDecodeRune
- preferFprint
- preferStringWriter
- preferWriteByte
- sliceClear
#- ruleguard
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
disabled-checks:
- regexpMust
- exitAfterDefer
- dupBranchBody
- singleCaseSwitch
- unlambda
- captLocal
- commentFormatting
- ifElseChain
- importShadow
- builtinShadow
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
- diagnostic
- opinionated
- style
disabled-tags:
- experimental
govet:
disable:
- deepequalerrors
- fieldalignment
- shadow
- unsafeptr
check-shadowing: true
enable-all: true
settings:
printf:
# Run `go tool vet help printf` to see available settings for `printf` analyzer.
funcs:
- (github.com/ethereum/go-ethereum/log.Logger).Trace
- (github.com/ethereum/go-ethereum/log.Logger).Debug
- (github.com/ethereum/go-ethereum/log.Logger).Info
- (github.com/ethereum/go-ethereum/log.Logger).Warn
- (github.com/ethereum/go-ethereum/log.Logger).Error
- (github.com/ethereum/go-ethereum/log.Logger).Crit
issues:
exclude-rules:
@ -48,3 +160,28 @@ issues:
- path: cmd/faucet/
linters:
- deadcode
# Exclude some linters from running on tests files.
- path: test\.go
linters:
- gosec
- unused
- deadcode
- gocritic
- path: cmd/devp2p
linters:
- gosec
- unused
- deadcode
- gocritic
- path: metrics/sample\.go
linters:
- gosec
- gocritic
- path: p2p/simulations
linters:
- gosec
- gocritic
max-issues-per-linter: 0
max-same-issues: 0
#new: true
new-from-rev: origin/master

View file

@ -7,7 +7,7 @@ release:
builds:
- id: darwin-amd64
main: ./cmd/geth
main: ./cmd/cli
binary: bor
goos:
- darwin
@ -22,7 +22,7 @@ builds:
-s -w
- id: darwin-arm64
main: ./cmd/geth
main: ./cmd/cli
binary: bor
goos:
- darwin
@ -37,7 +37,7 @@ builds:
-s -w
- id: linux-amd64
main: ./cmd/geth
main: ./cmd/cli
binary: bor
goos:
- linux
@ -53,7 +53,7 @@ builds:
-s -w -extldflags "-static"
- id: linux-arm64
main: ./cmd/geth
main: ./cmd/cli
binary: bor
goos:
- linux
@ -75,12 +75,18 @@ nfpms:
description: Polygon Blockchain
license: GPLv3 LGPLv3
bindir: /usr/local/bin
formats:
- apk
- deb
- rpm
contents:
- dst: /var/lib/bor
type: dir
file_info:
mode: 0777
- src: builder/files/bor.service
dst: /lib/systemd/system/bor.service
type: config
@ -90,6 +96,12 @@ nfpms:
- src: builder/files/genesis-testnet-v4.json
dst: /etc/bor/genesis-testnet-v4.json
type: config
- src: builder/files/config.toml
dst: /var/lib/bor/config.toml
type: config
scripts:
postinstall: builder/files/bor-post-install.sh
overrides:
rpm:

View file

@ -1,15 +1,20 @@
FROM golang:latest
ARG BOR_DIR=/bor
ARG BOR_DIR=/var/lib/bor
ENV BOR_DIR=$BOR_DIR
RUN apt-get update -y && apt-get upgrade -y \
&& apt install build-essential git -y \
&& mkdir -p /bor
&& mkdir -p ${BOR_DIR}
WORKDIR ${BOR_DIR}
COPY . .
RUN make bor-all
RUN make bor
RUN cp build/bin/bor /usr/bin/
RUN groupadd -g 10137 bor \
&& useradd -u 10137 --no-log-init --create-home -r -g bor bor \
&& chown -R bor:bor ${BOR_DIR}
ENV SHELL /bin/bash
EXPOSE 8545 8546 8547 30303 30303/udp

View file

@ -13,6 +13,6 @@ RUN set -x \
&& apk add --update --no-cache \
ca-certificates \
&& rm -rf /var/cache/apk/*
COPY --from=builder /bor/build/bin/* /usr/local/bin/
COPY --from=builder /bor/build/bin/* /usr/bin/
EXPOSE 8545 8546 30303 30303/udp

View file

@ -1,18 +0,0 @@
# Build Geth in a stock Go builder container
FROM golang:1.18.1-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git bash
ADD . /bor
RUN cd /bor && make bor-all
CMD ["/bin/bash"]
# Pull Bor into a second stage deploy alpine container
FROM alpine:latest
RUN apk add --no-cache ca-certificates
COPY --from=builder /bor/build/bin/bor /usr/local/bin/
COPY --from=builder /bor/build/bin/bootnode /usr/local/bin/
EXPOSE 8545 8546 8547 30303 30303/udp

View file

@ -1,10 +1,20 @@
FROM alpine:3.14
ARG BOR_DIR=/var/lib/bor
ENV BOR_DIR=$BOR_DIR
RUN apk add --no-cache ca-certificates && \
mkdir -p /etc/bor
COPY bor /usr/local/bin/
COPY builder/files/genesis-mainnet-v1.json /etc/bor/
COPY builder/files/genesis-testnet-v4.json /etc/bor/
mkdir -p ${BOR_DIR}
WORKDIR ${BOR_DIR}
COPY bor /usr/bin/
COPY builder/files/genesis-mainnet-v1.json ${BOR_DIR}
COPY builder/files/genesis-testnet-v4.json ${BOR_DIR}
RUN groupadd -g 10137 bor \
&& useradd -u 10137 --no-log-init --create-home -r -g bor bor \
&& chown -R bor:bor ${BOR_DIR}
USER bor
EXPOSE 8545 8546 8547 30303 30303/udp
ENTRYPOINT ["bor"]

View file

@ -2,27 +2,37 @@
# with Go source code. If you know what GOPATH is then you probably
# don't need to bother with make.
.PHONY: geth android ios evm all test clean
.PHONY: geth android ios geth-cross evm all test clean docs
.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
GOBIN = ./build/bin
GO ?= latest
GOBIN = $(CURDIR)/build/bin
GORUN = env GO111MODULE=on go run
GOPATH = $(shell go env GOPATH)
bor:
$(GORUN) build/ci.go install ./cmd/geth
mkdir -p $(GOPATH)/bin/
cp $(GOBIN)/geth $(GOBIN)/bor
cp $(GOBIN)/* $(GOPATH)/bin/
GIT_COMMIT ?= $(shell git rev-list -1 HEAD)
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
GIT_TAG ?= $(shell git describe --tags `git rev-list --tags="v*" --max-count=1`)
bor-all:
$(GORUN) build/ci.go install
PACKAGE = github.com/ethereum/go-ethereum
GO_FLAGS += -buildvcs=false
GO_FLAGS += -ldflags "-X ${PACKAGE}/params.GitCommit=${GIT_COMMIT} -X ${PACKAGE}/params.GitBranch=${GIT_BRANCH} -X ${PACKAGE}/params.GitTag=${GIT_TAG}"
TESTALL = $$(go list ./... | grep -v go-ethereum/cmd/)
TESTE2E = ./tests/...
GOTEST = GODEBUG=cgocheck=0 go test $(GO_FLAGS) -p 1
bor:
mkdir -p $(GOPATH)/bin/
cp $(GOBIN)/geth $(GOBIN)/bor
cp $(GOBIN)/* $(GOPATH)/bin/
go build -o $(GOBIN)/bor ./cmd/cli/main.go
cp $(GOBIN)/bor $(GOPATH)/bin/
@echo "Done building."
protoc:
protoc --go_out=. --go-grpc_out=. ./command/server/proto/*.proto
protoc --go_out=. --go-grpc_out=. ./internal/cli/server/proto/*.proto
geth:
$(GORUN) build/ci.go install ./cmd/geth
@ -45,11 +55,29 @@ ios:
@echo "Import \"$(GOBIN)/Geth.framework\" to use the library."
test:
# Skip mobile and cmd tests since they are being deprecated
go test -v $$(go list ./... | grep -v go-ethereum/cmd/) -cover -coverprofile=cover.out
$(GOTEST) --timeout 5m -shuffle=on -cover -coverprofile=cover.out $(TESTALL)
lint: ## Run linters.
$(GORUN) build/ci.go lint
test-race:
$(GOTEST) --timeout 15m -race -shuffle=on $(TESTALL)
test-integration:
$(GOTEST) --timeout 30m -tags integration $(TESTE2E)
escape:
cd $(path) && go test -gcflags "-m -m" -run none -bench=BenchmarkJumpdest* -benchmem -memprofile mem.out
lint:
@./build/bin/golangci-lint run --config ./.golangci.yml
lintci-deps:
rm -f ./build/bin/golangci-lint
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./build/bin v1.46.0
goimports:
goimports -local "$(PACKAGE)" -w .
docs:
$(GORUN) cmd/clidoc/main.go -d ./docs/cli
clean:
env GO111MODULE=on go clean -cache
@ -59,16 +87,20 @@ clean:
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
devtools:
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
env GOBIN= go install github.com/kevinburke/go-bindata/go-bindata@latest
env GOBIN= go install github.com/fjl/gencodec@latest
env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest
env GOBIN= go install ./cmd/abigen
# Notice! If you adding new binary - add it also to tests/deps/fake.go file
$(GOBUILD) -o $(GOBIN)/stringer github.com/golang.org/x/tools/cmd/stringer
$(GOBUILD) -o $(GOBIN)/go-bindata github.com/kevinburke/go-bindata/go-bindata
$(GOBUILD) -o $(GOBIN)/codecgen github.com/ugorji/go/codec/codecgen
$(GOBUILD) -o $(GOBIN)/abigen ./cmd/abigen
$(GOBUILD) -o $(GOBIN)/mockgen github.com/golang/mock/mockgen
$(GOBUILD) -o $(GOBIN)/protoc-gen-go github.com/golang/protobuf/protoc-gen-go
PATH=$(GOBIN):$(PATH) go generate ./common
PATH=$(GOBIN):$(PATH) go generate ./core/types
PATH=$(GOBIN):$(PATH) go generate ./consensus/bor
@type "solc" 2> /dev/null || echo 'Please install solc'
@type "protoc" 2> /dev/null || echo 'Please install protoc'
# 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-*

View file

@ -63,12 +63,6 @@ them using your favourite package manager. Once the dependencies are installed,
$ make bor
```
- or, to build the full suite of utilities:
```shell
$ make bor-all
```
### Make awesome changes!
1. Create new branch for your changes
@ -113,12 +107,6 @@ them using your favourite package manager. Once the dependencies are installed,
<hr style="margin-top: 3em; margin-bottom: 3em;">
Build the beta client:
```shell
go build -o bor-beta command/*.go
```
## License
The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the

View file

@ -27,10 +27,13 @@ import (
"testing"
"time"
"go.uber.org/goleak"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/leak"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@ -38,6 +41,8 @@ import (
)
func TestSimulatedBackend(t *testing.T) {
defer goleak.VerifyNone(t, leak.IgnoreList()...)
var gasLimit uint64 = 8000029
key, _ := crypto.GenerateKey() // nolint: gosec
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -e
PKG="bor"
if ! getent passwd $PKG >/dev/null ; then
adduser --disabled-password --disabled-login --shell /usr/sbin/nologin --quiet --system --no-create-home --home /nonexistent $PKG
echo "Created system user $PKG"
fi

View file

@ -6,21 +6,9 @@
[Service]
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/bin/bor \
--bor-mumbai \
# --bor-mainnet \
--datadir /var/lib/bor/data \
--bootnodes "enode://0cb82b395094ee4a2915e9714894627de9ed8498fb881cec6db7c65e8b9a5bd7f2f25cc84e71e89d0947e51c76e85d0847de848c7782b13c0255247a6758178c@44.232.55.71:30303,enode://88116f4295f5a31538ae409e4d44ad40d22e44ee9342869e7d68bdec55b0f83c1530355ce8b41fbec0928a7d75a5745d528450d30aec92066ab6ba1ee351d710@159.203.9.164:30303"
# Validator params
# Uncomment and configure the following lines in case you run a validator
# --keystore /var/lib/bor/keystore \
# --unlock [VALIDATOR ADDRESS] \
# --password /var/lib/bor/password.txt \
# --allow-insecure-unlock \
# --nodiscover --maxpeers 1 \
# --mine
ExecStart=/usr/local/bin/bor server -config="/var/lib/bor/config.toml"
Type=simple
User=root
User=bor
KillSignal=SIGINT
TimeoutStopSec=120

137
builder/files/config.toml Normal file
View file

@ -0,0 +1,137 @@
# NOTE: Uncomment and configure the following 8 fields in case you run a validator:
# `mine`, `etherbase`, `nodiscover`, `maxpeers`, `keystore`, `allow-insecure-unlock`, `password`, `unlock`
chain = "mainnet"
# chain = "mumbai"
# identity = "Pratiks-MacBook-Pro.local"
# log-level = "INFO"
datadir = "/var/lib/bor/data"
# keystore = "/var/lib/bor/keystore"
syncmode = "full"
# gcmode = "full"
# snapshot = true
# "bor.logs" = false
# ethstats = ""
# ["eth.requiredblocks"]
[p2p]
# maxpeers = 1
# nodiscover = true
# maxpendpeers = 50
# bind = "0.0.0.0"
# port = 30303
# nat = "any"
[p2p.discovery]
# v5disc = false
bootnodes = ["enode://0cb82b395094ee4a2915e9714894627de9ed8498fb881cec6db7c65e8b9a5bd7f2f25cc84e71e89d0947e51c76e85d0847de848c7782b13c0255247a6758178c@44.232.55.71:30303", "enode://88116f4295f5a31538ae409e4d44ad40d22e44ee9342869e7d68bdec55b0f83c1530355ce8b41fbec0928a7d75a5745d528450d30aec92066ab6ba1ee351d710@159.203.9.164:30303"]
# Uncomment below `bootnodes` field for Mumbai bootnode
# bootnodes = ["enode://095c4465fe509bd7107bbf421aea0d3ad4d4bfc3ff8f9fdc86f4f950892ae3bbc3e5c715343c4cf60c1c06e088e621d6f1b43ab9130ae56c2cacfd356a284ee4@18.213.200.99:30303"]
# bootnodesv4 = []
# bootnodesv5 = []
# static-nodes = []
# trusted-nodes = []
# dns = []
# [heimdall]
# url = "http://localhost:1317"
# "bor.without" = false
# grpc-address = ""
[txpool]
nolocals = true
pricelimit = 30000000000
accountslots = 16
globalslots = 32768
accountqueue = 16
globalqueue = 32768
lifetime = "1h30m0s"
# locals = []
# journal = ""
# rejournal = "1h0m0s"
# pricebump = 10
[miner]
gaslimit = 20000000
gasprice = "30000000000"
# mine = true
# etherbase = "VALIDATOR ADDRESS"
# extradata = ""
# [jsonrpc]
# ipcdisable = false
# ipcpath = ""
# gascap = 50000000
# txfeecap = 5.0
# [jsonrpc.http]
# enabled = false
# port = 8545
# prefix = ""
# host = "localhost"
# api = ["eth", "net", "web3", "txpool", "bor"]
# vhosts = ["*"]
# corsdomain = ["*"]
# [jsonrpc.ws]
# enabled = false
# port = 8546
# prefix = ""
# host = "localhost"
# api = ["web3", "net"]
# origins = ["*"]
# [jsonrpc.graphql]
# enabled = false
# port = 0
# prefix = ""
# host = ""
# vhosts = ["*"]
# corsdomain = ["*"]
# [gpo]
# blocks = 20
# percentile = 60
# maxprice = "5000000000000"
# ignoreprice = "2"
[telemetry]
metrics = true
# expensive = false
# prometheus-addr = "127.0.0.1:7071"
# opencollector-endpoint = "127.0.0.1:4317"
# [telemetry.influx]
# influxdb = false
# endpoint = ""
# database = ""
# username = ""
# password = ""
# influxdbv2 = false
# token = ""
# bucket = ""
# organization = ""
# [telemetry.influx.tags]
# [cache]
# cache = 1024
# gc = 25
# snapshot = 10
# database = 50
# trie = 15
# journal = "triecache"
# rejournal = "1h0m0s"
# noprefetch = false
# preimages = false
# txlookuplimit = 2350000
[accounts]
# allow-insecure-unlock = true
# password = "/var/lib/bor/password.txt"
# unlock = ["VALIDATOR ADDRESS"]
# lightkdf = false
# disable-bor-wallet = false
# [grpc]
# addr = ":3131"
# [developer]
# dev = false
# period = 0

77
cmd/clidoc/main.go Normal file
View file

@ -0,0 +1,77 @@
package main
import (
"flag"
"log"
"os"
"path/filepath"
"sort"
"strings"
"github.com/ethereum/go-ethereum/internal/cli"
)
const (
DefaultDir string = "./docs/cli"
DefaultMainPage string = "README.md"
)
func main() {
commands := cli.Commands()
dest := flag.String("d", DefaultDir, "Destination directory where the docs will be generated")
flag.Parse()
dirPath := filepath.Join(".", *dest)
if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
log.Fatalln("Failed to create directory.", err)
}
mainPage := []string{
"# Bor command line interface",
"## Commands",
}
keys := make([]string, len(commands))
i := 0
for k := range commands {
keys[i] = k
i++
}
sort.Strings(keys)
for _, name := range keys {
cmd, err := commands[name]()
if err != nil {
log.Fatalf("Error occurred when inspecting bor command %s: %s", name, err)
}
fileName := strings.ReplaceAll(name, " ", "_") + ".md"
overwriteFile(filepath.Join(dirPath, fileName), cmd.MarkDown())
mainPage = append(mainPage, "- [```"+name+"```](./"+fileName+")")
}
overwriteFile(filepath.Join(dirPath, DefaultMainPage), strings.Join(mainPage, "\n\n"))
os.Exit(0)
}
func overwriteFile(filePath string, text string) {
log.Printf("Writing to page: %s\n", filePath)
f, err := os.Create(filePath)
if err != nil {
log.Fatalln(err)
}
if _, err = f.WriteString(text); err != nil {
log.Fatalln(err)
}
if err = f.Close(); err != nil {
log.Fatalln(err)
}
}

View file

@ -17,17 +17,16 @@
package main
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"reflect"
"time"
"unicode"
"gopkg.in/urfave/cli.v1"
"github.com/BurntSushi/toml"
"github.com/ethereum/go-ethereum/accounts/external"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
@ -41,7 +40,6 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/naoina/toml"
)
var (
@ -61,28 +59,6 @@ var (
}
)
// These settings ensure that TOML keys use the same names as Go struct fields.
var tomlSettings = toml.Config{
NormFieldName: func(rt reflect.Type, key string) string {
return key
},
FieldToKey: func(rt reflect.Type, field string) string {
return field
},
MissingField: func(rt reflect.Type, field string) error {
id := fmt.Sprintf("%s.%s", rt.String(), field)
if deprecated(id) {
log.Warn("Config field is deprecated and won't have an effect", "name", id)
return nil
}
var link string
if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" {
link = fmt.Sprintf(", see https://godoc.org/%s#%s for available fields", rt.PkgPath(), rt.Name())
}
return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link)
},
}
type ethstatsConfig struct {
URL string `toml:",omitempty"`
}
@ -95,20 +71,19 @@ type gethConfig struct {
}
func loadConfig(file string, cfg *gethConfig) error {
f, err := os.Open(file)
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
defer f.Close()
err = tomlSettings.NewDecoder(bufio.NewReader(f)).Decode(cfg)
// Add file name to errors that have a line number.
if _, ok := err.(*toml.LineError); ok {
err = errors.New(file + ", " + err.Error())
}
tomlData := string(data)
if _, err = toml.Decode(tomlData, &cfg); err != nil {
return err
}
return nil
}
func defaultNodeConfig() node.Config {
cfg := node.DefaultConfig
cfg.Name = clientIdentifier
@ -214,22 +189,10 @@ func dumpConfig(ctx *cli.Context) error {
comment += "# Note: this config doesn't contain the genesis block.\n\n"
}
out, err := tomlSettings.Marshal(&cfg)
if err != nil {
if err := toml.NewEncoder(os.Stdout).Encode(&cfg); err != nil {
return err
}
dump := os.Stdout
if ctx.NArg() > 0 {
dump, err = os.OpenFile(ctx.Args().Get(0), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer dump.Close()
}
dump.WriteString(comment)
dump.Write(out)
return nil
}
@ -366,7 +329,7 @@ func setDefaultMumbaiGethConfig(ctx *cli.Context, config *gethConfig) {
config.Eth.TxPool.AccountQueue = 64
config.Eth.TxPool.GlobalQueue = 131072
config.Eth.TxPool.Lifetime = 90 * time.Minute
config.Node.P2P.MaxPeers = 200
config.Node.P2P.MaxPeers = 50
config.Metrics.Enabled = true
// --pprof is enabled in 'internal/debug/flags.go'
}
@ -389,7 +352,7 @@ func setDefaultBorMainnetGethConfig(ctx *cli.Context, config *gethConfig) {
config.Eth.TxPool.AccountQueue = 64
config.Eth.TxPool.GlobalQueue = 131072
config.Eth.TxPool.Lifetime = 90 * time.Minute
config.Node.P2P.MaxPeers = 200
config.Node.P2P.MaxPeers = 50
config.Metrics.Enabled = true
// --pprof is enabled in 'internal/debug/flags.go'
}

View file

@ -1620,6 +1620,13 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.GlobalIsSet(SyncModeFlag.Name) {
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
// To be extra preventive, we won't allow the node to start
// in snap sync mode until we have it working
// TODO(snap): Comment when we have snap sync working
if cfg.SyncMode == downloader.SnapSync {
cfg.SyncMode = downloader.FullSync
}
}
if ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name)

28
common/debug/debug.go Normal file
View file

@ -0,0 +1,28 @@
package debug
import (
"runtime"
)
// Callers returns given number of callers with packages
func Callers(show int) []string {
fpcs := make([]uintptr, show)
n := runtime.Callers(2, fpcs)
if n == 0 {
return nil
}
callers := make([]string, 0, len(fpcs))
for _, p := range fpcs {
caller := runtime.FuncForPC(p - 1)
if caller == nil {
continue
}
callers = append(callers, caller.Name())
}
return callers
}

View file

@ -0,0 +1,23 @@
package leak
import "go.uber.org/goleak"
func IgnoreList() []goleak.Option {
return []goleak.Option{
// a list of goroutne leaks that hard to fix due to external dependencies or too big refactoring needed
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/core.(*txSenderCacher).cache"),
goleak.IgnoreTopFunction("github.com/rjeczalik/notify.(*recursiveTree).dispatch"),
goleak.IgnoreTopFunction("github.com/rjeczalik/notify.(*recursiveTree).internal"),
goleak.IgnoreTopFunction("github.com/rjeczalik/notify.(*nonrecursiveTree).dispatch"),
goleak.IgnoreTopFunction("github.com/rjeczalik/notify.(*nonrecursiveTree).internal"),
goleak.IgnoreTopFunction("github.com/rjeczalik/notify._Cfunc_CFRunLoopRun"),
// todo: this leaks should be fixed
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/metrics.(*meterArbiter).tick"),
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/consensus/ethash.(*remoteSealer).loop"),
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/core.(*BlockChain).updateFutureBlocks"),
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/core/state/snapshot.(*diskLayer).generate"),
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/accounts/abi/bind/backends.nullSubscription.func1"),
goleak.IgnoreTopFunction("github.com/ethereum/go-ethereum/eth/filters.(*EventSystem).eventLoop"),
}
}

11
common/set/slice.go Normal file
View file

@ -0,0 +1,11 @@
package set
func New[T comparable](slice []T) map[T]struct{} {
m := make(map[T]struct{}, len(slice))
for _, el := range slice {
m[el] = struct{}{}
}
return m
}

View file

@ -0,0 +1,6 @@
package abi
type ABI interface {
Pack(name string, args ...interface{}) ([]byte, error)
UnpackIntoInterface(v interface{}, name string, data []byte) error
}

View file

@ -4,14 +4,17 @@ import (
"encoding/hex"
"math"
"math/big"
"sort"
"strconv"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rpc"
lru "github.com/hashicorp/golang-lru"
"github.com/xsleonard/go-merkle"
"golang.org/x/crypto/sha3"
@ -43,9 +46,88 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
if header == nil {
return nil, errUnknownBlock
}
return api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
}
type BlockSigners struct {
Signers []difficultiesKV
Diff int
Author common.Address
}
type difficultiesKV struct {
Signer common.Address
Difficulty uint64
}
func rankMapDifficulties(values map[common.Address]uint64) []difficultiesKV {
ss := make([]difficultiesKV, 0, len(values))
for k, v := range values {
ss = append(ss, difficultiesKV{k, v})
}
sort.Slice(ss, func(i, j int) bool {
return ss[i].Difficulty > ss[j].Difficulty
})
return ss
}
// GetSnapshotProposerSequence retrieves the in-turn signers of all sprints in a span
func (api *API) GetSnapshotProposerSequence(number *rpc.BlockNumber) (BlockSigners, error) {
snapNumber := *number - 1
var difficulties = make(map[common.Address]uint64)
snap, err := api.GetSnapshot(&snapNumber)
if err != nil {
return BlockSigners{}, err
}
proposer := snap.ValidatorSet.GetProposer().Address
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
signers := snap.signers()
for i := 0; i < len(signers); i++ {
tempIndex := i
if tempIndex < proposerIndex {
tempIndex = tempIndex + len(signers)
}
difficulties[signers[i]] = uint64(len(signers) - (tempIndex - proposerIndex))
}
rankedDifficulties := rankMapDifficulties(difficulties)
author, err := api.GetAuthor(number)
if err != nil {
return BlockSigners{}, err
}
diff := int(difficulties[*author])
blockSigners := &BlockSigners{
Signers: rankedDifficulties,
Diff: diff,
Author: *author,
}
return *blockSigners, nil
}
// GetSnapshotProposer retrieves the in-turn signer at a given block.
func (api *API) GetSnapshotProposer(number *rpc.BlockNumber) (common.Address, error) {
*number -= 1
snap, err := api.GetSnapshot(number)
if err != nil {
return common.Address{}, err
}
return snap.ValidatorSet.GetProposer().Address, nil
}
// GetAuthor retrieves the author a block.
func (api *API) GetAuthor(number *rpc.BlockNumber) (*common.Address, error) {
// Retrieve the requested block number (or current if none requested)
@ -59,7 +141,9 @@ func (api *API) GetAuthor(number *rpc.BlockNumber) (*common.Address, error) {
if header == nil {
return nil, errUnknownBlock
}
author, err := api.bor.Author(header)
return &author, err
}
@ -69,6 +153,7 @@ func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
if header == nil {
return nil, errUnknownBlock
}
return api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
}
@ -85,10 +170,13 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) {
if header == nil {
return nil, errUnknownBlock
}
snap, err := api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return nil, err
}
return snap.signers(), nil
}
@ -98,10 +186,13 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
if header == nil {
return nil, errUnknownBlock
}
snap, err := api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return nil, err
}
return snap.signers(), nil
}
@ -111,15 +202,17 @@ func (api *API) GetCurrentProposer() (common.Address, error) {
if err != nil {
return common.Address{}, err
}
return snap.ValidatorSet.GetProposer().Address, nil
}
// GetCurrentValidators gets the current validators
func (api *API) GetCurrentValidators() ([]*Validator, error) {
func (api *API) GetCurrentValidators() ([]*valset.Validator, error) {
snap, err := api.GetSnapshot(nil)
if err != nil {
return make([]*Validator, 0), err
return make([]*valset.Validator, 0), err
}
return snap.ValidatorSet.Validators, nil
}
@ -128,26 +221,36 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) {
if err := api.initializeRootHashCache(); err != nil {
return "", err
}
key := getRootHashKey(start, end)
if root, known := api.rootHashCache.Get(key); known {
return root.(string), nil
}
length := uint64(end - start + 1)
length := end - start + 1
if length > MaxCheckpointLength {
return "", &MaxCheckpointLengthExceededError{start, end}
}
currentHeaderNumber := api.chain.CurrentHeader().Number.Uint64()
if start > end || end > currentHeaderNumber {
return "", &InvalidStartEndBlockError{start, end, currentHeaderNumber}
return "", &valset.InvalidStartEndBlockError{Start: start, End: end, CurrentHeader: currentHeaderNumber}
}
blockHeaders := make([]*types.Header, end-start+1)
wg := new(sync.WaitGroup)
concurrent := make(chan bool, 20)
for i := start; i <= end; i++ {
wg.Add(1)
concurrent <- true
go func(number uint64) {
blockHeaders[number-start] = api.chain.GetHeaderByNumber(uint64(number))
blockHeaders[number-start] = api.chain.GetHeaderByNumber(number)
<-concurrent
wg.Done()
}(i)
@ -156,6 +259,7 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) {
close(concurrent)
headers := make([][32]byte, nextPowerOfTwo(length))
for i := 0; i < len(blockHeaders); i++ {
blockHeader := blockHeaders[i]
header := crypto.Keccak256(appendBytes32(
@ -166,6 +270,7 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) {
))
var arr [32]byte
copy(arr[:], header)
headers[i] = arr
}
@ -174,8 +279,10 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) {
if err := tree.Generate(convert(headers), sha3.NewLegacyKeccak256()); err != nil {
return "", err
}
root := hex.EncodeToString(tree.Root().Hash)
api.rootHashCache.Add(key, root)
return root, nil
}
@ -184,6 +291,7 @@ func (api *API) initializeRootHashCache() error {
if api.rootHashCache == nil {
api.rootHashCache, err = lru.NewARC(10)
}
return err
}

View file

@ -0,0 +1,14 @@
package api
import (
"context"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)
//go:generate mockgen -destination=./caller_mock.go -package=api . Caller
type Caller interface {
Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error)
}

View file

@ -0,0 +1,53 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ethereum/go-ethereum/consensus/bor/api (interfaces: Caller)
// Package api is a generated GoMock package.
package api
import (
context "context"
reflect "reflect"
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
ethapi "github.com/ethereum/go-ethereum/internal/ethapi"
rpc "github.com/ethereum/go-ethereum/rpc"
gomock "github.com/golang/mock/gomock"
)
// MockCaller is a mock of Caller interface.
type MockCaller struct {
ctrl *gomock.Controller
recorder *MockCallerMockRecorder
}
// MockCallerMockRecorder is the mock recorder for MockCaller.
type MockCallerMockRecorder struct {
mock *MockCaller
}
// NewMockCaller creates a new mock instance.
func NewMockCaller(ctrl *gomock.Controller) *MockCaller {
mock := &MockCaller{ctrl: ctrl}
mock.recorder = &MockCallerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCaller) EXPECT() *MockCallerMockRecorder {
return m.recorder
}
// Call mocks base method.
func (m *MockCaller) Call(arg0 context.Context, arg1 ethapi.TransactionArgs, arg2 rpc.BlockNumberOrHash, arg3 *ethapi.StateOverride) (hexutil.Bytes, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Call", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(hexutil.Bytes)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Call indicates an expected call of Call.
func (mr *MockCallerMockRecorder) Call(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockCaller)(nil).Call), arg0, arg1, arg2, arg3)
}

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,8 @@ import (
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
@ -12,10 +14,11 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/assert"
)
func TestGenesisContractChange(t *testing.T) {
t.Parallel()
addr0 := common.Address{0x1}
b := &Bor{
@ -101,6 +104,8 @@ func TestGenesisContractChange(t *testing.T) {
}
func TestEncodeSigHeaderJaipur(t *testing.T) {
t.Parallel()
// As part of the EIP-1559 fork in mumbai, an incorrect seal hash
// was used for Bor that did not included the BaseFee. The Jaipur
// block is a hard fork to fix that.

View file

@ -1,4 +1,4 @@
package bor
package clerk
import (
"fmt"

File diff suppressed because one or more lines are too long

View file

@ -3,37 +3,10 @@ package bor
import (
"fmt"
"time"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
)
// TotalVotingPowerExceededError is returned when the maximum allowed total voting power is exceeded
type TotalVotingPowerExceededError struct {
Sum int64
Validators []*Validator
}
func (e *TotalVotingPowerExceededError) Error() string {
return fmt.Sprintf(
"Total voting power should be guarded to not exceed %v; got: %v; for validator set: %v",
MaxTotalVotingPower,
e.Sum,
e.Validators,
)
}
type InvalidStartEndBlockError struct {
Start uint64
End uint64
CurrentHeader uint64
}
func (e *InvalidStartEndBlockError) Error() string {
return fmt.Sprintf(
"Invalid parameters start: %d and end block: %d params",
e.Start,
e.End,
)
}
type MaxCheckpointLengthExceededError struct {
Start uint64
End uint64
@ -129,7 +102,7 @@ type InvalidStateReceivedError struct {
Number uint64
LastStateID uint64
To *time.Time
Event *EventRecordWithTime
Event *clerk.EventRecordWithTime
}
func (e *InvalidStateReceivedError) Error() string {

16
consensus/bor/genesis.go Normal file
View file

@ -0,0 +1,16 @@
package bor
import (
"math/big"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
)
//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract
type GenesisContract interface {
CommitState(event *clerk.EventRecordWithTime, state *state.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error)
LastStateId(snapshotNumber uint64) (*big.Int, error)
}

View file

@ -0,0 +1,69 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: GenesisContract)
// Package bor is a generated GoMock package.
package bor
import (
big "math/big"
reflect "reflect"
clerk "github.com/ethereum/go-ethereum/consensus/bor/clerk"
statefull "github.com/ethereum/go-ethereum/consensus/bor/statefull"
state "github.com/ethereum/go-ethereum/core/state"
types "github.com/ethereum/go-ethereum/core/types"
gomock "github.com/golang/mock/gomock"
)
// MockGenesisContract is a mock of GenesisContract interface.
type MockGenesisContract struct {
ctrl *gomock.Controller
recorder *MockGenesisContractMockRecorder
}
// MockGenesisContractMockRecorder is the mock recorder for MockGenesisContract.
type MockGenesisContractMockRecorder struct {
mock *MockGenesisContract
}
// NewMockGenesisContract creates a new mock instance.
func NewMockGenesisContract(ctrl *gomock.Controller) *MockGenesisContract {
mock := &MockGenesisContract{ctrl: ctrl}
mock.recorder = &MockGenesisContractMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGenesisContract) EXPECT() *MockGenesisContractMockRecorder {
return m.recorder
}
// CommitState mocks base method.
func (m *MockGenesisContract) CommitState(arg0 *clerk.EventRecordWithTime, arg1 *state.StateDB, arg2 *types.Header, arg3 statefull.ChainContext) (uint64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CommitState", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(uint64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CommitState indicates an expected call of CommitState.
func (mr *MockGenesisContractMockRecorder) CommitState(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitState", reflect.TypeOf((*MockGenesisContract)(nil).CommitState), arg0, arg1, arg2, arg3)
}
// LastStateId mocks base method.
func (m *MockGenesisContract) LastStateId(arg0 uint64) (*big.Int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LastStateId", arg0)
ret0, _ := ret[0].(*big.Int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LastStateId indicates an expected call of LastStateId.
func (mr *MockGenesisContractMockRecorder) LastStateId(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastStateId", reflect.TypeOf((*MockGenesisContract)(nil).LastStateId), arg0)
}

File diff suppressed because one or more lines are too long

15
consensus/bor/heimdall.go Normal file
View file

@ -0,0 +1,15 @@
package bor
import (
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
)
//go:generate mockgen -destination=../../tests/bor/mocks/IHeimdallClient.go -package=mocks . IHeimdallClient
type IHeimdallClient interface {
StateSyncEvents(fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error)
Span(spanID uint64) (*span.HeimdallSpan, error)
FetchLatestCheckpoint() (*checkpoint.Checkpoint, error)
Close()
}

View file

@ -0,0 +1,22 @@
package checkpoint
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// Checkpoint defines a response object type of bor checkpoint
type Checkpoint struct {
Proposer common.Address `json:"proposer"`
StartBlock *big.Int `json:"start_block"`
EndBlock *big.Int `json:"end_block"`
RootHash common.Hash `json:"root_hash"`
BorChainID string `json:"bor_chain_id"`
Timestamp uint64 `json:"timestamp"`
}
type CheckpointResponse struct {
Height string `json:"height"`
Result Checkpoint `json:"result"`
}

View file

@ -0,0 +1,243 @@
package heimdall
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sort"
"time"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
"github.com/ethereum/go-ethereum/log"
)
// errShutdownDetected is returned if a shutdown was detected
var errShutdownDetected = errors.New("shutdown detected")
const (
stateFetchLimit = 50
apiHeimdallTimeout = 5 * time.Second
)
type StateSyncEventsResponse struct {
Height string `json:"height"`
Result []*clerk.EventRecordWithTime `json:"result"`
}
type SpanResponse struct {
Height string `json:"height"`
Result span.HeimdallSpan `json:"result"`
}
type HeimdallClient struct {
urlString string
client http.Client
closeCh chan struct{}
}
func NewHeimdallClient(urlString string) *HeimdallClient {
return &HeimdallClient{
urlString: urlString,
client: http.Client{
Timeout: apiHeimdallTimeout,
},
closeCh: make(chan struct{}),
}
}
const (
fetchStateSyncEventsFormat = "from-id=%d&to-time=%d&limit=%d"
fetchStateSyncEventsPath = "clerk/event-record/list"
fetchLatestCheckpoint = "/checkpoints/latest"
fetchSpanFormat = "bor/span/%d"
)
func (h *HeimdallClient) StateSyncEvents(fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) {
eventRecords := make([]*clerk.EventRecordWithTime, 0)
for {
url, err := stateSyncURL(h.urlString, fromID, to)
if err != nil {
return nil, err
}
log.Info("Fetching state sync events", "queryParams", url.RawQuery)
response, err := FetchWithRetry[StateSyncEventsResponse](h.client, url, h.closeCh)
if err != nil {
return nil, err
}
if response == nil || response.Result == nil {
// status 204
break
}
eventRecords = append(eventRecords, response.Result...)
if len(response.Result) < stateFetchLimit {
break
}
fromID += uint64(stateFetchLimit)
}
sort.SliceStable(eventRecords, func(i, j int) bool {
return eventRecords[i].ID < eventRecords[j].ID
})
return eventRecords, nil
}
func (h *HeimdallClient) Span(spanID uint64) (*span.HeimdallSpan, error) {
url, err := spanURL(h.urlString, spanID)
if err != nil {
return nil, err
}
response, err := FetchWithRetry[SpanResponse](h.client, url, h.closeCh)
if err != nil {
return nil, err
}
return &response.Result, nil
}
// FetchLatestCheckpoint fetches the latest bor submitted checkpoint from heimdall
func (h *HeimdallClient) FetchLatestCheckpoint() (*checkpoint.Checkpoint, error) {
url, err := latestCheckpointURL(h.urlString)
if err != nil {
return nil, err
}
response, err := FetchWithRetry[checkpoint.CheckpointResponse](h.client, url, h.closeCh)
if err != nil {
return nil, err
}
return &response.Result, nil
}
// FetchWithRetry returns data from heimdall with retry
func FetchWithRetry[T any](client http.Client, url *url.URL, closeCh chan struct{}) (*T, error) {
// attempt counter
attempt := 1
result := new(T)
ctx, cancel := context.WithTimeout(context.Background(), apiHeimdallTimeout)
// request data once
body, err := internalFetch(ctx, client, url)
cancel()
if err == nil && body != nil {
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}
// create a new ticker for retrying the request
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
log.Info("Retrying again in 5 seconds to fetch data from Heimdall", "path", url.Path, "attempt", attempt)
attempt++
select {
case <-closeCh:
log.Debug("Shutdown detected, terminating request")
return nil, errShutdownDetected
case <-ticker.C:
ctx, cancel = context.WithTimeout(context.Background(), apiHeimdallTimeout)
body, err = internalFetch(ctx, client, url)
cancel()
if err == nil && body != nil {
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}
}
}
}
func spanURL(urlString string, spanID uint64) (*url.URL, error) {
return makeURL(urlString, fmt.Sprintf(fetchSpanFormat, spanID), "")
}
func stateSyncURL(urlString string, fromID uint64, to int64) (*url.URL, error) {
queryParams := fmt.Sprintf(fetchStateSyncEventsFormat, fromID, to, stateFetchLimit)
return makeURL(urlString, fetchStateSyncEventsPath, queryParams)
}
func latestCheckpointURL(urlString string) (*url.URL, error) {
return makeURL(urlString, fetchLatestCheckpoint, "")
}
func makeURL(urlString, rawPath, rawQuery string) (*url.URL, error) {
u, err := url.Parse(urlString)
if err != nil {
return nil, err
}
u.Path = rawPath
u.RawQuery = rawQuery
return u, err
}
// internal fetch method
func internalFetch(ctx context.Context, client http.Client, u *url.URL) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
// check status code
if res.StatusCode != 200 && res.StatusCode != 204 {
return nil, fmt.Errorf("Error while fetching data from Heimdall")
}
// unmarshall data from buffer
if res.StatusCode == 204 {
return nil, nil
}
// get response
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return body, nil
}
// Close sends a signal to stop the running process
func (h *HeimdallClient) Close() {
close(h.closeCh)
h.client.CloseIdleConnections()
}

View file

@ -0,0 +1,35 @@
package heimdall
import (
"testing"
)
func TestSpanURL(t *testing.T) {
t.Parallel()
url, err := spanURL("http://bor0", 1)
if err != nil {
t.Fatal("got an error", err)
}
const expected = "http://bor0/bor/span/1"
if url.String() != expected {
t.Fatalf("expected URL %q, got %q", url.String(), expected)
}
}
func TestStateSyncURL(t *testing.T) {
t.Parallel()
url, err := stateSyncURL("http://bor0", 10, 100)
if err != nil {
t.Fatal("got an error", err)
}
const expected = "http://bor0/clerk/event-record/list?from-id=10&to-time=100&limit=50"
if url.String() != expected {
t.Fatalf("expected URL %q, got %q", url.String(), expected)
}
}

View file

@ -0,0 +1,20 @@
package span
import (
"github.com/ethereum/go-ethereum/consensus/bor/valset"
)
// Span Bor represents a current bor span
type Span struct {
ID uint64 `json:"span_id" yaml:"span_id"`
StartBlock uint64 `json:"start_block" yaml:"start_block"`
EndBlock uint64 `json:"end_block" yaml:"end_block"`
}
// HeimdallSpan represents span from heimdall APIs
type HeimdallSpan struct {
Span
ValidatorSet valset.ValidatorSet `json:"validator_set" yaml:"validator_set"`
SelectedProducers []valset.Validator `json:"selected_producers" yaml:"selected_producers"`
ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"`
}

View file

@ -0,0 +1,200 @@
package span
import (
"context"
"encoding/hex"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/bor/abi"
"github.com/ethereum/go-ethereum/consensus/bor/api"
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"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/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
type ChainSpanner struct {
ethAPI api.Caller
validatorSet abi.ABI
chainConfig *params.ChainConfig
validatorContractAddress common.Address
}
func NewChainSpanner(ethAPI api.Caller, validatorSet abi.ABI, chainConfig *params.ChainConfig, validatorContractAddress common.Address) *ChainSpanner {
return &ChainSpanner{
ethAPI: ethAPI,
validatorSet: validatorSet,
chainConfig: chainConfig,
validatorContractAddress: validatorContractAddress,
}
}
// GetCurrentSpan get current span from contract
func (c *ChainSpanner) GetCurrentSpan(headerHash common.Hash) (*Span, error) {
// block
blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false)
// method
method := "getCurrentSpan"
data, err := c.validatorSet.Pack(method)
if err != nil {
log.Error("Unable to pack tx for getCurrentSpan", "error", err)
return nil, err
}
msgData := (hexutil.Bytes)(data)
toAddress := c.validatorContractAddress
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
// todo: would we like to have a timeout here?
result, err := c.ethAPI.Call(context.Background(), ethapi.TransactionArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
}, blockNr, nil)
if err != nil {
return nil, err
}
// span result
ret := new(struct {
Number *big.Int
StartBlock *big.Int
EndBlock *big.Int
})
if err := c.validatorSet.UnpackIntoInterface(ret, method, result); err != nil {
return nil, err
}
// create new span
span := Span{
ID: ret.Number.Uint64(),
StartBlock: ret.StartBlock.Uint64(),
EndBlock: ret.EndBlock.Uint64(),
}
return &span, nil
}
// GetCurrentValidators get current validators
func (c *ChainSpanner) GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// method
const method = "getBorValidators"
data, err := c.validatorSet.Pack(method, big.NewInt(0).SetUint64(blockNumber))
if err != nil {
log.Error("Unable to pack tx for getValidator", "error", err)
return nil, err
}
// call
msgData := (hexutil.Bytes)(data)
toAddress := c.validatorContractAddress
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
// block
blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false)
result, err := c.ethAPI.Call(ctx, ethapi.TransactionArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
}, blockNr, nil)
if err != nil {
panic(err)
}
var (
ret0 = new([]common.Address)
ret1 = new([]*big.Int)
)
out := &[]interface{}{
ret0,
ret1,
}
if err := c.validatorSet.UnpackIntoInterface(out, method, result); err != nil {
return nil, err
}
valz := make([]*valset.Validator, len(*ret0))
for i, a := range *ret0 {
valz[i] = &valset.Validator{
Address: a,
VotingPower: (*ret1)[i].Int64(),
}
}
return valz, nil
}
const method = "commitSpan"
func (c *ChainSpanner) CommitSpan(heimdallSpan HeimdallSpan, state *state.StateDB, header *types.Header, chainContext core.ChainContext) error {
// get validators bytes
validators := make([]valset.MinimalVal, 0, len(heimdallSpan.ValidatorSet.Validators))
for _, val := range heimdallSpan.ValidatorSet.Validators {
validators = append(validators, val.MinimalVal())
}
validatorBytes, err := rlp.EncodeToBytes(validators)
if err != nil {
return err
}
// get producers bytes
producers := make([]valset.MinimalVal, 0, len(heimdallSpan.SelectedProducers))
for _, val := range heimdallSpan.SelectedProducers {
producers = append(producers, val.MinimalVal())
}
producerBytes, err := rlp.EncodeToBytes(producers)
if err != nil {
return err
}
log.Info("✅ Committing new span",
"id", heimdallSpan.ID,
"startBlock", heimdallSpan.StartBlock,
"endBlock", heimdallSpan.EndBlock,
"validatorBytes", hex.EncodeToString(validatorBytes),
"producerBytes", hex.EncodeToString(producerBytes),
)
data, err := c.validatorSet.Pack(method,
big.NewInt(0).SetUint64(heimdallSpan.ID),
big.NewInt(0).SetUint64(heimdallSpan.StartBlock),
big.NewInt(0).SetUint64(heimdallSpan.EndBlock),
validatorBytes,
producerBytes,
)
if err != nil {
log.Error("Unable to pack tx for commitSpan", "error", err)
return err
}
// get system message
msg := statefull.GetSystemMessage(c.validatorContractAddress, data)
// apply message
_, err = statefull.ApplyMessage(msg, state, header, c.chainConfig, chainContext)
return err
}

View file

@ -2,12 +2,12 @@ package bor
func appendBytes32(data ...[]byte) []byte {
var result []byte
for _, v := range data {
paddedV, err := convertTo32(v)
if err == nil {
paddedV := convertTo32(v)
result = append(result, paddedV[:]...)
}
}
return result
}
@ -24,25 +24,29 @@ func nextPowerOfTwo(n uint64) uint64 {
n |= n >> 16
n |= n >> 32
n++
return n
}
func convertTo32(input []byte) (output [32]byte, err error) {
func convertTo32(input []byte) (output [32]byte) {
l := len(input)
if l > 32 || l == 0 {
return
}
copy(output[32-l:], input[:])
return
}
func convert(input []([32]byte)) [][]byte {
var output [][]byte
output := make([][]byte, 0, len(input))
for _, in := range input {
newInput := make([]byte, len(in[:]))
copy(newInput, in[:])
output = append(output, newInput)
}
return output
}

View file

@ -1,167 +0,0 @@
package bor
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sort"
"time"
"github.com/ethereum/go-ethereum/log"
)
var (
stateFetchLimit = 50
)
// ResponseWithHeight defines a response object type that wraps an original
// response with a height.
type ResponseWithHeight struct {
Height string `json:"height"`
Result json.RawMessage `json:"result"`
}
type IHeimdallClient interface {
Fetch(path string, query string) (*ResponseWithHeight, error)
FetchWithRetry(path string, query string) (*ResponseWithHeight, error)
FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error)
Close()
}
type HeimdallClient struct {
urlString string
client http.Client
closeCh chan struct{}
}
func NewHeimdallClient(urlString string) (*HeimdallClient, error) {
h := &HeimdallClient{
urlString: urlString,
client: http.Client{
Timeout: time.Duration(5 * time.Second),
},
closeCh: make(chan struct{}),
}
return h, nil
}
func (h *HeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error) {
eventRecords := make([]*EventRecordWithTime, 0)
for {
queryParams := fmt.Sprintf("from-id=%d&to-time=%d&limit=%d", fromID, to, stateFetchLimit)
log.Info("Fetching state sync events", "queryParams", queryParams)
response, err := h.FetchWithRetry("clerk/event-record/list", queryParams)
if err != nil {
return nil, err
}
var _eventRecords []*EventRecordWithTime
if response.Result == nil { // status 204
break
}
if err := json.Unmarshal(response.Result, &_eventRecords); err != nil {
return nil, err
}
eventRecords = append(eventRecords, _eventRecords...)
if len(_eventRecords) < stateFetchLimit {
break
}
fromID += uint64(stateFetchLimit)
}
sort.SliceStable(eventRecords, func(i, j int) bool {
return eventRecords[i].ID < eventRecords[j].ID
})
return eventRecords, nil
}
// Fetch fetches response from heimdall
func (h *HeimdallClient) Fetch(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
u, err := url.Parse(h.urlString)
if err != nil {
return nil, err
}
u.Path = rawPath
u.RawQuery = rawQuery
return h.internalFetch(u)
}
// FetchWithRetry returns data from heimdall with retry
func (h *HeimdallClient) FetchWithRetry(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
u, err := url.Parse(h.urlString)
if err != nil {
return nil, err
}
u.Path = rawPath
u.RawQuery = rawQuery
// attempt counter
attempt := 1
// request data once
res, err := h.internalFetch(u)
if err == nil && res != nil {
return res, nil
}
// create a new ticker for retrying the request
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
log.Info("Retrying again in 5 seconds to fetch data from Heimdall", "path", u.Path, "attempt", attempt)
attempt++
select {
case <-h.closeCh:
log.Debug("Shutdown detected, terminating request")
return nil, errShutdownDetected
case <-ticker.C:
res, err := h.internalFetch(u)
if err == nil && res != nil {
return res, nil
}
}
}
}
// internal fetch method
func (h *HeimdallClient) internalFetch(u *url.URL) (*ResponseWithHeight, error) {
res, err := h.client.Get(u.String())
if err != nil {
return nil, err
}
defer res.Body.Close()
// check status code
if res.StatusCode != 200 && res.StatusCode != 204 {
return nil, fmt.Errorf("Error while fetching data from Heimdall")
}
// unmarshall data from buffer
var response ResponseWithHeight
if res.StatusCode == 204 {
return &response, nil
}
// get response
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err := json.Unmarshal(body, &response); err != nil {
return nil, err
}
return &response, nil
}
// Close sends a signal to stop the running process
func (h *HeimdallClient) Close() {
close(h.closeCh)
h.client.CloseIdleConnections()
}

View file

@ -1,37 +1,29 @@
package bor
import (
"bytes"
"encoding/json"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
lru "github.com/hashicorp/golang-lru"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/params"
)
// Snapshot is the state of the authorization voting at a given point in time.
type Snapshot struct {
config *params.BorConfig // Consensus engine parameters to fine tune behavior
ethAPI *ethapi.PublicBlockChainAPI
sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover
Number uint64 `json:"number"` // Block number where the snapshot was created
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
ValidatorSet *ValidatorSet `json:"validatorSet"` // Validator set at this moment
ValidatorSet *valset.ValidatorSet `json:"validatorSet"` // Validator set at this moment
Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections
}
// signersAscending implements the sort interface to allow sorting a list of addresses
type signersAscending []common.Address
func (s signersAscending) Len() int { return len(s) }
func (s signersAscending) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }
func (s signersAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// newSnapshot creates a new snapshot with the specified startup parameters. This
// method does not initialize the set of recent signers, so only ever use if for
// the genesis block.
@ -40,37 +32,38 @@ func newSnapshot(
sigcache *lru.ARCCache,
number uint64,
hash common.Hash,
validators []*Validator,
ethAPI *ethapi.PublicBlockChainAPI,
validators []*valset.Validator,
) *Snapshot {
snap := &Snapshot{
config: config,
ethAPI: ethAPI,
sigcache: sigcache,
Number: number,
Hash: hash,
ValidatorSet: NewValidatorSet(validators),
ValidatorSet: valset.NewValidatorSet(validators),
Recents: make(map[uint64]common.Address),
}
return snap
}
// loadSnapshot loads an existing snapshot from the database.
func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash, ethAPI *ethapi.PublicBlockChainAPI) (*Snapshot, error) {
func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
blob, err := db.Get(append([]byte("bor-"), hash[:]...))
if err != nil {
return nil, err
}
snap := new(Snapshot)
if err := json.Unmarshal(blob, snap); err != nil {
return nil, err
}
snap.config = config
snap.sigcache = sigcache
snap.ethAPI = ethAPI
// update total voting power
if err := snap.ValidatorSet.updateTotalVotingPower(); err != nil {
if err := snap.ValidatorSet.UpdateTotalVotingPower(); err != nil {
return nil, err
}
@ -83,6 +76,7 @@ func (s *Snapshot) store(db ethdb.Database) error {
if err != nil {
return err
}
return db.Put(append([]byte("bor-"), s.Hash[:]...), blob)
}
@ -90,7 +84,6 @@ func (s *Snapshot) store(db ethdb.Database) error {
func (s *Snapshot) copy() *Snapshot {
cpy := &Snapshot{
config: s.config,
ethAPI: s.ethAPI,
sigcache: s.sigcache,
Number: s.Number,
Hash: s.Hash,
@ -115,6 +108,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
return nil, errOutOfRangeChain
}
}
if headers[0].Number.Uint64() != s.Number+1 {
return nil, errOutOfRangeChain
}
@ -126,7 +120,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
number := header.Number.Uint64()
// Delete the oldest signer from the recent list to allow it signing again
if number >= s.config.Sprint && number-s.config.Sprint >= 0 {
if number >= s.config.Sprint {
delete(snap.Recents, number-s.config.Sprint)
}
@ -153,15 +147,17 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
if err := validateHeaderExtraField(header.Extra); err != nil {
return nil, err
}
validatorBytes := header.Extra[extraVanity : len(header.Extra)-extraSeal]
// get validators from headers and use that for new validator set
newVals, _ := ParseValidators(validatorBytes)
newVals, _ := valset.ParseValidators(validatorBytes)
v := getUpdatedValidatorSet(snap.ValidatorSet.Copy(), newVals)
v.IncrementProposerPriority(1)
snap.ValidatorSet = v
}
}
snap.Number += uint64(len(headers))
snap.Hash = headers[len(headers)-1].Hash()
@ -173,10 +169,13 @@ func (s *Snapshot) GetSignerSuccessionNumber(signer common.Address) (int, error)
validators := s.ValidatorSet.Validators
proposer := s.ValidatorSet.GetProposer().Address
proposerIndex, _ := s.ValidatorSet.GetByAddress(proposer)
if proposerIndex == -1 {
return -1, &UnauthorizedProposerError{s.Number, proposer.Bytes()}
}
signerIndex, _ := s.ValidatorSet.GetByAddress(signer)
if signerIndex == -1 {
return -1, &UnauthorizedSignerError{s.Number, signer.Bytes()}
}
@ -187,6 +186,7 @@ func (s *Snapshot) GetSignerSuccessionNumber(signer common.Address) (int, error)
tempIndex = tempIndex + len(validators)
}
}
return tempIndex - proposerIndex, nil
}
@ -196,13 +196,14 @@ func (s *Snapshot) signers() []common.Address {
for _, sig := range s.ValidatorSet.Validators {
sigs = append(sigs, sig.Address)
}
return sigs
}
// Difficulty returns the difficulty for a particular signer at the current snapshot number
func (s *Snapshot) Difficulty(signer common.Address) uint64 {
// if signer is empty
if bytes.Compare(signer.Bytes(), common.Address{}.Bytes()) == 0 {
if signer == (common.Address{}) {
return 1
}

View file

@ -7,8 +7,11 @@ import (
"time"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
"github.com/ethereum/go-ethereum/common"
unique "github.com/ethereum/go-ethereum/common/set"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
)
const (
@ -16,8 +19,10 @@ const (
)
func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) {
t.Parallel()
validators := buildRandomValidatorSet(numVals)
validatorSet := NewValidatorSet(validators)
validatorSet := valset.NewValidatorSet(validators)
snap := Snapshot{
ValidatorSet: validatorSet,
}
@ -28,20 +33,24 @@ func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) {
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, 0, successionNumber)
}
func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) {
t.Parallel()
validators := buildRandomValidatorSet(numVals)
// sort validators by address, which is what NewValidatorSet also does
sort.Sort(ValidatorsByAddress(validators))
sort.Sort(valset.ValidatorsByAddress(validators))
proposerIndex := 32
signerIndex := 56
// give highest ProposerPriority to a particular val, so that they become the proposer
validators[proposerIndex].VotingPower = 200
snap := Snapshot{
ValidatorSet: NewValidatorSet(validators),
ValidatorSet: valset.NewValidatorSet(validators),
}
// choose a signer at an index greater than proposer index
@ -50,17 +59,20 @@ func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) {
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, signerIndex-proposerIndex, successionNumber)
}
func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) {
t.Parallel()
validators := buildRandomValidatorSet(numVals)
proposerIndex := 98
signerIndex := 11
// give highest ProposerPriority to a particular val, so that they become the proposer
validators[proposerIndex].VotingPower = 200
snap := Snapshot{
ValidatorSet: NewValidatorSet(validators),
ValidatorSet: valset.NewValidatorSet(validators),
}
// choose a signer at an index greater than proposer index
@ -69,29 +81,38 @@ func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) {
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, signerIndex+numVals-proposerIndex, successionNumber)
}
func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) {
t.Parallel()
validators := buildRandomValidatorSet(numVals)
snap := Snapshot{
ValidatorSet: NewValidatorSet(validators),
ValidatorSet: valset.NewValidatorSet(validators),
}
dummyProposerAddress := randomAddress()
snap.ValidatorSet.Proposer = &Validator{Address: dummyProposerAddress}
snap.ValidatorSet.Proposer = &valset.Validator{Address: dummyProposerAddress}
// choose any signer
signer := snap.ValidatorSet.Validators[3].Address
_, err := snap.GetSignerSuccessionNumber(signer)
assert.NotNil(t, err)
e, ok := err.(*UnauthorizedProposerError)
assert.True(t, ok)
assert.Equal(t, dummyProposerAddress.Bytes(), e.Proposer)
}
func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) {
t.Parallel()
validators := buildRandomValidatorSet(numVals)
snap := Snapshot{
ValidatorSet: NewValidatorSet(validators),
ValidatorSet: valset.NewValidatorSet(validators),
}
dummySignerAddress := randomAddress()
_, err := snap.GetSignerSuccessionNumber(dummySignerAddress)
@ -101,24 +122,78 @@ func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) {
assert.Equal(t, dummySignerAddress.Bytes(), e.Signer)
}
func buildRandomValidatorSet(numVals int) []*Validator {
// nolint: unparam
func buildRandomValidatorSet(numVals int) []*valset.Validator {
rand.Seed(time.Now().Unix())
validators := make([]*Validator, numVals)
validators := make([]*valset.Validator, numVals)
valAddrs := randomAddresses(numVals)
for i := 0; i < numVals; i++ {
validators[i] = &Validator{
Address: randomAddress(),
validators[i] = &valset.Validator{
Address: valAddrs[i],
// cannot process validators with voting power 0, hence +1
VotingPower: int64(rand.Intn(99) + 1),
}
}
// sort validators by address, which is what NewValidatorSet also does
sort.Sort(ValidatorsByAddress(validators))
sort.Sort(valset.ValidatorsByAddress(validators))
return validators
}
func randomAddress() common.Address {
bytes := make([]byte, 32)
rand.Read(bytes)
return common.BytesToAddress(bytes)
}
func randomAddresses(n int) []common.Address {
if n <= 0 {
return []common.Address{}
}
addrs := make([]common.Address, 0, n)
addrsSet := make(map[common.Address]struct{}, n)
var (
addr common.Address
exist bool
)
bytes := make([]byte, 32)
for {
rand.Read(bytes)
addr = common.BytesToAddress(bytes)
_, exist = addrsSet[addr]
if !exist {
addrs = append(addrs, addr)
addrsSet[addr] = struct{}{}
}
if len(addrs) == n {
return addrs
}
}
}
func TestRandomAddresses(t *testing.T) {
t.Parallel()
rapid.Check(t, func(t *rapid.T) {
length := rapid.IntMax(100).Draw(t, "length").(int)
addrs := randomAddresses(length)
addressSet := unique.New(addrs)
if len(addrs) != len(addressSet) {
t.Fatalf("length of unique addresses %d, expected %d", len(addressSet), len(addrs))
}
})
}

View file

@ -1,16 +1,17 @@
package bor
// Span represents a current bor span
type Span struct {
ID uint64 `json:"span_id" yaml:"span_id"`
StartBlock uint64 `json:"start_block" yaml:"start_block"`
EndBlock uint64 `json:"end_block" yaml:"end_block"`
}
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
)
// HeimdallSpan represents span from heimdall APIs
type HeimdallSpan struct {
Span
ValidatorSet ValidatorSet `json:"validator_set" yaml:"validator_set"`
SelectedProducers []Validator `json:"selected_producers" yaml:"selected_producers"`
ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"`
//go:generate mockgen -destination=./span_mock.go -package=bor . Spanner
type Spanner interface {
GetCurrentSpan(headerHash common.Hash) (*span.Span, error)
GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error)
CommitSpan(heimdallSpan span.HeimdallSpan, state *state.StateDB, header *types.Header, chainContext core.ChainContext) error
}

View file

@ -0,0 +1,84 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: Spanner)
// Package bor is a generated GoMock package.
package bor
import (
reflect "reflect"
common "github.com/ethereum/go-ethereum/common"
span "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
valset "github.com/ethereum/go-ethereum/consensus/bor/valset"
core "github.com/ethereum/go-ethereum/core"
state "github.com/ethereum/go-ethereum/core/state"
types "github.com/ethereum/go-ethereum/core/types"
gomock "github.com/golang/mock/gomock"
)
// MockSpanner is a mock of Spanner interface.
type MockSpanner struct {
ctrl *gomock.Controller
recorder *MockSpannerMockRecorder
}
// MockSpannerMockRecorder is the mock recorder for MockSpanner.
type MockSpannerMockRecorder struct {
mock *MockSpanner
}
// NewMockSpanner creates a new mock instance.
func NewMockSpanner(ctrl *gomock.Controller) *MockSpanner {
mock := &MockSpanner{ctrl: ctrl}
mock.recorder = &MockSpannerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSpanner) EXPECT() *MockSpannerMockRecorder {
return m.recorder
}
// CommitSpan mocks base method.
func (m *MockSpanner) CommitSpan(arg0 span.HeimdallSpan, arg1 *state.StateDB, arg2 *types.Header, arg3 core.ChainContext) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CommitSpan", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// CommitSpan indicates an expected call of CommitSpan.
func (mr *MockSpannerMockRecorder) CommitSpan(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitSpan", reflect.TypeOf((*MockSpanner)(nil).CommitSpan), arg0, arg1, arg2, arg3)
}
// GetCurrentSpan mocks base method.
func (m *MockSpanner) GetCurrentSpan(arg0 common.Hash) (*span.Span, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCurrentSpan", arg0)
ret0, _ := ret[0].(*span.Span)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentSpan indicates an expected call of GetCurrentSpan.
func (mr *MockSpannerMockRecorder) GetCurrentSpan(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSpan", reflect.TypeOf((*MockSpanner)(nil).GetCurrentSpan), arg0)
}
// GetCurrentValidators mocks base method.
func (m *MockSpanner) GetCurrentValidators(arg0 common.Hash, arg1 uint64) ([]*valset.Validator, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCurrentValidators", arg0, arg1)
ret0, _ := ret[0].([]*valset.Validator)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentValidators indicates an expected call of GetCurrentValidators.
func (mr *MockSpannerMockRecorder) GetCurrentValidators(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidators", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidators), arg0, arg1)
}

View file

@ -0,0 +1,118 @@
package statefull
import (
"math"
"math/big"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"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/core/vm"
"github.com/ethereum/go-ethereum/params"
)
var systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE")
type ChainContext struct {
Chain consensus.ChainHeaderReader
Bor consensus.Engine
}
func (c ChainContext) Engine() consensus.Engine {
return c.Bor
}
func (c ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
return c.Chain.GetHeader(hash, number)
}
// callmsg implements core.Message to allow passing it as a transaction simulator.
type Callmsg struct {
ethereum.CallMsg
}
func (m Callmsg) From() common.Address { return m.CallMsg.From }
func (m Callmsg) Nonce() uint64 { return 0 }
func (m Callmsg) CheckNonce() bool { return false }
func (m Callmsg) To() *common.Address { return m.CallMsg.To }
func (m Callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m Callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m Callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m Callmsg) Data() []byte { return m.CallMsg.Data }
// get system message
func GetSystemMessage(toAddress common.Address, data []byte) Callmsg {
return Callmsg{
ethereum.CallMsg{
From: systemAddress,
Gas: math.MaxUint64 / 2,
GasPrice: big.NewInt(0),
Value: big.NewInt(0),
To: &toAddress,
Data: data,
},
}
}
// apply message
func ApplyMessage(
msg Callmsg,
state *state.StateDB,
header *types.Header,
chainConfig *params.ChainConfig,
chainContext core.ChainContext,
) (uint64, error) {
initialGas := msg.Gas()
// Create a new context to be used in the EVM environment
blockContext := core.NewEVMBlockContext(header, chainContext, &header.Coinbase)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, state, chainConfig, vm.Config{})
// Apply the transaction to the current state (included in the env)
_, gasLeft, err := vmenv.Call(
vm.AccountRef(msg.From()),
*msg.To(),
msg.Data(),
msg.Gas(),
msg.Value(),
)
// Update the state with pending changes
if err != nil {
state.Finalise(true)
}
gasUsed := initialGas - gasLeft
return gasUsed, nil
}
func ApplyBorMessage(vmenv vm.EVM, msg Callmsg) (*core.ExecutionResult, error) {
initialGas := msg.Gas()
// Apply the transaction to the current state (included in the env)
ret, gasLeft, err := vmenv.Call(
vm.AccountRef(msg.From()),
*msg.To(),
msg.Data(),
msg.Gas(),
msg.Value(),
)
// Update the state with pending changes
if err != nil {
vmenv.StateDB.Finalise(true)
}
gasUsed := initialGas - gasLeft
return &core.ExecutionResult{
UsedGas: gasUsed,
Err: err,
ReturnData: ret,
}, nil
}

View file

@ -0,0 +1,11 @@
package bor
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
)
//go:generate mockgen -destination=./validators_getter_mock.go -package=bor . ValidatorsGetter
type ValidatorsGetter interface {
GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error)
}

View file

@ -0,0 +1,51 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: ValidatorsGetter)
// Package bor is a generated GoMock package.
package bor
import (
reflect "reflect"
common "github.com/ethereum/go-ethereum/common"
valset "github.com/ethereum/go-ethereum/consensus/bor/valset"
gomock "github.com/golang/mock/gomock"
)
// MockValidatorsGetter is a mock of ValidatorsGetter interface.
type MockValidatorsGetter struct {
ctrl *gomock.Controller
recorder *MockValidatorsGetterMockRecorder
}
// MockValidatorsGetterMockRecorder is the mock recorder for MockValidatorsGetter.
type MockValidatorsGetterMockRecorder struct {
mock *MockValidatorsGetter
}
// NewMockValidatorsGetter creates a new mock instance.
func NewMockValidatorsGetter(ctrl *gomock.Controller) *MockValidatorsGetter {
mock := &MockValidatorsGetter{ctrl: ctrl}
mock.recorder = &MockValidatorsGetterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockValidatorsGetter) EXPECT() *MockValidatorsGetterMockRecorder {
return m.recorder
}
// GetCurrentValidators mocks base method.
func (m *MockValidatorsGetter) GetCurrentValidators(arg0 common.Hash, arg1 uint64) ([]*valset.Validator, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCurrentValidators", arg0, arg1)
ret0, _ := ret[0].([]*valset.Validator)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentValidators indicates an expected call of GetCurrentValidators.
func (mr *MockValidatorsGetterMockRecorder) GetCurrentValidators(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidators", reflect.TypeOf((*MockValidatorsGetter)(nil).GetCurrentValidators), arg0, arg1)
}

View file

@ -0,0 +1,32 @@
package valset
import "fmt"
// TotalVotingPowerExceededError is returned when the maximum allowed total voting power is exceeded
type TotalVotingPowerExceededError struct {
Sum int64
Validators []*Validator
}
func (e *TotalVotingPowerExceededError) Error() string {
return fmt.Sprintf(
"Total voting power should be guarded to not exceed %v; got: %v; for validator set: %v",
MaxTotalVotingPower,
e.Sum,
e.Validators,
)
}
type InvalidStartEndBlockError struct {
Start uint64
End uint64
CurrentHeader uint64
}
func (e *InvalidStartEndBlockError) Error() string {
return fmt.Sprintf(
"Invalid parameters start: %d and end block: %d params",
e.Start,
e.End,
)
}

View file

@ -1,8 +1,7 @@
package bor
package valset
import (
"bytes"
// "encoding/json"
"errors"
"fmt"
"math/big"
@ -43,9 +42,12 @@ func (v *Validator) Cmp(other *Validator) *Validator {
if v == nil {
return other
}
if other == nil {
return v
}
// nolint:nestif
if v.ProposerPriority > other.ProposerPriority {
return v
} else if v.ProposerPriority < other.ProposerPriority {
@ -66,6 +68,7 @@ func (v *Validator) String() string {
if v == nil {
return "nil-Validator"
}
return fmt.Sprintf("Validator{%v Power:%v Priority:%v}",
v.Address.Hex(),
v.VotingPower,
@ -87,6 +90,7 @@ func (v *Validator) HeaderBytes() []byte {
result := make([]byte, 40)
copy(result[:20], v.Address.Bytes())
copy(result[20:], v.PowerBytes())
return result
}
@ -95,6 +99,7 @@ func (v *Validator) PowerBytes() []byte {
powerBytes := big.NewInt(0).SetInt64(v.VotingPower).Bytes()
result := make([]byte, 20)
copy(result[20-len(powerBytes):], powerBytes)
return result
}
@ -114,6 +119,7 @@ func ParseValidators(validatorsBytes []byte) ([]*Validator, error) {
}
result := make([]*Validator, len(validatorsBytes)/40)
for i := 0; i < len(validatorsBytes); i += 40 {
address := make([]byte, 20)
power := make([]byte, 20)
@ -142,6 +148,7 @@ func SortMinimalValByAddress(a []MinimalVal) []MinimalVal {
sort.Slice(a, func(i, j int) bool {
return bytes.Compare(a[i].Signer.Bytes(), a[j].Signer.Bytes()) < 0
})
return a
}
@ -150,5 +157,6 @@ func ValidatorsToMinimalValidators(vals []Validator) (minVals []MinimalVal) {
for _, val := range vals {
minVals = append(minVals, val.MinimalVal())
}
return
}

View file

@ -1,4 +1,4 @@
package bor
package valset
// Tendermint leader selection algorithm
@ -55,13 +55,16 @@ type ValidatorSet struct {
// function panics.
func NewValidatorSet(valz []*Validator) *ValidatorSet {
vals := &ValidatorSet{}
err := vals.updateWithChangeSet(valz, false)
if err != nil {
panic(fmt.Sprintf("cannot create validator set: %s", err))
}
if len(valz) > 0 {
vals.IncrementProposerPriority(1)
}
return vals
}
@ -72,9 +75,10 @@ func (vals *ValidatorSet) IsNilOrEmpty() bool {
// Increment ProposerPriority and update the proposer on a copy, and return it.
func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet {
copy := vals.Copy()
copy.IncrementProposerPriority(times)
return copy
validatorCopy := vals.Copy()
validatorCopy.IncrementProposerPriority(times)
return validatorCopy
}
// IncrementProposerPriority increments ProposerPriority of each validator and updates the
@ -84,6 +88,7 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) {
if vals.IsNilOrEmpty() {
panic("empty validator set")
}
if times <= 0 {
panic("Cannot call IncrementProposerPriority with non-positive times")
}
@ -120,6 +125,7 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) {
// NOTE: This may make debugging priority issues easier as well.
diff := computeMaxMinPriorityDiff(vals)
ratio := (diff + diffMax - 1) / diffMax
if diff > diffMax {
for _, val := range vals.Validators {
val.ProposerPriority = val.ProposerPriority / ratio
@ -145,10 +151,13 @@ func (vals *ValidatorSet) incrementProposerPriority() *Validator {
func (vals *ValidatorSet) computeAvgProposerPriority() int64 {
n := int64(len(vals.Validators))
sum := big.NewInt(0)
for _, val := range vals.Validators {
sum.Add(sum, big.NewInt(val.ProposerPriority))
}
avg := sum.Div(sum, big.NewInt(n))
if avg.IsInt64() {
return avg.Int64()
}
@ -162,17 +171,22 @@ func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 {
if vals.IsNilOrEmpty() {
panic("empty validator set")
}
max := int64(math.MinInt64)
min := int64(math.MaxInt64)
for _, v := range vals.Validators {
if v.ProposerPriority < min {
min = v.ProposerPriority
}
if v.ProposerPriority > max {
max = v.ProposerPriority
}
}
diff := max - min
if diff < 0 {
return -1 * diff
} else {
@ -185,6 +199,7 @@ func (vals *ValidatorSet) getValWithMostPriority() *Validator {
for _, val := range vals.Validators {
res = res.Cmp(val)
}
return res
}
@ -192,7 +207,9 @@ func (vals *ValidatorSet) shiftByAvgProposerPriority() {
if vals.IsNilOrEmpty() {
panic("empty validator set")
}
avgProposerPriority := vals.computeAvgProposerPriority()
for _, val := range vals.Validators {
val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority)
}
@ -203,10 +220,13 @@ func validatorListCopy(valsList []*Validator) []*Validator {
if valsList == nil {
return nil
}
valsCopy := make([]*Validator, len(valsList))
for i, val := range valsList {
valsCopy[i] = val.Copy()
}
return valsCopy
}
@ -225,6 +245,7 @@ func (vals *ValidatorSet) HasAddress(address []byte) bool {
idx := sort.Search(len(vals.Validators), func(i int) bool {
return bytes.Compare(address, vals.Validators[i].Address.Bytes()) <= 0
})
return idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address.Bytes(), address)
}
@ -234,9 +255,10 @@ func (vals *ValidatorSet) GetByAddress(address common.Address) (index int, val *
idx := sort.Search(len(vals.Validators), func(i int) bool {
return bytes.Compare(address.Bytes(), vals.Validators[i].Address.Bytes()) <= 0
})
if idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address.Bytes(), address.Bytes()) {
if idx < len(vals.Validators) && vals.Validators[idx].Address == address {
return idx, vals.Validators[idx].Copy()
}
return -1, nil
}
@ -247,7 +269,9 @@ func (vals *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator)
if index < 0 || index >= len(vals.Validators) {
return nil, nil
}
val = vals.Validators[index]
return val.Address.Bytes(), val.Copy()
}
@ -257,8 +281,7 @@ func (vals *ValidatorSet) Size() int {
}
// Force recalculation of the set's total voting power.
func (vals *ValidatorSet) updateTotalVotingPower() error {
func (vals *ValidatorSet) UpdateTotalVotingPower() error {
sum := int64(0)
for _, val := range vals.Validators {
// mind overflow
@ -267,7 +290,9 @@ func (vals *ValidatorSet) updateTotalVotingPower() error {
return &TotalVotingPowerExceededError{sum, vals.Validators}
}
}
vals.totalVotingPower = sum
return nil
}
@ -276,11 +301,13 @@ func (vals *ValidatorSet) updateTotalVotingPower() error {
func (vals *ValidatorSet) TotalVotingPower() int64 {
if vals.totalVotingPower == 0 {
log.Info("invoking updateTotalVotingPower before returning it")
if err := vals.updateTotalVotingPower(); err != nil {
if err := vals.UpdateTotalVotingPower(); err != nil {
// Can/should we do better?
panic(err)
}
}
return vals.totalVotingPower
}
@ -290,9 +317,11 @@ func (vals *ValidatorSet) GetProposer() (proposer *Validator) {
if len(vals.Validators) == 0 {
return nil
}
if vals.Proposer == nil {
vals.Proposer = vals.findProposer()
}
return vals.Proposer.Copy()
}
@ -303,6 +332,7 @@ func (vals *ValidatorSet) findProposer() *Validator {
proposer = proposer.Cmp(val)
}
}
return proposer
}
@ -343,6 +373,7 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e
removals = make([]*Validator, 0, len(changes))
updates = make([]*Validator, 0, len(changes))
var prevAddr common.Address
// Scan changes by address and append valid validators to updates or removals lists.
@ -351,22 +382,27 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e
err = fmt.Errorf("duplicate entry %v in %v", valUpdate, changes)
return nil, nil, err
}
if valUpdate.VotingPower < 0 {
err = fmt.Errorf("voting power can't be negative: %v", valUpdate)
return nil, nil, err
}
if valUpdate.VotingPower > MaxTotalVotingPower {
err = fmt.Errorf("to prevent clipping/ overflow, voting power can't be higher than %v: %v ",
MaxTotalVotingPower, valUpdate)
return nil, nil, err
}
if valUpdate.VotingPower == 0 {
removals = append(removals, valUpdate)
} else {
updates = append(updates, valUpdate)
}
prevAddr = valUpdate.Address
}
return updates, removals, err
}
@ -382,12 +418,12 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e
// by processChanges for duplicates and invalid values.
// No changes are made to the validator set 'vals'.
func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVotingPower int64, numNewValidators int, err error) {
updatedTotalVotingPower = vals.TotalVotingPower()
for _, valUpdate := range updates {
address := valUpdate.Address
_, val := vals.GetByAddress(address)
if val == nil {
// New validator, add its voting power the the total.
updatedTotalVotingPower += valUpdate.VotingPower
@ -396,11 +432,14 @@ func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVoting
// Updated validator, add the difference in power to the total.
updatedTotalVotingPower += valUpdate.VotingPower - val.VotingPower
}
overflow := updatedTotalVotingPower > MaxTotalVotingPower
if overflow {
err = fmt.Errorf(
"failed to add/update validator %v, total voting power would exceed the max allowed %v",
valUpdate, MaxTotalVotingPower)
return 0, 0, err
}
}
@ -414,10 +453,10 @@ func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVoting
// 'updates' parameter must be a list of unique validators to be added or updated.
// No changes are made to the validator set 'vals'.
func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotalVotingPower int64) {
for _, valUpdate := range updates {
address := valUpdate.Address
_, val := vals.GetByAddress(address)
if val == nil {
// add val
// Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't
@ -432,7 +471,6 @@ func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotal
valUpdate.ProposerPriority = val.ProposerPriority
}
}
}
// Merges the vals' validator list with the updates list.
@ -440,7 +478,6 @@ func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotal
// Expects updates to be a list of updates sorted by address with no duplicates or errors,
// must have been validated with verifyUpdates() and priorities computed with computeNewPriorities().
func (vals *ValidatorSet) applyUpdates(updates []*Validator) {
existing := vals.Validators
merged := make([]*Validator, len(existing)+len(updates))
i := 0
@ -478,24 +515,25 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) {
// Checks that the validators to be removed are part of the validator set.
// No changes are made to the validator set 'vals'.
func verifyRemovals(deletes []*Validator, vals *ValidatorSet) error {
for _, valUpdate := range deletes {
address := valUpdate.Address
_, val := vals.GetByAddress(address)
if val == nil {
return fmt.Errorf("failed to find validator %X to remove", address)
}
}
if len(deletes) > len(vals.Validators) {
panic("more deletes than validators")
}
return nil
}
// Removes the validators specified in 'deletes' from validator set 'vals'.
// Should not fail as verification has been done before.
func (vals *ValidatorSet) applyRemovals(deletes []*Validator) {
existing := vals.Validators
merged := make([]*Validator, len(existing)-len(deletes))
@ -509,6 +547,7 @@ func (vals *ValidatorSet) applyRemovals(deletes []*Validator) {
merged[i] = existing[0]
i++
}
existing = existing[1:]
}
@ -526,7 +565,6 @@ func (vals *ValidatorSet) applyRemovals(deletes []*Validator) {
// are not allowed and will trigger an error if present in 'changes'.
// The 'allowDeletes' flag is set to false by NewValidatorSet() and to true by UpdateWithChangeSet().
func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes bool) error {
if len(changes) <= 0 {
return nil
}
@ -564,7 +602,7 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes
vals.applyUpdates(updates)
vals.applyRemovals(deletes)
if err := vals.updateTotalVotingPower(); err != nil {
if err := vals.UpdateTotalVotingPower(); err != nil {
return err
}
@ -596,19 +634,19 @@ func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error {
func IsErrTooMuchChange(err error) bool {
switch err.(type) {
case errTooMuchChange:
case tooMuchChangeError:
return true
default:
return false
}
}
type errTooMuchChange struct {
type tooMuchChangeError struct {
got int64
needed int64
}
func (e errTooMuchChange) Error() string {
func (e tooMuchChangeError) Error() string {
return fmt.Sprintf("Invalid commit -- insufficient old voting power: got %v, needed %v", e.got, e.needed)
}
@ -622,11 +660,14 @@ func (vals *ValidatorSet) StringIndented(indent string) string {
if vals == nil {
return "nil-ValidatorSet"
}
var valStrings []string
vals.Iterate(func(index int, val *Validator) bool {
valStrings = append(valStrings, val.String())
return false
})
return fmt.Sprintf(`ValidatorSet{
%s Proposer: %v
%s Validators:
@ -636,7 +677,6 @@ func (vals *ValidatorSet) StringIndented(indent string) string {
indent,
indent, strings.Join(valStrings, "\n"+indent+" "),
indent)
}
//-------------------------------------
@ -668,6 +708,7 @@ func safeAdd(a, b int64) (int64, bool) {
} else if b < 0 && a < math.MinInt64-b {
return -1, true
}
return a + b, false
}
@ -677,6 +718,7 @@ func safeSub(a, b int64) (int64, bool) {
} else if b < 0 && a > math.MaxInt64+b {
return -1, true
}
return a - b, false
}
@ -686,8 +728,10 @@ func safeAddClip(a, b int64) int64 {
if b < 0 {
return math.MinInt64
}
return math.MaxInt64
}
return c
}
@ -697,7 +741,9 @@ func safeSubClip(a, b int64) int64 {
if b > 0 {
return math.MinInt64
}
return math.MaxInt64
}
return c
}

View file

@ -70,7 +70,7 @@ func TestRemoteNotify(t *testing.T) {
if want := common.BytesToHash(target.Bytes()).Hex(); work[2] != want {
t.Errorf("work packet target mismatch: have %s, want %s", work[2], want)
}
case <-time.After(3 * time.Second):
case <-time.After(5 * time.Second):
t.Fatalf("notification timed out")
}
}

View file

@ -45,12 +45,14 @@ type Merger struct {
// NewMerger creates a new Merger which stores its transition status in the provided db.
func NewMerger(db ethdb.KeyValueStore) *Merger {
var status transitionStatus
blob := rawdb.ReadTransitionStatus(db)
if len(blob) != 0 {
if err := rlp.DecodeBytes(blob, &status); err != nil {
log.Crit("Failed to decode the transition status", "err", err)
}
}
return &Merger{
db: db,
status: status,

View file

@ -305,7 +305,8 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
}
makeChainForBench(db, full, count)
db.Close()
cacheConfig := *defaultCacheConfig
cacheConfig := *DefaultCacheConfig
cacheConfig.TrieDirtyDisabled = true
b.ReportAllocs()

View file

@ -27,6 +27,8 @@ import (
"sync/atomic"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/common/prque"
@ -43,7 +45,6 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru"
)
var (
@ -133,9 +134,9 @@ type CacheConfig struct {
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
}
// defaultCacheConfig are the default caching values if none are specified by the
// DefaultCacheConfig are the default caching values if none are specified by the
// user (also used during testing).
var defaultCacheConfig = &CacheConfig{
var DefaultCacheConfig = &CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
@ -222,7 +223,7 @@ type BlockChain struct {
// and Processor.
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) {
if cacheConfig == nil {
cacheConfig = defaultCacheConfig
cacheConfig = DefaultCacheConfig
}
bodyCache, _ := lru.New(bodyCacheLimit)
bodyRLPCache, _ := lru.New(bodyCacheLimit)

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
@ -397,10 +398,25 @@ func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscr
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
}
// Snaps retrieves the snapshot tree.
func (bc *BlockChain) Snaps() *snapshot.Tree {
return bc.snaps
}
// DB retrieves the blockchain database.
func (bc *BlockChain) DB() ethdb.Database {
return bc.db
}
//
// Bor related changes
//
type BorStateSyncer interface {
SetStateSync(stateData []*types.StateSyncData)
SubscribeStateSyncEvent(ch chan<- StateSyncEvent) event.Subscription
}
// SetStateSync set sync data in state_data
func (bc *BlockChain) SetStateSync(stateData []*types.StateSyncData) {
bc.stateSyncData = stateData

View file

@ -920,7 +920,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
archiveDb, delfn := makeDb()
defer delfn()
archiveCaching := *defaultCacheConfig
archiveCaching := *DefaultCacheConfig
archiveCaching.TrieDirtyDisabled = true
archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
)
@ -335,6 +336,9 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
type fakeChainReader struct {
config *params.ChainConfig
stateSyncData []*types.StateSyncData
stateSyncFeed event.Feed
scope event.SubscriptionScope
}
// Config returns the chain configuration.
@ -348,3 +352,13 @@ func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil }
// SetStateSync set sync data in state_data
func (cr *fakeChainReader) SetStateSync(stateData []*types.StateSyncData) {
cr.stateSyncData = stateData
}
// SubscribeStateSyncEvent registers a subscription of StateSyncEvent.
func (cr *fakeChainReader) SubscribeStateSyncEvent(ch chan<- StateSyncEvent) event.Subscription {
return cr.scope.Track(cr.stateSyncFeed.Subscribe(ch))
}

View file

@ -21,6 +21,8 @@ import (
"math/big"
"testing"
"golang.org/x/crypto/sha3"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
@ -32,7 +34,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3"
)
// TestStateProcessorErrors tests the output from the 'core' errors
@ -308,7 +309,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header := &types.Header{
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
Difficulty: engine.CalcDifficulty(&fakeChainReader{config: config}, parent.Time()+10, &types.Header{
Number: parent.Number(),
Time: parent.Time(),
Difficulty: parent.Difficulty(),

View file

@ -18,7 +18,7 @@
// the database in some strange state with gaps in the chain, nor with block data
// dangling in the future.
package core
package tests
import (
"io/ioutil"
@ -27,12 +27,22 @@ import (
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/bor"
"github.com/ethereum/go-ethereum/consensus/bor/api"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/tests/bor/mocks"
)
// Tests a recovery for a short canonical chain where a recent block was already
@ -1750,7 +1760,17 @@ func testLongReorgedSnapSyncingDeepRepair(t *testing.T, snapshots bool) {
}, snapshots)
}
var (
testKey1, _ = crypto.GenerateKey()
testAddress1 = crypto.PubkeyToAddress(testKey1.PublicKey)
testKey2, _ = crypto.GenerateKey()
testAddress2 = crypto.PubkeyToAddress(testKey2.PublicKey) //nolint:unused,varcheck
)
func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
t.Skip("need to add a proper signer for Bor consensus")
// It's hard to follow the test case, visualize the input
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
// fmt.Println(tt.dump(true))
@ -1768,47 +1788,104 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
}
defer db.Close() // Might double close, should be fine
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ethAPIMock := api.NewMockCaller(ctrl)
ethAPIMock.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
spanner := bor.NewMockSpanner(ctrl)
spanner.EXPECT().GetCurrentValidators(gomock.Any(), gomock.Any()).Return([]*valset.Validator{
{
ID: 0,
Address: common.Address{0x1},
VotingPower: 100,
ProposerPriority: 0,
},
}, nil).AnyTimes()
heimdallClientMock := mocks.NewMockIHeimdallClient(ctrl)
heimdallClientMock.EXPECT().Close().Times(1)
contractMock := bor.NewMockGenesisContract(ctrl)
// Initialize a fresh chain
var (
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
engine = ethash.NewFullFaker()
config = &CacheConfig{
gspec = &core.Genesis{
Config: params.BorUnittestChainConfig,
BaseFee: big.NewInt(params.InitialBaseFee),
}
config = &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0, // Disable snapshot by default
}
)
engine := miner.NewFakeBor(t, db, params.BorUnittestChainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock)
defer engine.Close()
engineBorInternal, ok := engine.(*bor.Bor)
if ok {
gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength)
copy(gspec.ExtraData[32:32+common.AddressLength], testAddress1.Bytes())
engineBorInternal.Authorize(testAddress1, func(account accounts.Account, s string, data []byte) ([]byte, error) {
return crypto.Sign(crypto.Keccak256(data), testKey1)
})
}
genesis := gspec.MustCommit(db)
if snapshots {
config.SnapshotLimit = 256
config.SnapshotWait = true
}
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
chain, err := core.NewBlockChain(db, config, params.BorUnittestChainConfig, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create chain: %v", err)
}
// If sidechain blocks are needed, make a light chain and import it
var sideblocks types.Blocks
if tt.sidechainBlocks > 0 {
sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0x01})
sideblocks, _ = core.GenerateChain(params.BorUnittestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *core.BlockGen) {
b.SetCoinbase(testAddress1)
if bor.IsSprintStart(b.Number().Uint64(), params.BorUnittestChainConfig.Bor.Sprint) {
b.SetExtra(gspec.ExtraData)
} else {
b.SetExtra(make([]byte, 32+crypto.SignatureLength))
}
})
if _, err := chain.InsertChain(sideblocks); err != nil {
t.Fatalf("Failed to import side chain: %v", err)
}
}
canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
canonblocks, _ := core.GenerateChain(params.BorUnittestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{0x02})
b.SetDifficulty(big.NewInt(1000000))
if bor.IsSprintStart(b.Number().Uint64(), params.BorUnittestChainConfig.Bor.Sprint) {
b.SetExtra(gspec.ExtraData)
} else {
b.SetExtra(make([]byte, 32+crypto.SignatureLength))
}
})
if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err)
}
if tt.commitBlock > 0 {
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
err = chain.StateCache().TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
if err != nil {
t.Fatal("on trieDB.Commit", err)
}
if snapshots {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
if err := chain.Snaps().Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err)
}
}
@ -1837,7 +1914,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
}
defer db.Close()
newChain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
newChain, err := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
@ -1888,19 +1965,21 @@ func TestIssue23496(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create temporary datadir: %v", err)
}
os.RemoveAll(datadir)
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
defer db.Close() // Might double close, should be fine
// Initialize a fresh chain
var (
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
engine = ethash.NewFullFaker()
config = &CacheConfig{
config = &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
@ -1908,11 +1987,13 @@ func TestIssue23496(t *testing.T) {
SnapshotWait: true,
}
)
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create chain: %v", err)
}
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), 4, func(i int, b *BlockGen) {
blocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), 4, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{0x02})
b.SetDifficulty(big.NewInt(1000000))
})
@ -1921,13 +2002,18 @@ func TestIssue23496(t *testing.T) {
if _, err := chain.InsertChain(blocks[:1]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err)
}
chain.stateCache.TrieDB().Commit(blocks[0].Root(), true, nil)
err = chain.StateCache().TrieDB().Commit(blocks[0].Root(), true, nil)
if err != nil {
t.Fatal("on trieDB.Commit", err)
}
// Insert block B2 and commit the snapshot into disk
if _, err := chain.InsertChain(blocks[1:2]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err)
}
if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil {
if err := chain.Snaps().Cap(blocks[1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err)
}
@ -1935,7 +2021,11 @@ func TestIssue23496(t *testing.T) {
if _, err := chain.InsertChain(blocks[2:3]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err)
}
chain.stateCache.TrieDB().Commit(blocks[2].Root(), true, nil)
err = chain.StateCache().TrieDB().Commit(blocks[2].Root(), true, nil)
if err != nil {
t.Fatal("on trieDB.Commit", err)
}
// Insert the remaining blocks
if _, err := chain.InsertChain(blocks[3:]); err != nil {
@ -1950,20 +2040,24 @@ func TestIssue23496(t *testing.T) {
if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
defer db.Close()
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
chain, err = core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
defer chain.Stop()
if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) {
t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4)
}
if head := chain.CurrentFastBlock(); head.NumberU64() != uint64(4) {
t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), uint64(4))
}
if head := chain.CurrentBlock(); head.NumberU64() != uint64(1) {
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), uint64(1))
}
@ -1972,15 +2066,19 @@ func TestIssue23496(t *testing.T) {
if _, err := chain.InsertChain(blocks[1:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err)
}
if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) {
t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4)
}
if head := chain.CurrentFastBlock(); head.NumberU64() != uint64(4) {
t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), uint64(4))
}
if head := chain.CurrentBlock(); head.NumberU64() != uint64(4) {
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), uint64(4))
}
if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil {
t.Error("Failed to regenerate the snapshot of known state")
}

View file

@ -17,7 +17,7 @@
// Tests that setting the chain head backwards doesn't leave the database in some
// strange state with gaps in the chain, nor with block data dangling in the future.
package core
package tests
import (
"fmt"
@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@ -1959,79 +1960,98 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
if err != nil {
t.Fatalf("Failed to create temporary datadir: %v", err)
}
os.RemoveAll(datadir)
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
defer db.Close()
// Initialize a fresh chain
var (
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
engine = ethash.NewFullFaker()
config = &CacheConfig{
config = &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0, // Disable snapshot
}
)
if snapshots {
config.SnapshotLimit = 256
config.SnapshotWait = true
}
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create chain: %v", err)
}
// If sidechain blocks are needed, make a light chain and import it
var sideblocks types.Blocks
if tt.sidechainBlocks > 0 {
sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
sideblocks, _ = core.GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{0x01})
})
if _, err := chain.InsertChain(sideblocks); err != nil {
t.Fatalf("Failed to import side chain: %v", err)
}
}
canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
canonblocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{0x02})
b.SetDifficulty(big.NewInt(1000000))
})
if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err)
}
if tt.commitBlock > 0 {
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
err = chain.StateCache().TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
if err != nil {
t.Fatal("on trieDB.Commit", err)
}
if snapshots {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
if err := chain.Snaps().Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err)
}
}
}
if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err)
}
// Manually dereference anything not committed to not have to work with 128+ tries
for _, block := range sideblocks {
chain.stateCache.TrieDB().Dereference(block.Root())
chain.StateCache().TrieDB().Dereference(block.Root())
}
for _, block := range canonblocks {
chain.stateCache.TrieDB().Dereference(block.Root())
chain.StateCache().TrieDB().Dereference(block.Root())
}
// Force run a freeze cycle
type freezer interface {
Freeze(threshold uint64) error
Ancients() (uint64, error)
}
db.(freezer).Freeze(tt.freezeThreshold)
// Set the simulated pivot block
if tt.pivotBlock != nil {
rawdb.WriteLastPivotNumber(db, *tt.pivotBlock)
}
// Set the head of the chain back to the requested number
chain.SetHead(tt.setheadBlock)
@ -2044,12 +2064,15 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader {
t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader)
}
if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock {
t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock)
}
if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock {
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock)
}
if frozen, err := db.(freezer).Ancients(); err != nil {
t.Errorf("Failed to retrieve ancient count: %v\n", err)
} else if int(frozen) != tt.expFrozen {
@ -2059,7 +2082,8 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
// verifyNoGaps checks that there are no gaps after the initial set of blocks in
// the database and errors if found.
func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) {
//nolint:gocognit
func verifyNoGaps(t *testing.T, chain *core.BlockChain, canonical bool, inserted types.Blocks) {
t.Helper()
var end uint64
@ -2111,7 +2135,8 @@ func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted type
// verifyCutoff checks that there are no chain data available in the chain after
// the specified limit, but that it is available before.
func verifyCutoff(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks, head int) {
//nolint:gocognit
func verifyCutoff(t *testing.T, chain *core.BlockChain, canonical bool, inserted types.Blocks, head int) {
t.Helper()
for i := 1; i <= len(inserted); i++ {

View file

@ -17,7 +17,7 @@
// Tests that abnormal program termination (i.e.crash) and restart can recovery
// the snapshot properly if the snapshot is enabled.
package core
package tests
import (
"bytes"
@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@ -57,7 +58,9 @@ type snapshotTestBasic struct {
engine consensus.Engine
}
func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) {
func (basic *snapshotTestBasic) prepare(t *testing.T) (*core.BlockChain, []*types.Block) {
t.Helper()
// Create a temporary persistent database
datadir, err := ioutil.TempDir("", "")
if err != nil {
@ -71,20 +74,22 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
}
// Initialize a fresh chain
var (
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
engine = ethash.NewFullFaker()
gendb = rawdb.NewMemoryDatabase()
// Snapshot is enabled, the first snapshot is created from the Genesis.
// The snapshot memory allowance is 256MB, it means no snapshot flush
// will happen during the block insertion.
cacheConfig = defaultCacheConfig
cacheConfig = core.DefaultCacheConfig
)
chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
chain, err := core.NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create chain: %v", err)
}
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *BlockGen) {})
blocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *core.BlockGen) {})
// Insert the blocks with configured settings.
var breakpoints []uint64
@ -93,27 +98,39 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
} else {
breakpoints = append(breakpoints, basic.commitBlock, basic.snapshotBlock)
}
var startPoint uint64
for _, point := range breakpoints {
if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err)
}
startPoint = point
if basic.commitBlock > 0 && basic.commitBlock == point {
chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil)
err = chain.StateCache().TrieDB().Commit(blocks[point-1].Root(), true, nil)
if err != nil {
t.Fatal("on trieDB.Commit", err)
}
}
if basic.snapshotBlock > 0 && basic.snapshotBlock == point {
// Flushing the entire snap tree into the disk, the
// relevant (a) snapshot root and (b) snapshot generator
// will be persisted atomically.
chain.snaps.Cap(blocks[point-1].Root(), 0)
diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root()
err = chain.Snaps().Cap(blocks[point-1].Root(), 0)
if err != nil {
t.Fatal("on Snaps.Cap", err)
}
diskRoot, blockRoot := chain.Snaps().DiskRoot(), blocks[point-1].Root()
if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) {
t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot)
}
}
}
if _, err := chain.InsertChain(blocks[startPoint:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err)
}
@ -123,10 +140,13 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
basic.db = db
basic.gendb = gendb
basic.engine = engine
return chain, blocks
}
func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) {
func (basic *snapshotTestBasic) verify(t *testing.T, chain *core.BlockChain, blocks []*types.Block) {
t.Helper()
// Iterate over all the remaining blocks and ensure there are no gaps
verifyNoGaps(t, chain, true, blocks)
verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks)
@ -134,9 +154,11 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [
if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadHeader {
t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadHeader)
}
if head := chain.CurrentFastBlock(); head.NumberU64() != basic.expHeadFastBlock {
t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadFastBlock)
}
if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock {
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock)
}
@ -145,12 +167,12 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [
block := chain.GetBlockByNumber(basic.expSnapshotBottom)
if block == nil {
t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
} else if !bytes.Equal(chain.Snaps().DiskRoot().Bytes(), block.Root().Bytes()) {
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.Snaps().DiskRoot())
}
// Check the snapshot, ensure it's integrated
if err := chain.snaps.Verify(block.Root()); err != nil {
if err := chain.Snaps().Verify(block.Root()); err != nil {
t.Errorf("The disk layer is not integrated %v", err)
}
}
@ -223,10 +245,12 @@ func (snaptest *snapshotTest) test(t *testing.T) {
// Restart the chain normally
chain.Stop()
newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
defer newchain.Stop()
snaptest.verify(t, newchain, blocks)
@ -245,7 +269,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
chain, blocks := snaptest.prepare(t)
// Pull the plug on the database, simulating a hard crash
db := chain.db
db := chain.DB()
db.Close()
// Start a new blockchain back up and see where the repair leads us
@ -259,13 +283,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
// the crash, we do restart twice here: one after the crash and one
// after the normal stop. It's used to ensure the broken snapshot
// can be detected all the time.
newchain, err := NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err := core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
newchain.Stop()
newchain, err = NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err = core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
@ -292,27 +316,31 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
// Insert blocks without enabling snapshot if gapping is required.
chain.Stop()
gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *BlockGen) {})
gappedBlocks, _ := core.GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *core.BlockGen) {})
// Insert a few more blocks without enabling snapshot
var cacheConfig = &CacheConfig{
var cacheConfig = &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
}
newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err := core.NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
newchain.InsertChain(gappedBlocks)
newchain.Stop()
// Restart the chain with enabling the snapshot
newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
defer newchain.Stop()
snaptest.verify(t, newchain, blocks)
@ -337,7 +365,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) {
chain.SetHead(snaptest.setHead)
chain.Stop()
newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
@ -368,11 +396,12 @@ func (snaptest *restartCrashSnapshotTest) test(t *testing.T) {
// and state committed.
chain.Stop()
newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {})
newBlocks, _ := core.GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *core.BlockGen) {})
newchain.InsertChain(newBlocks)
// Commit the entire snapshot into the disk if requested. Note only
@ -385,7 +414,7 @@ func (snaptest *restartCrashSnapshotTest) test(t *testing.T) {
// journal and latest state will be committed
// Restart the chain after the crash
newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
@ -414,38 +443,42 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
// and state committed.
chain.Stop()
config := &CacheConfig{
config := &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
}
newchain, err := NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err := core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {})
newBlocks, _ := core.GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *core.BlockGen) {})
newchain.InsertChain(newBlocks)
newchain.Stop()
// Restart the chain, the wiper should starts working
config = &CacheConfig{
config = &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 256,
SnapshotWait: false, // Don't wait rebuild
}
newchain, err = NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
_, err = core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
// Simulate the blockchain crash.
newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to recreate chain: %v", err)
}
snaptest.verify(t, newchain, blocks)
}

View file

@ -74,6 +74,8 @@ type StateDB interface {
AddPreimage(common.Hash, []byte)
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
Finalise(bool)
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM

View file

@ -5,18 +5,16 @@
- [Configuration file](./config.md)
## Deprecation notes
## Additional notes
- The new entrypoint to run the Bor client is ```server```.
```
$ bor server
$ bor server <flags>
```
- Toml files to configure nodes are being deprecated. Currently, we only allow for static and trusted nodes to be configured using toml files.
- Toml files used earlier just to configure static/trusted nodes are being deprecated. Instead, a toml file now can be used instead of flags and can contain all configuration for the node to run. The link to a sample config file is given above. To simply run bor with a configuration file, the following command can be used.
```
$ bor server --config ./legacy.toml
$ bor server --config <path_to_config.toml>
```
- ```Admin```, ```Personal``` and account related endpoints in ```Eth``` are being removed from the JsonRPC interface. Some of this functionality will be moved to the new GRPC server for operational tasks.

View file

@ -1,24 +1,35 @@
# Command line interface
# Bor command line interface
## Commands
- [```server```](./server.md)
- [```debug```](./debug.md)
- [```account```](./account.md)
- [```account new```](./account_new.md)
- [```account import```](./account_import.md)
- [```account list```](./account_list.md)
- [```account import```](./account_import.md)
- [```account new```](./account_new.md)
- [```attach```](./attach.md)
- [```bootnode```](./bootnode.md)
- [```chain```](./chain.md)
- [```chain sethead```](./chain_sethead.md)
- [```chain watch```](./chain_watch.md)
- [```debug```](./debug.md)
- [```debug block```](./debug_block.md)
- [```debug pprof```](./debug_pprof.md)
- [```dumpconfig```](./dumpconfig.md)
- [```fingerprint```](./fingerprint.md)
- [```peers```](./peers.md)
- [```peers add```](./peers_add.md)
@ -29,8 +40,10 @@
- [```peers status```](./peers_status.md)
- [```removedb```](./removedb.md)
- [```server```](./server.md)
- [```status```](./status.md)
- [```chain watch```](./chain_watch.md)
- [```version```](./version.md)

View file

@ -1,4 +1,3 @@
# Account
The ```account``` command groups actions to interact with accounts:

View file

@ -1,4 +1,9 @@
# Account import
The ```account import``` command imports an account in Json format to the Bor data directory.
## Options
- ```datadir```: Path of the data directory to store information
- ```keystore```: Path of the data directory to store information

View file

@ -1,4 +1,9 @@
# Account list
The ```account list``` command lists all the accounts in the Bor data directory.
The `account list` command lists all the accounts in the Bor data directory.
## Options
- ```datadir```: Path of the data directory to store information
- ```keystore```: Path of the data directory to store information

View file

@ -1,4 +1,9 @@
# Account new
The ```account new``` command creates a new local account file on the Bor data directory. Bor should not be running to execute this command.
The `account new` command creates a new local account file on the Bor data directory. Bor should not be running to execute this command.
## Options
- ```datadir```: Path of the data directory to store information
- ```keystore```: Path of the data directory to store information

11
docs/cli/attach.md Normal file
View file

@ -0,0 +1,11 @@
# Attach
Connect to remote Bor IPC console.
## Options
- ```exec```: Command to run in remote console
- ```preload```: Comma separated list of JavaScript files to preload into the console
- ```jspath```: JavaScript root path for `loadScript`

17
docs/cli/bootnode.md Normal file
View file

@ -0,0 +1,17 @@
# Bootnode
## Options
- ```listen-addr```: listening address of bootnode (<ip>:<port>)
- ```v5```: Enable UDP v5
- ```log-level```: Log level (trace|debug|info|warn|error|crit)
- ```nat```: port mapping mechanism (any|none|upnp|pmp|extip:<IP>)
- ```node-key```: file or hex node key
- ```save-key```: path to save the ecdsa private key
- ```dry-run```: validates parameters and prints bootnode configurations, but does not start bootnode

View file

@ -1,6 +1,7 @@
# Chain
The ```chain``` command groups actions to interact with the blockchain in the client:
- [```chain sethead```](./chain_sethead.md): Set the current chain to a certain block.
- [```chain watch```](./chain_watch.md): Watch the chainHead, reorg and fork events in real-time.

View file

@ -1,4 +1,3 @@
# Chain sethead
The ```chain sethead <number>``` command sets the current chain to a certain block.
@ -9,4 +8,6 @@ The ```chain sethead <number>``` command sets the current chain to a certain blo
## Options
- ```yes```: Force set head.
- ```address```: Address of the grpc endpoint
- ```yes```: Force set head

View file

@ -1,13 +1,10 @@
# Debug
The ```bor debug``` command takes a debug dump of the running client.
## Options
- [```bor debug pprof```](./debug_pprof.md): Dumps bor pprof traces.
- ```seconds```: Number of seconds to trace cpu and traces.
- ```output```: Output directory for the data dump.
- [```bor debug block <number>```](./debug_block.md): Dumps bor block traces.
## Examples

9
docs/cli/debug_block.md Normal file
View file

@ -0,0 +1,9 @@
# Debug trace
The ```bor debug block <number>``` command will create an archive containing traces of a bor block.
## Options
- ```address```: Address of the grpc endpoint
- ```output```: Output directory

11
docs/cli/debug_pprof.md Normal file
View file

@ -0,0 +1,11 @@
# Debug Pprof
The ```debug pprof <enode>``` command will create an archive containing bor pprof traces.
## Options
- ```address```: Address of the grpc endpoint
- ```seconds```: seconds to trace
- ```output```: Output directory

3
docs/cli/dumpconfig.md Normal file
View file

@ -0,0 +1,3 @@
# Dumpconfig
The ```bor dumpconfig <your-favourite-flags>``` command will export the user provided flags into a configuration file

3
docs/cli/fingerprint.md Normal file
View file

@ -0,0 +1,3 @@
# Fingerprint
Display the system fingerprint

View file

@ -1,4 +1,3 @@
# Peers
The ```peers``` command groups actions to interact with peers:

View file

@ -1,8 +1,9 @@
# Peers add
The ```peers add <enode>``` command joins the local client to another remote peer.
## Arguments
## Options
- ```trusted```: Whether the peer is added as a trusted peer.
- ```address```: Address of the grpc endpoint
- ```trusted```: Add the peer as a trusted

View file

@ -1,4 +1,7 @@
# Peers list
# Peers add
The ```peers list``` command lists the connected peers.
## Options
- ```address```: Address of the grpc endpoint

View file

@ -1,4 +1,9 @@
# Peers remove
The ```peers remove <enode>``` command disconnects the local client from a connected peer if exists.
## Options
- ```address```: Address of the grpc endpoint
- ```trusted```: Add the peer as a trusted

View file

@ -1,4 +1,7 @@
# Peers status
The ```peers status <peer id>``` command displays the status of a peer by its id.
## Options
- ```address```: Address of the grpc endpoint

9
docs/cli/removedb.md Normal file
View file

@ -0,0 +1,9 @@
# RemoveDB
The ```bor removedb``` command will remove the blockchain and state databases at the given datadir location
## Options
- ```address```: Address of the grpc endpoint
- ```datadir```: Path of the data directory to store information

View file

@ -1,45 +1,194 @@
# Server
The ```bor server``` command runs the Bor client.
## General Options
## Options
- ```chain```: Name of the chain to sync (mainnet or mumbai).
- ```chain```: Name of the chain to sync
- ```log-level```: Set log level for the server (info, warn, debug, trace).
- ```identity```: Name/Identity of the node
- ```datadir```: Path of the data directory to store information (defaults to $HOME).
- ```log-level```: Set log level for the server
- ```config```: List of files that contain the configuration.
- ```datadir```: Path of the data directory to store information
- ```syncmode```: Blockchain sync mode ("fast", "full", "snap" or "light").
- ```keystore```: Path of the directory to store keystores
- ```gcmode```: Blockchain garbage collection mode ("full", "archive").
- ```config```: File for the config file
- ```whitelist```: Comma separated block number-to-hash mappings to enforce (<number>=<hash>).
- ```syncmode```: Blockchain sync mode (only "full" sync supported)
- ```snapshot```: Enables snapshot-database mode (default = enable).
- ```gcmode```: Blockchain garbage collection mode ("full", "archive")
- ```bor.heimdall```: URL of Heimdall service.
- ```eth.requiredblocks```: Comma separated block number-to-hash mappings to require for peering (<number>=<hash>)
- ```bor.withoutheimdall```: Run without Heimdall service (for testing purpose).
- ```snapshot```: Enables the snapshot-database mode (default = true)
- ```ethstats```: Reporting URL of a ethstats service (nodename:secret@host:port).
- ```bor.logs```: Enables bor log retrieval (default = false)
- ```gpo.blocks```: Number of recent blocks to check for gas prices.
- ```bor.heimdall```: URL of Heimdall service
- ```gpo.percentile```: Suggested gas price is the given percentile of a set of recent transaction gas prices.
- ```bor.withoutheimdall```: Run without Heimdall service (for testing purpose)
- ```gpo.maxprice```: Maximum gas price will be recommended by gpo.
- ```bor.heimdallgRPC```: Address of Heimdall gRPC service
- ```gpo.ignoreprice```: Gas price below which gpo will ignore transactions.
- ```ethstats```: Reporting URL of a ethstats service (nodename:secret@host:port)
- ```grpc.addr```: Address and port to bind the GRPC server.
- ```gpo.blocks```: Number of recent blocks to check for gas prices
- ```gpo.percentile```: Suggested gas price is the given percentile of a set of recent transaction gas prices
- ```gpo.maxprice```: Maximum gas price will be recommended by gpo
- ```gpo.ignoreprice```: Gas price below which gpo will ignore transactions
- ```disable-bor-wallet```: Disable the personal wallet endpoints
- ```grpc.addr```: Address and port to bind the GRPC server
- ```dev```: Enable developer mode with ephemeral proof-of-authority network and a pre-funded developer account, mining enabled
- ```dev.period```: Block period to use in developer mode (0 = mine only if transaction pending)
### Account Management Options
- ```unlock```: Comma separated list of accounts to unlock
- ```password```: Password file to use for non-interactive password input
- ```allow-insecure-unlock```: Allow insecure account unlocking when account-related RPCs are exposed by http
- ```lightkdf```: Reduce key-derivation RAM & CPU usage at some expense of KDF strength
### Cache Options
- ```cache```: Megabytes of memory allocated to internal caching (default = 4096 mainnet full node)
- ```cache.database```: Percentage of cache memory allowance to use for database io
- ```cache.trie```: Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)
- ```cache.trie.journal```: Disk journal directory for trie cache to survive node restarts
- ```cache.trie.rejournal```: Time interval to regenerate the trie cache journal
- ```cache.gc```: Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)
- ```cache.snapshot```: Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode)
- ```cache.noprefetch```: Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)
- ```cache.preimages```: Enable recording the SHA3/keccak preimages of trie keys
- ```cache.triesinmemory```: Number of block states (tries) to keep in memory (default = 128)
- ```txlookuplimit```: Number of recent blocks to maintain transactions index for (default = about 56 days, 0 = entire chain)
### JsonRPC Options
- ```rpc.gascap```: Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)
- ```rpc.txfeecap```: Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)
- ```ipcdisable```: Disable the IPC-RPC server
- ```ipcpath```: Filename for IPC socket/pipe within the datadir (explicit paths escape it)
- ```http.corsdomain```: Comma separated list of domains from which to accept cross origin requests (browser enforced)
- ```http.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.
- ```ws.origins```: Origins from which to accept websockets requests
- ```graphql.corsdomain```: Comma separated list of domains from which to accept cross origin requests (browser enforced)
- ```graphql.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.
- ```http```: Enable the HTTP-RPC server
- ```http.addr```: HTTP-RPC server listening interface
- ```http.port```: HTTP-RPC server listening port
- ```http.rpcprefix```: HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths.
- ```http.api```: API's offered over the HTTP-RPC interface
- ```ws```: Enable the WS-RPC server
- ```ws.addr```: WS-RPC server listening interface
- ```ws.port```: WS-RPC server listening port
- ```ws.rpcprefix```: HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.
- ```ws.api```: API's offered over the WS-RPC interface
- ```graphql```: Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.
### P2P Options
- ```bind```: Network binding address
- ```port```: Network listening port
- ```bootnodes```: Comma separated enode URLs for P2P discovery bootstrap
- ```maxpeers```: Maximum number of network peers (network disabled if set to 0)
- ```maxpendpeers```: Maximum number of pending connection attempts (defaults used if set to 0)
- ```nat```: NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)
- ```nodiscover```: Disables the peer discovery mechanism (manual peer addition)
- ```v5disc```: Enables the experimental RLPx V5 (Topic Discovery) mechanism
### Sealer Options
- ```mine```: Enable mining
- ```miner.etherbase```: Public address for block mining rewards (default = first account)
- ```miner.extradata```: Block extra data set by the miner (default = client version)
- ```miner.gaslimit```: Target gas ceiling for mined blocks
- ```miner.gasprice```: Minimum gas price for mining a transaction
### Telemetry Options
- ```metrics```: Enable metrics collection and reporting
- ```metrics.expensive```: Enable expensive metrics collection and reporting
- ```metrics.influxdb```: Enable metrics export/push to an external InfluxDB database (v1)
- ```metrics.influxdb.endpoint```: InfluxDB API endpoint to report metrics to
- ```metrics.influxdb.database```: InfluxDB database name to push reported metrics to
- ```metrics.influxdb.username```: Username to authorize access to the database
- ```metrics.influxdb.password```: Password to authorize access to the database
- ```metrics.influxdb.tags```: Comma-separated InfluxDB tags (key/values) attached to all measurements
- ```metrics.prometheus-addr```: Address for Prometheus Server
- ```metrics.opencollector-endpoint```: OpenCollector Endpoint (host:port)
- ```metrics.influxdbv2```: Enable metrics export/push to an external InfluxDB v2 database
- ```metrics.influxdb.token```: Token to authorize access to the database (v2 only)
- ```metrics.influxdb.bucket```: InfluxDB bucket name to push reported metrics to (v2 only)
- ```metrics.influxdb.organization```: InfluxDB organization name (v2 only)
### Transaction Pool Options
- ```txpool.locals```: Comma separated accounts to treat as locals (no flush, priority inclusion).
- ```txpool.locals```: Comma separated accounts to treat as locals (no flush, priority inclusion)
- ```txpool.nolocals```: Disables price exemptions for locally submitted transactions
@ -60,135 +209,3 @@ The ```bor server``` command runs the Bor client.
- ```txpool.globalqueue```: Maximum number of non-executable transaction slots for all accounts
- ```txpool.lifetime```: Maximum amount of time non-executable transaction are queued
### Sealer Options
- ```mine```: Enable sealing.
- ```miner.etherbase```: Public address for block mining rewards (default = first account)
- ```miner.extradata```: Block extra data set by the miner (default = client version).
- ```miner.gaslimit```: Target gas ceiling for mined blocks.
- ```miner.gasprice```: Minimum gas price for mining a transaction.
### Cache Options
- ```cache```: Megabytes of memory allocated to internal caching (default = 4096 mainnet full node).
- ```cache.database```: Percentage of cache memory allowance to use for database io.
- ```cache.trie```: Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode).
- ```cache.trie.journal```: Disk journal directory for trie cache to survive node restarts.
- ```cache.trie.rejournal```: Time interval to regenerate the trie cache journal.
- ```cache.gc```: Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode).
- ```cache.snapshot```: Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode).
- ```cache.noprefetch```: Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data).
- ```cache.preimages```: Enable recording the SHA3/keccak preimages of trie keys.
- ```txlookuplimit```: Number of recent blocks to maintain transactions index for (default = about 56 days, 0 = entire chain).
### JsonRPC Options
- ```rpc.gascap```: Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite).
- ```rpc.txfeecap```: Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap).
- ```ipcdisable```: Disable the IPC-RPC server.
- ```ipcpath```: Filename for IPC socket/pipe within the datadir (explicit paths escape it).
- ```jsonrpc.corsdomain```: Comma separated list of domains from which to accept cross.
- ```jsonrpc.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.
- ```http```: Enable the HTTP-RPC server.
- ```http.addr```: HTTP-RPC server listening interface.
- ```http.port```: HTTP-RPC server listening port.
- ```http.rpcprefix```: HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths.
- ```http.modules```: API's offered over the HTTP-RPC interface.
- ```ws```: Enable the WS-RPC server.
- ```ws.addr```: WS-RPC server listening interface.
- ```ws.port```: WS-RPC server listening port.
- ```ws.rpcprefix```: HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.
- ```ws.modules```: API's offered over the WS-RPC interface.
- ```graphql```: Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.
### P2P Options
- ```bind```: Network binding address
- ```port```: Network listening port
- ```bootnodes```: Comma separated enode URLs for P2P discovery bootstrap
- ```maxpeers```: "Maximum number of network peers (network disabled if set to 0)
- ```maxpendpeers```: Maximum number of pending connection attempts (defaults used if set to 0)
- ```nat```: "NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)
- ```nodiscover```: "Disables the peer discovery mechanism (manual peer addition)
- ```v5disc```: "Enables the experimental RLPx V5 (Topic Discovery) mechanism
### Telemetry Options
- ```metrics```: Enable metrics collection and reporting.
- ```metrics.expensive```: Enable expensive metrics collection and reporting.
- ```metrics.influxdb```: Enable metrics export/push to an external InfluxDB database (v1).
- ```metrics.influxdb.endpoint```: InfluxDB API endpoint to report metrics to.
- ```metrics.influxdb.database```: InfluxDB database name to push reported metrics to.
- ```metrics.influxdb.username```: Username to authorize access to the database.
- ```metrics.influxdb.password```: Password to authorize access to the database.
- ```metrics.influxdb.tags```: Comma-separated InfluxDB tags (key/values) attached to all measurements.
- ```metrics.influxdbv2```: Enable metrics export/push to an external InfluxDB v2 database.
- ```metrics.influxdb.token```: Token to authorize access to the database (v2 only).
- ```metrics.influxdb.bucket```: InfluxDB bucket name to push reported metrics to (v2 only).
- ```metrics.influxdb.organization```: InfluxDB organization name (v2 only).
### Account Management Options
- ```unlock```: "Comma separated list of accounts to unlock.
- ```password```: Password file to use for non-interactive password input.
- ```allow-insecure-unlock```: Allow insecure account unlocking when account-related RPCs are exposed by http.
- ```lightkdf```: Reduce key-derivation RAM & CPU usage at some expense of KDF strength.
## Usage
Use multiple files to configure the client:
```
$ bor server --config ./legacy-config.toml --config ./config2.hcl
```

View file

@ -1,4 +1,3 @@
# Status
The ```status``` command outputs the status of the client.

View file

@ -1,4 +1,3 @@
# Version
The ```bor version``` command outputs the version of the binary.

View file

@ -1,133 +1,146 @@
# Config
Toml files format used in geth are being deprecated.
Bor uses uses JSON and [HCL](https://github.com/hashicorp/hcl) formats to create configuration files. This is the format in HCL alongside the default values:
- The `bor dumpconfig` command prints the default configurations, in the TOML format, on the terminal.
- One can `pipe (>)` this to a file (say `config.toml`) and use it to start bor.
- Command to provide a config file: `bor server -config config.toml`
- Bor uses TOML, HCL, and JSON format config files.
- This is the format of the config file in TOML:
- **NOTE: The values of these following flags are just for reference**
- `config.toml` file:
```
chain = "mainnet"
log-level = "info"
data-dir = ""
sync-mode = "fast"
gc-mode = "full"
identity = "myIdentity"
log-level = "INFO"
datadir = "/var/lib/bor/data"
keystore = "path/to/keystore"
syncmode = "full"
gcmode = "full"
snapshot = true
ethstats = ""
whitelist = {}
p2p {
max-peers = 30
max-pend-peers = 50
["eth.requiredblocks"]
[p2p]
maxpeers = 50
maxpendpeers = 50
bind = "0.0.0.0"
port = 30303
no-discover = false
nodiscover = false
nat = "any"
discovery {
v5-enabled = false
bootnodes = []
[p2p.discovery]
v5disc = false
bootnodes = ["enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303", "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"]
bootnodesv4 = []
bootnodesv5 = []
staticNodes = []
trustedNodes = []
bootnodesv5 = ["enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA"]
static-nodes = ["enode://8499da03c47d637b20eee24eec3c356c9a2e6148d6fe25ca195c7949ab8ec2c03e3556126b0d7ed644675e78c4318b08691b7b57de10e5f0d40d05b09238fa0a@52.187.207.27:30303"]
trusted-nodes = ["enode://2b252ab6a1d0f971d9722cb839a42cb81db019ba44c08754628ab4a823487071b5695317c8ccd085219c3a03af063495b2f1da8d18218da2d6a82981b45e6ffc@65.108.70.101:30303"]
dns = []
}
}
heimdall {
[heimdall]
url = "http://localhost:1317"
without = false
}
"bor.without" = false
txpool {
locals = []
no-locals = false
[txpool]
locals = ["$ADDRESS1", "$ADDRESS2"]
nolocals = false
journal = ""
rejournal = "1h"
price-limit = 1
price-bump = 10
account-slots = 16
global-slots = 4096
account-queue = 64
global-queue = 1024
lifetime = "3h"
}
rejournal = "1h0m0s"
pricelimit = 30000000000
pricebump = 10
accountslots = 16
globalslots = 32768
accountqueue = 16
globalqueue = 32768
lifetime = "3h0m0s"
sealer {
enabled = false
[miner]
mine = false
etherbase = ""
gas-ceil = 8000000
extra-data = ""
}
extradata = ""
gaslimit = 20000000
gasprice = "30000000000"
gpo {
blocks = 20
percentile = 60
}
[jsonrpc]
ipcdisable = false
ipcpath = "/var/lib/bor/bor.ipc"
gascap = 50000000
txfeecap = 5e+00
jsonrpc {
ipc-disable = false
ipc-path = ""
modules = ["web3", "net"]
cors = ["*"]
vhost = ["*"]
http {
[jsonrpc.http]
enabled = false
port = 8545
prefix = ""
host = "localhost"
}
api = ["eth", "net", "web3", "txpool", "bor"]
vhosts = ["*"]
corsdomain = ["*"]
ws {
[jsonrpc.ws]
enabled = false
port = 8546
prefix = ""
host = "localhost"
}
api = ["web3", "net"]
vhosts = ["*"]
corsdomain = ["*"]
graphqh {
[jsonrpc.graphql]
enabled = false
}
}
port = 0
prefix = ""
host = ""
api = []
vhosts = ["*"]
corsdomain = ["*"]
telemetry {
enabled = false
[gpo]
blocks = 20
percentile = 60
maxprice = "5000000000000"
ignoreprice = "2"
[telemetry]
metrics = false
expensive = false
prometheus-addr = ""
opencollector-endpoint = ""
influxdb {
v1-enabled = false
[telemetry.influx]
influxdb = false
endpoint = ""
database = ""
username = ""
password = ""
v2-enabled = false
influxdbv2 = false
token = ""
bucket = ""
organization = ""
}
}
cache {
[cache]
cache = 1024
perc-database = 50
perc-trie = 15
perc-gc = 25
perc-snapshot = 10
gc = 25
snapshot = 10
database = 50
trie = 15
journal = "triecache"
rejournal = "60m"
no-prefetch = false
rejournal = "1h0m0s"
noprefetch = false
preimages = false
tx-lookup-limit = 2350000
}
txlookuplimit = 2350000
accounts {
unlock = []
password-file = ""
[accounts]
unlock = ["$ADDRESS1", "$ADDRESS2"]
password = "path/to/password.txt"
allow-insecure-unlock = false
use-lightweight-kdf = false
}
lightkdf = false
disable-bor-wallet = false
grpc {
[grpc]
addr = ":3131"
}
[developer]
dev = false
period = 0
```

View file

@ -359,3 +359,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
return b.eth.stateAtTransaction(block, txIndex, reexec)
}
func (b *EthAPIBackend) GetCheckpointWhitelist() map[uint64]common.Hash {
return b.eth.Downloader().ChainValidator.GetCheckpointWhitelist()
}
func (b *EthAPIBackend) PurgeCheckpointWhitelist() {
b.eth.Downloader().ChainValidator.PurgeCheckpointWhitelist()
}

View file

@ -82,6 +82,7 @@ type Ethereum struct {
eventMux *event.TypeMux
engine consensus.Engine
accountManager *accounts.Manager
authorized bool // If consensus engine is authorized with keystore
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
@ -100,6 +101,8 @@ type Ethereum struct {
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
closeCh chan struct{} // Channel to signal the background processes to exit
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
}
@ -153,6 +156,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
chainDb: chainDb,
eventMux: stack.EventMux(),
accountManager: stack.AccountManager(),
authorized: false,
engine: nil,
closeBloomHandler: make(chan struct{}),
networkID: config.NetworkId,
@ -161,6 +165,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
p2pServer: stack.Server(),
closeCh: make(chan struct{}),
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
}
@ -181,7 +186,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// END: Bor changes
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
var dbVer = "<nil>"
dbVer := "<nil>"
if bcVersion != nil {
dbVer = fmt.Sprintf("%d", *bcVersion)
}
@ -252,6 +257,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
Checkpoint: checkpoint,
EthAPI: ethAPI,
PeerRequiredBlocks: config.PeerRequiredBlocks,
}); err != nil {
return nil, err
@ -469,8 +475,10 @@ func (s *Ethereum) StartMining(threads int) error {
if threads == 0 {
threads = -1 // Disable the miner from within
}
th.SetThreads(threads)
}
// If the miner was not running, initialize it
if !s.IsMining() {
// Propagate the initial price point to the transaction pool
@ -483,8 +491,13 @@ func (s *Ethereum) StartMining(threads int) error {
eb, err := s.Etherbase()
if err != nil {
log.Error("Cannot start mining without etherbase", "err", err)
return fmt.Errorf("etherbase missing: %v", err)
}
// If personal endpoints are disabled, the server creating
// this Ethereum instance has already Authorized consensus.
if !s.authorized {
var cli *clique.Clique
if c, ok := s.engine.(*clique.Clique); ok {
cli = c
@ -493,28 +506,36 @@ func (s *Ethereum) StartMining(threads int) error {
cli = c
}
}
if cli != nil {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
if wallet == nil || err != nil {
log.Error("Etherbase account unavailable locally", "err", err)
return fmt.Errorf("signer missing: %v", err)
}
cli.Authorize(eb, wallet.SignData)
}
if bor, ok := s.engine.(*bor.Bor); ok {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
if wallet == nil || err != nil {
log.Error("Etherbase account unavailable locally", "err", err)
return fmt.Errorf("signer missing: %v", err)
}
bor.Authorize(eb, wallet.SignData)
}
}
// If mining is started, we can disable the transaction rejection mechanism
// introduced to speed sync times.
atomic.StoreUint32(&s.handler.acceptTxs, 1)
go s.miner.Start(eb)
}
return nil
}
@ -553,6 +574,14 @@ func (s *Ethereum) SyncMode() downloader.SyncMode {
return mode
}
// SetAuthorized sets the authorized bool variable
// denoting that consensus has been authorized while creation
func (s *Ethereum) SetAuthorized(authorized bool) {
s.lock.Lock()
s.authorized = authorized
s.lock.Unlock()
}
// Protocols returns all the currently configured
// network protocols to start.
func (s *Ethereum) Protocols() []p2p.Protocol {
@ -560,6 +589,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol {
if s.config.SnapshotCache > 0 {
protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...)
}
return protos
}
@ -582,8 +612,77 @@ func (s *Ethereum) Start() error {
}
maxPeers -= s.config.LightPeers
}
// Start the networking layer and the light server if requested
s.handler.Start(maxPeers)
go s.startCheckpointWhitelistService()
return nil
}
// StartCheckpointWhitelistService starts the goroutine to fetch checkpoints and update the
// checkpoint whitelist map.
func (s *Ethereum) startCheckpointWhitelistService() {
// a shortcut helps with tests and early exit
select {
case <-s.closeCh:
return
default:
}
// first run the checkpoint whitelist
err := s.handleWhitelistCheckpoint()
if err != nil {
if errors.Is(err, ErrBorConsensusWithoutHeimdall) || errors.Is(err, ErrNotBorConsensus) {
return
}
log.Warn("unable to whitelist checkpoint - first run", "err", err)
}
ticker := time.NewTicker(100 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
err := s.handleWhitelistCheckpoint()
if err != nil {
log.Warn("unable to whitelist checkpoint", "err", err)
}
case <-s.closeCh:
return
}
}
}
var (
ErrNotBorConsensus = errors.New("not bor consensus was given")
ErrBorConsensusWithoutHeimdall = errors.New("bor consensus without heimdall")
)
// handleWhitelistCheckpoint handles the checkpoint whitelist mechanism.
func (s *Ethereum) handleWhitelistCheckpoint() error {
ethHandler := (*ethHandler)(s.handler)
bor, ok := ethHandler.chain.Engine().(*bor.Bor)
if !ok {
return ErrNotBorConsensus
}
if bor.HeimdallClient == nil {
return ErrBorConsensusWithoutHeimdall
}
endBlockNum, endBlockHash, err := ethHandler.fetchWhitelistCheckpoint(bor)
if err != nil {
return err
}
// Update the checkpoint whitelist map.
ethHandler.downloader.ProcessCheckpoint(endBlockNum, endBlockHash)
return nil
}
@ -599,6 +698,9 @@ func (s *Ethereum) Stop() error {
s.bloomIndexer.Close()
close(s.closeBloomHandler)
// Close all bg processes
close(s.closeCh)
// closing consensus engine first, as miner has deps on it
s.engine.Close()
s.txPool.Stop()

View file

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader/whitelist"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
@ -143,6 +144,8 @@ type Downloader struct {
quitCh chan struct{} // Quit channel to signal termination
quitLock sync.Mutex // Lock to prevent double closes
ChainValidator
// Testing hooks
syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run
bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch
@ -150,6 +153,14 @@ type Downloader struct {
chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations)
}
// interface for whitelist service
type ChainValidator interface {
IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error)
ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash)
GetCheckpointWhitelist() map[uint64]common.Hash
PurgeCheckpointWhitelist()
}
// LightChain encapsulates functions required to synchronise a light chain.
type LightChain interface {
// HasHeader verifies a header's presence in the local chain.
@ -204,7 +215,8 @@ type BlockChain interface {
}
// New creates a new downloader to fetch hashes and blocks from remote peers.
func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader {
//nolint: staticcheck
func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func(), whitelistService ChainValidator) *Downloader {
if lightchain == nil {
lightchain = chain
}
@ -221,6 +233,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain Bl
quitCh: make(chan struct{}),
SnapSyncer: snap.NewSyncer(stateDb),
stateSyncStart: make(chan *stateSync),
ChainValidator: whitelistService,
}
dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success))
@ -332,9 +345,11 @@ func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, m
case nil, errBusy, errCanceled:
return err
}
if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) ||
errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) ||
errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) {
errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) ||
errors.Is(err, whitelist.ErrCheckpointMismatch) {
log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err)
if d.dropPeer == nil {
// The dropPeer method is nil when `--copydb` is used for a local copy.
@ -345,10 +360,17 @@ func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, m
}
return err
}
if errors.Is(err, ErrMergeTransition) {
return err // This is an expected fault, don't keep printing it in a spin-loop
}
log.Warn("Synchronisation failed, retrying", "err", err)
if errors.Is(err, whitelist.ErrNoRemoteCheckoint) {
log.Warn("Doesn't have remote checkpoint yet", "peer", id, "err", err)
}
log.Warn("Synchronisation failed, retrying", "peer", id, "err", err)
return err
}
@ -764,12 +786,24 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui
return int64(from), count, span - 1, uint64(max)
}
// curried fetchHeadersByNumber
func (d *Downloader) getFetchHeadersByNumber(p *peerConnection) func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
return func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
return d.fetchHeadersByNumber(p, number, amount, skip, reverse)
}
}
// findAncestor tries to locate the common ancestor link 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 links should already get us a match.
// 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 *peerConnection, remoteHeader *types.Header) (uint64, error) {
// Check the validity of chain to be downloaded
if _, err := d.IsValidChain(remoteHeader, d.getFetchHeadersByNumber(p)); err != nil {
return 0, err
}
// Figure out the valid ancestor range to prevent rewrite attacks
var (
floor = int64(-1)
@ -1346,6 +1380,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
if chunkHeaders[len(chunkHeaders)-1].Number.Uint64()+uint64(fsHeaderForceVerify) > pivot {
frequency = 1
}
// Although the received headers might be all valid, a legacy
// PoW/PoA sync must not accept post-merge headers. Make sure
// that any transition is rejected at this point.
@ -1353,13 +1388,16 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
rejected []*types.Header
td *big.Int
)
if !beaconMode && ttd != nil {
td = d.blockchain.GetTd(chunkHeaders[0].ParentHash, chunkHeaders[0].Number.Uint64()-1)
if td == nil {
// This should never really happen, but handle gracefully for now
log.Error("Failed to retrieve parent header TD", "number", chunkHeaders[0].Number.Uint64()-1, "hash", chunkHeaders[0].ParentHash)
return fmt.Errorf("%w: parent TD missing", errInvalidChain)
}
for i, header := range chunkHeaders {
td = new(big.Int).Add(td, header.Difficulty)
if td.Cmp(ttd) >= 0 {
@ -1373,10 +1411,12 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
} else {
chunkHeaders, rejected = chunkHeaders[:i], chunkHeaders[i:]
}
break
}
}
}
if len(chunkHeaders) > 0 {
if n, err := d.lightchain.InsertHeaderChain(chunkHeaders, frequency); err != nil {
rollbackErr = err
@ -1385,12 +1425,15 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
if (mode == SnapSync || frequency > 1) && n > 0 && rollback == 0 {
rollback = chunkHeaders[0].Number.Uint64()
}
log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err)
return fmt.Errorf("%w: %v", errInvalidChain, err)
}
// All verifications passed, track all headers within the allowed limits
if mode == SnapSync {
head := chunkHeaders[len(chunkHeaders)-1].Number.Uint64()
if head-rollback > uint64(fsHeaderSafetyNet) {
rollback = head - uint64(fsHeaderSafetyNet)
} else {
@ -1398,14 +1441,26 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
}
}
}
if len(rejected) != 0 {
// Merge threshold reached, stop importing, but don't roll back
rollback = 0
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd)
return ErrMergeTransition
}
if len(rejected) != 0 {
// Merge threshold reached, stop importing, but don't roll back
rollback = 0
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd)
return ErrMergeTransition
}
}
// Unless we're doing light chains, schedule the headers for associated content retrieval
if mode == FullSync || mode == SnapSync {
// If we've reached the allowed number of pending headers, stall a bit

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/downloader/whitelist"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/event"
@ -42,6 +43,8 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/assert"
)
// downloadTester is a test simulator for mocking out local block chain.
@ -60,25 +63,35 @@ func newTester() *downloadTester {
if err != nil {
panic(err)
}
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false)
if err != nil {
panic(err)
}
core.GenesisBlockForTesting(db, testAddress, big.NewInt(1000000000000000))
chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
if err != nil {
panic(err)
}
tester := &downloadTester{
freezer: freezer,
chain: chain,
peers: make(map[string]*downloadTesterPeer),
}
tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, nil)
//nolint: staticcheck
tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, nil, whitelist.NewService(10))
return tester
}
func (dl *downloadTester) setWhitelist(w ChainValidator) {
dl.downloader.ChainValidator = w
}
// terminate aborts any operations on the embedded downloader and releases all
// held resources.
func (dl *downloadTester) terminate() {
@ -155,7 +168,7 @@ func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) {
}
func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header {
var headers = make([]*types.Header, len(rlpdata))
headers := make([]*types.Header, len(rlpdata))
for i, data := range rlpdata {
var h types.Header
if err := rlp.DecodeBytes(data, &h); err != nil {
@ -620,6 +633,7 @@ func TestBoundedHeavyForkedSync66Full(t *testing.T) {
func TestBoundedHeavyForkedSync66Snap(t *testing.T) {
testBoundedHeavyForkedSync(t, eth.ETH66, SnapSync)
}
func TestBoundedHeavyForkedSync66Light(t *testing.T) {
testBoundedHeavyForkedSync(t, eth.ETH66, LightSync)
}
@ -916,6 +930,7 @@ func TestHighTDStarvationAttack66Full(t *testing.T) {
func TestHighTDStarvationAttack66Snap(t *testing.T) {
testHighTDStarvationAttack(t, eth.ETH66, SnapSync)
}
func TestHighTDStarvationAttack66Light(t *testing.T) {
testHighTDStarvationAttack(t, eth.ETH66, LightSync)
}
@ -1268,36 +1283,45 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
expected []int
}{
// Remote is way higher. We should ask for the remote head and go backwards
{1500, 1000,
{
1500, 1000,
[]int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499},
},
{15000, 13006,
{
15000, 13006,
[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
},
// Remote is pretty close to us. We don't have to fetch as many
{1200, 1150,
{
1200, 1150,
[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
},
// Remote is equal to us (so on a fork with higher td)
// We should get the closest couple of ancestors
{1500, 1500,
{
1500, 1500,
[]int{1497, 1499},
},
// We're higher than the remote! Odd
{1000, 1500,
{
1000, 1500,
[]int{997, 999},
},
// Check some weird edgecases that it behaves somewhat rationally
{0, 1500,
{
0, 1500,
[]int{0, 2},
},
{6000000, 0,
{
6000000, 0,
[]int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999},
},
{0, 0,
{
0, 0,
[]int{0, 2},
},
}
reqs := func(from, count, span int) []int {
var r []int
num := from
@ -1307,7 +1331,12 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
}
return r
}
for i, tt := range testCases {
i := i
tt := tt
t.Run("", func(t *testing.T) {
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
data := reqs(int(from), count, span)
@ -1326,6 +1355,7 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
}
}
}
if failed {
res := strings.Replace(fmt.Sprint(data), " ", ",", -1)
exp := strings.Replace(fmt.Sprint(tt.expected), " ", ",", -1)
@ -1333,6 +1363,7 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
t.Logf("exp: %v\n", exp)
t.Errorf("test %d: wrong values", i)
}
})
}
}
@ -1359,12 +1390,134 @@ func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) {
if mode == SnapSync || mode == LightSync {
expect = errUnsyncedPeer
}
if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) {
t.Fatalf("block sync error mismatch: have %v, want %v", err, expect)
}
if mode == SnapSync || mode == LightSync {
assertOwnChain(t, tester, 1)
} else {
assertOwnChain(t, tester, len(chain.blocks))
}
}
// whitelistFake is a mock for the chain validator service
type whitelistFake struct {
// count denotes the number of times the validate function was called
count int
// validate is the dynamic function to be called while syncing
validate func(count int) (bool, error)
}
// newWhitelistFake returns a new mock whitelist
func newWhitelistFake(validate func(count int) (bool, error)) *whitelistFake {
return &whitelistFake{0, validate}
}
// IsValidChain is the mock function which the downloader will use to validate the chain
// to be received from a peer.
func (w *whitelistFake) IsValidChain(_ *types.Header, _ func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) {
defer func() {
w.count++
}()
return w.validate(w.count)
}
func (w *whitelistFake) ProcessCheckpoint(_ uint64, _ common.Hash) {}
func (w *whitelistFake) GetCheckpointWhitelist() map[uint64]common.Hash {
return nil
}
func (w *whitelistFake) PurgeCheckpointWhitelist() {}
// TestFakedSyncProgress66WhitelistMismatch tests if in case of whitelisted
// checkpoint mismatch with opposite peer, the sync should fail.
func TestFakedSyncProgress66WhitelistMismatch(t *testing.T) {
t.Parallel()
protocol := uint(eth.ETH66)
mode := FullSync
tester := newTester()
validate := func(count int) (bool, error) {
return false, whitelist.ErrCheckpointMismatch
}
tester.downloader.ChainValidator = newWhitelistFake(validate)
defer tester.terminate()
chainA := testChainForkLightA.blocks
tester.newPeer("light", protocol, chainA[1:])
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("light", nil, mode); err == nil {
t.Fatal("succeeded attacker synchronisation")
}
}
// TestFakedSyncProgress66WhitelistMatch tests if in case of whitelisted
// checkpoint match with opposite peer, the sync should succeed.
func TestFakedSyncProgress66WhitelistMatch(t *testing.T) {
t.Parallel()
protocol := uint(eth.ETH66)
mode := FullSync
tester := newTester()
validate := func(count int) (bool, error) {
return true, nil
}
tester.downloader.ChainValidator = newWhitelistFake(validate)
defer tester.terminate()
chainA := testChainForkLightA.blocks
tester.newPeer("light", protocol, chainA[1:])
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("light", nil, mode); err != nil {
t.Fatal("succeeded attacker synchronisation")
}
}
// TestFakedSyncProgress66NoRemoteCheckpoint tests if in case of missing/invalid
// checkpointed blocks with opposite peer, the sync should fail initially but
// with the retry mechanism, it should succeed eventually.
func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
t.Parallel()
protocol := uint(eth.ETH66)
mode := FullSync
tester := newTester()
validate := func(count int) (bool, error) {
// only return the `ErrNoRemoteCheckoint` error for the first call
if count == 0 {
return false, whitelist.ErrNoRemoteCheckoint
}
return true, nil
}
tester.downloader.ChainValidator = newWhitelistFake(validate)
defer tester.terminate()
chainA := testChainForkLightA.blocks
tester.newPeer("light", protocol, chainA[1:])
// Synchronise with the peer and make sure all blocks were retrieved
// Should fail in first attempt
if err := tester.sync("light", nil, mode); err != nil {
assert.Equal(t, whitelist.ErrNoRemoteCheckoint, err, "failed synchronisation")
}
// Try syncing again, should succeed
if err := tester.sync("light", nil, mode); err != nil {
t.Fatal("succeeded attacker synchronisation")
}
}

View file

@ -64,8 +64,11 @@ type chainData struct {
offset int
}
var chain *chainData
var emptyChain *chainData
var (
chain *chainData
chainLongerFork *chainData
emptyChain *chainData
)
func init() {
// Create a chain of blocks to import
@ -75,6 +78,9 @@ func init() {
blocks, _ = makeChain(targetBlocks, 0, genesis, true)
emptyChain = &chainData{blocks, 0}
chainLongerForkBlocks, _ := makeChain(1024, 0, blocks[len(blocks)-1], false)
chainLongerFork = &chainData{chainLongerForkBlocks, 0}
}
func (chain *chainData) headers() []*types.Header {

View file

@ -0,0 +1,126 @@
package whitelist
import (
"errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// Checkpoint whitelist
type Service struct {
m sync.Mutex
checkpointWhitelist map[uint64]common.Hash // Checkpoint whitelist, populated by reaching out to heimdall
checkpointOrder []uint64 // Checkpoint order, populated by reaching out to heimdall
maxCapacity uint
}
func NewService(maxCapacity uint) *Service {
return &Service{
checkpointWhitelist: make(map[uint64]common.Hash),
checkpointOrder: []uint64{},
maxCapacity: maxCapacity,
}
}
var (
ErrCheckpointMismatch = errors.New("checkpoint mismatch")
ErrNoRemoteCheckoint = errors.New("remote peer doesn't have a checkoint")
)
// IsValidChain checks if the chain we're about to receive from this peer is valid or not
// in terms of reorgs. We won't reorg beyond the last bor checkpoint submitted to mainchain.
func (w *Service) IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) {
// We want to validate the chain by comparing the last checkpointed block
// we're storing in `checkpointWhitelist` with the peer's block.
//
// Check for availaibility of the last checkpointed block.
// This can be also be empty if our heimdall is not responding
// or we're running without it.
if len(w.checkpointWhitelist) == 0 {
// worst case, we don't have the checkpoints in memory
return true, nil
}
// Fetch the last checkpoint entry
lastCheckpointBlockNum := w.checkpointOrder[len(w.checkpointOrder)-1]
lastCheckpointBlockHash := w.checkpointWhitelist[lastCheckpointBlockNum]
// todo: we can extract this as an interface and mock as well or just test IsValidChain in isolation from downloader passing fake fetchHeadersByNumber functions
headers, hashes, err := fetchHeadersByNumber(lastCheckpointBlockNum, 1, 0, false)
if err != nil {
return false, fmt.Errorf("%w: last checkpoint %d, err %v", ErrNoRemoteCheckoint, lastCheckpointBlockNum, err)
}
if len(headers) == 0 {
return false, fmt.Errorf("%w: last checkpoint %d", ErrNoRemoteCheckoint, lastCheckpointBlockNum)
}
reqBlockNum := headers[0].Number.Uint64()
reqBlockHash := hashes[0]
// Check against the checkpointed blocks
if reqBlockNum == lastCheckpointBlockNum && reqBlockHash == lastCheckpointBlockHash {
return true, nil
}
return false, ErrCheckpointMismatch
}
func (w *Service) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) {
w.m.Lock()
defer w.m.Unlock()
w.enqueueCheckpointWhitelist(endBlockNum, endBlockHash)
// If size of checkpoint whitelist map is greater than 10, remove the oldest entry.
if w.length() > int(w.maxCapacity) {
w.dequeueCheckpointWhitelist()
}
}
// GetCheckpointWhitelist returns the existing whitelisted
// entries of checkpoint of the form block number -> block hash.
func (w *Service) GetCheckpointWhitelist() map[uint64]common.Hash {
w.m.Lock()
defer w.m.Unlock()
return w.checkpointWhitelist
}
// PurgeCheckpointWhitelist purges data from checkpoint whitelist map
func (w *Service) PurgeCheckpointWhitelist() {
w.m.Lock()
defer w.m.Unlock()
w.checkpointWhitelist = make(map[uint64]common.Hash)
w.checkpointOrder = make([]uint64, 0)
}
// EnqueueWhitelistBlock enqueues blockNumber, blockHash to the checkpoint whitelist map
func (w *Service) enqueueCheckpointWhitelist(key uint64, val common.Hash) {
if _, ok := w.checkpointWhitelist[key]; !ok {
log.Debug("Enqueing new checkpoint whitelist", "block number", key, "block hash", val)
w.checkpointWhitelist[key] = val
w.checkpointOrder = append(w.checkpointOrder, key)
}
}
// DequeueWhitelistBlock dequeues block, blockhash from the checkpoint whitelist map
func (w *Service) dequeueCheckpointWhitelist() {
if len(w.checkpointOrder) > 0 {
log.Debug("Dequeing checkpoint whitelist", "block number", w.checkpointOrder[0], "block hash", w.checkpointWhitelist[w.checkpointOrder[0]])
delete(w.checkpointWhitelist, w.checkpointOrder[0])
w.checkpointOrder = w.checkpointOrder[1:]
}
}
// length returns the len of the whitelist.
func (w *Service) length() int {
return len(w.checkpointWhitelist)
}

View file

@ -0,0 +1,107 @@
package whitelist
import (
"errors"
"math/big"
"testing"
"gotest.tools/assert"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// NewMockService creates a new mock whitelist service
func NewMockService(maxCapacity uint) *Service {
return &Service{
checkpointWhitelist: make(map[uint64]common.Hash),
checkpointOrder: []uint64{},
maxCapacity: maxCapacity,
}
}
// TestWhitelistCheckpoint checks the checkpoint whitelist map queue mechanism
func TestWhitelistCheckpoint(t *testing.T) {
t.Parallel()
s := NewMockService(10)
for i := 0; i < 10; i++ {
s.enqueueCheckpointWhitelist(uint64(i), common.Hash{})
}
assert.Equal(t, s.length(), 10, "expected 10 items in whitelist")
s.enqueueCheckpointWhitelist(11, common.Hash{})
s.dequeueCheckpointWhitelist()
assert.Equal(t, s.length(), 10, "expected 10 items in whitelist")
}
// TestIsValidChain checks che IsValidChain function in isolation
// for different cases by providing a mock fetchHeadersByNumber function
func TestIsValidChain(t *testing.T) {
t.Parallel()
s := NewMockService(10)
// case1: no checkpoint whitelist, should consider the chain as valid
res, err := s.IsValidChain(nil, nil)
assert.NilError(t, err, "expected no error")
assert.Equal(t, res, true, "expected chain to be valid")
// add checkpoint entries and mock fetchHeadersByNumber function
s.ProcessCheckpoint(uint64(0), common.Hash{})
s.ProcessCheckpoint(uint64(1), common.Hash{})
assert.Equal(t, s.length(), 2, "expected 2 items in whitelist")
// create a false function, returning absolutely nothing
falseFetchHeadersByNumber := func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
return nil, nil, nil
}
// case2: false fetchHeadersByNumber function provided, should consider the chain as invalid
// and throw `ErrNoRemoteCheckoint` error
res, err = s.IsValidChain(nil, falseFetchHeadersByNumber)
if err == nil {
t.Fatal("expected error, got nil")
}
if !errors.Is(err, ErrNoRemoteCheckoint) {
t.Fatalf("expected error ErrNoRemoteCheckoint, got %v", err)
}
assert.Equal(t, res, false, "expected chain to be invalid")
// case3: correct fetchHeadersByNumber function provided, should consider the chain as valid
// create a mock function, returning a the required header
fetchHeadersByNumber := func(number uint64, _ int, _ int, _ bool) ([]*types.Header, []common.Hash, error) {
hash := common.Hash{}
header := types.Header{Number: big.NewInt(0)}
switch number {
case 0:
return []*types.Header{&header}, []common.Hash{hash}, nil
case 1:
header.Number = big.NewInt(1)
return []*types.Header{&header}, []common.Hash{hash}, nil
case 2:
header.Number = big.NewInt(1) // sending wrong header for misamatch
return []*types.Header{&header}, []common.Hash{hash}, nil
default:
return nil, nil, errors.New("invalid number")
}
}
res, err = s.IsValidChain(nil, fetchHeadersByNumber)
assert.NilError(t, err, "expected no error")
assert.Equal(t, res, true, "expected chain to be valid")
// add one more checkpoint whitelist entry
s.ProcessCheckpoint(uint64(2), common.Hash{})
assert.Equal(t, s.length(), 3, "expected 3 items in whitelist")
// case4: correct fetchHeadersByNumber function provided with wrong header
// for block number 2. Should consider the chain as invalid and throw an error
res, err = s.IsValidChain(nil, fetchHeadersByNumber)
assert.Equal(t, err, ErrCheckpointMismatch, "expected checkpoint mismatch error")
assert.Equal(t, res, false, "expected chain to be invalid")
}

View file

@ -29,6 +29,9 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/bor"
"github.com/ethereum/go-ethereum/consensus/bor/contract"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
@ -237,7 +240,14 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, et
// In order to pass the ethereum transaction tests, we need to set the burn contract which is in the bor config
// Then, bor != nil will also be enabled for ethash and clique. Only enable Bor for real if there is a validator contract present.
if chainConfig.Bor != nil && chainConfig.Bor.ValidatorContract != "" {
return bor.New(chainConfig, db, blockchainAPI, ethConfig.HeimdallURL, ethConfig.WithoutHeimdall)
genesisContractsClient := contract.NewGenesisContractsClient(chainConfig, chainConfig.Bor.ValidatorContract, chainConfig.Bor.StateReceiverContract, blockchainAPI)
spanner := span.NewChainSpanner(blockchainAPI, contract.ValidatorSet(), chainConfig, common.HexToAddress(chainConfig.Bor.ValidatorContract))
if ethConfig.WithoutHeimdall {
return bor.New(chainConfig, db, blockchainAPI, spanner, nil, genesisContractsClient)
} else {
return bor.New(chainConfig, db, blockchainAPI, spanner, heimdall.NewHeimdallClient(ethConfig.HeimdallURL), genesisContractsClient)
}
} else {
switch config.PowMode {
case ethash.ModeFake:

View file

@ -31,11 +31,13 @@ import (
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/downloader/whitelist"
"github.com/ethereum/go-ethereum/eth/fetcher"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
@ -84,8 +86,9 @@ type handlerConfig struct {
Network uint64 // Network identifier to adfvertise
Sync downloader.SyncMode // Whether to snap or full sync
BloomCache uint64 // Megabytes to alloc for snap sync bloom
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
EventMux *event.TypeMux //nolint:staticcheck // Legacy event mux, deprecate for `feed`
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
EthAPI *ethapi.PublicBlockChainAPI // EthAPI to interact
PeerRequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
}
@ -111,6 +114,8 @@ type handler struct {
peers *peerSet
merger *consensus.Merger
ethAPI *ethapi.PublicBlockChainAPI // EthAPI to interact
eventMux *event.TypeMux
txsCh chan core.NewTxsEvent
txsSub event.Subscription
@ -141,6 +146,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
chain: config.Chain,
peers: newPeerSet(),
merger: config.Merger,
ethAPI: config.EthAPI,
peerRequiredBlocks: config.PeerRequiredBlocks,
quitSync: make(chan struct{}),
}
@ -155,8 +161,15 @@ func newHandler(config *handlerConfig) (*handler, error) {
// In these cases however it's safe to reenable snap sync.
fullBlock, fastBlock := h.chain.CurrentBlock(), h.chain.CurrentFastBlock()
if fullBlock.NumberU64() == 0 && fastBlock.NumberU64() > 0 {
h.snapSync = uint32(1)
log.Warn("Switch sync mode from full sync to snap sync")
// Note: Ideally this should never happen with bor, but to be extra
// preventive we won't allow it to roll over to snap sync until
// we have it working
// TODO(snap): Uncomment when we have snap sync working
// h.snapSync = uint32(1)
// log.Warn("Switch sync mode from full sync to snap sync")
log.Warn("Preventing switching sync mode from full sync to snap sync")
}
} else {
if h.chain.CurrentBlock().NumberU64() > 0 {
@ -195,7 +208,8 @@ func newHandler(config *handlerConfig) (*handler, error) {
// Construct the downloader (long sync) and its backing state bloom if snap
// sync is requested. The downloader is responsible for deallocating the state
// bloom when it's done.
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success)
// todo: it'd better to extract maxCapacity into config
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success, whitelist.NewService(10))
// Construct the fetcher (short sync)
validator := func(header *types.Header) error {

75
eth/handler_bor.go Normal file
View file

@ -0,0 +1,75 @@
package eth
import (
"context"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/bor"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
var (
// errCheckpoint is returned when we are unable to fetch the
// latest checkpoint from the local heimdall.
errCheckpoint = errors.New("failed to fetch latest checkpoint")
// errMissingCheckpoint is returned when we don't have the
// checkpoint blocks locally, yet.
errMissingCheckpoint = errors.New("missing checkpoint blocks")
// errRootHash is returned when we aren't able to calculate the root hash
// locally for a range of blocks.
errRootHash = errors.New("failed to get local root hash")
// errCheckpointRootHashMismatch is returned when the local root hash
// doesn't match with the root hash in checkpoint.
errCheckpointRootHashMismatch = errors.New("checkpoint roothash mismatch")
// errEndBlock is returned when we're unable to fetch a block locally.
errEndBlock = errors.New("failed to get end block")
)
// fetchWhitelistCheckpoint fetched the latest checkpoint from it's local heimdall
// and verifies the data against bor data.
func (h *ethHandler) fetchWhitelistCheckpoint(bor *bor.Bor) (uint64, common.Hash, error) {
// check for checkpoint whitelisting: bor
checkpoint, err := bor.HeimdallClient.FetchLatestCheckpoint()
if err != nil {
log.Debug("Failed to fetch latest checkpoint for whitelisting")
return 0, common.Hash{}, errCheckpoint
}
// check if we have the checkpoint blocks
head := h.ethAPI.BlockNumber()
if head < hexutil.Uint64(checkpoint.EndBlock.Uint64()) {
log.Debug("Head block behind checkpoint block", "head", head, "checkpoint end block", checkpoint.EndBlock)
return 0, common.Hash{}, errMissingCheckpoint
}
// verify the root hash of checkpoint
roothash, err := h.ethAPI.GetRootHash(context.Background(), checkpoint.StartBlock.Uint64(), checkpoint.EndBlock.Uint64())
if err != nil {
log.Debug("Failed to get root hash of checkpoint while whitelisting")
return 0, common.Hash{}, errRootHash
}
if roothash != checkpoint.RootHash.String()[2:] {
log.Warn("Checkpoint root hash mismatch while whitelisting", "expected", checkpoint.RootHash.String()[2:], "got", roothash)
return 0, common.Hash{}, errCheckpointRootHashMismatch
}
// fetch the end checkpoint block hash
block, err := h.ethAPI.GetBlockByNumber(context.Background(), rpc.BlockNumber(checkpoint.EndBlock.Uint64()), false)
if err != nil {
log.Debug("Failed to get end block hash of checkpoint while whitelisting")
return 0, common.Hash{}, errEndBlock
}
hash := fmt.Sprintf("%v", block["hash"])
return checkpoint.EndBlock.Uint64(), common.HexToHash(hash), nil
}

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