mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Merge branch 'master' into sliding-window-test
This commit is contained in:
commit
63d18ed95d
138 changed files with 6790 additions and 1812 deletions
59
.travis.yml
59
.travis.yml
|
|
@ -14,7 +14,6 @@ matrix:
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# These are the latest Go versions.
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
|
|
@ -26,8 +25,20 @@ matrix:
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
|
# These are the latest Go versions.
|
||||||
|
- os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
go: 1.12.x
|
||||||
|
script:
|
||||||
|
- sudo modprobe fuse
|
||||||
|
- sudo chmod 666 /dev/fuse
|
||||||
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
|
- go run build/ci.go install
|
||||||
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
script:
|
script:
|
||||||
- echo "Increase the maximum number of open file descriptors on macOS"
|
- echo "Increase the maximum number of open file descriptors on macOS"
|
||||||
- NOFILE=20480
|
- NOFILE=20480
|
||||||
|
|
@ -44,7 +55,7 @@ matrix:
|
||||||
# This builder only tests code linters on latest version of Go
|
# This builder only tests code linters on latest version of Go
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- lint
|
- lint
|
||||||
git:
|
git:
|
||||||
|
|
@ -53,10 +64,10 @@ matrix:
|
||||||
- go run build/ci.go lint
|
- go run build/ci.go lint
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA upload
|
# This builder does the Ubuntu PPA upload
|
||||||
- if: type = push
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
git:
|
git:
|
||||||
|
|
@ -75,11 +86,11 @@ matrix:
|
||||||
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||||
|
|
||||||
# This builder does the Linux Azure uploads
|
# This builder does the Linux Azure uploads
|
||||||
- if: type = push
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- azure-linux
|
- azure-linux
|
||||||
git:
|
git:
|
||||||
|
|
@ -109,12 +120,12 @@ matrix:
|
||||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Linux Azure MIPS xgo uploads
|
# This builder does the Linux Azure MIPS xgo uploads
|
||||||
- if: type = push
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
git:
|
git:
|
||||||
|
|
@ -137,7 +148,7 @@ matrix:
|
||||||
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Android Maven and Azure uploads
|
# This builder does the Android Maven and Azure uploads
|
||||||
- if: type = push
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
|
|
@ -159,25 +170,24 @@ matrix:
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://storage.googleapis.com/golang/go1.11.5.linux-amd64.tar.gz | tar -xz
|
- curl https://dl.google.com/go/go1.12.linux-amd64.tar.gz | tar -xz
|
||||||
- export PATH=`pwd`/go/bin:$PATH
|
- export PATH=`pwd`/go/bin:$PATH
|
||||||
- export GOROOT=`pwd`/go
|
- export GOROOT=`pwd`/go
|
||||||
- export GOPATH=$HOME/go
|
- export GOPATH=$HOME/go
|
||||||
script:
|
script:
|
||||||
# Build the Android archive and upload it to Maven Central and Azure
|
# Build the Android archive and upload it to Maven Central and Azure
|
||||||
- curl https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip -o android-ndk-r17b.zip
|
- curl https://dl.google.com/android/repository/android-ndk-r19b-linux-x86_64.zip -o android-ndk-r19b.zip
|
||||||
- unzip -q android-ndk-r17b.zip && rm android-ndk-r17b.zip
|
- unzip -q android-ndk-r19b.zip && rm android-ndk-r19b.zip
|
||||||
- mv android-ndk-r17b $HOME
|
- mv android-ndk-r19b $ANDROID_HOME/ndk-bundle
|
||||||
- export ANDROID_NDK=$HOME/android-ndk-r17b
|
|
||||||
|
|
||||||
- mkdir -p $GOPATH/src/github.com/ethereum
|
- mkdir -p $GOPATH/src/github.com/ethereum
|
||||||
- ln -s `pwd` $GOPATH/src/github.com/ethereum
|
- ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum
|
||||||
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
|
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
||||||
- if: type = push
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
os: osx
|
os: osx
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- azure-ios
|
- azure-ios
|
||||||
|
|
@ -204,13 +214,22 @@ matrix:
|
||||||
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Azure archive purges to avoid accumulating junk
|
# This builder does the Azure archive purges to avoid accumulating junk
|
||||||
- if: type = cron
|
- if: repo = ethereum/go-ethereum AND type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.11.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go purge -store gethstore/builds -days 14
|
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||||
|
|
||||||
|
- name: Race Detector for Swarm
|
||||||
|
if: repo = ethersphere/go-ethereum
|
||||||
|
os: linux
|
||||||
|
dist: trusty
|
||||||
|
go: 1.12.x
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
|
script: ./build/travis_keepalive.sh go test -v -timeout 20m -race ./swarm...
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.11-alpine as builder
|
FROM golang:1.12-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.11-alpine as builder
|
FROM golang:1.12-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.11.5.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://dl.google.com/go/go1.12.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.11.5.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.12.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,18 @@ variables `PPA_SIGNING_KEY` and `PPA_SSH_KEY` on Travis.
|
||||||
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
|
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
|
||||||
version that is available in the main Ubuntu repository. In order to make this possible,
|
version that is available in the main Ubuntu repository. In order to make this possible,
|
||||||
our PPA depends on the ~gophers/ubuntu/archive PPA. Our source package build-depends on
|
our PPA depends on the ~gophers/ubuntu/archive PPA. Our source package build-depends on
|
||||||
golang-1.10, which is co-installable alongside the regular golang package. PPA dependencies
|
golang-1.11, which is co-installable alongside the regular golang package. PPA dependencies
|
||||||
can be edited at https://launchpad.net/%7Eethereum/+archive/ubuntu/ethereum/+edit-dependencies
|
can be edited at https://launchpad.net/%7Eethereum/+archive/ubuntu/ethereum/+edit-dependencies
|
||||||
|
|
||||||
## Building Packages Locally (for testing)
|
## Building Packages Locally (for testing)
|
||||||
|
|
||||||
You need to run Ubuntu to do test packaging.
|
You need to run Ubuntu to do test packaging.
|
||||||
|
|
||||||
Add the gophers PPA and install Go 1.10 and Debian packaging tools:
|
Add the gophers PPA and install Go 1.11 and Debian packaging tools:
|
||||||
|
|
||||||
$ sudo apt-add-repository ppa:gophers/ubuntu/archive
|
$ sudo apt-add-repository ppa:gophers/ubuntu/archive
|
||||||
$ sudo apt-get update
|
$ sudo apt-get update
|
||||||
$ sudo apt-get install build-essential golang-1.10 devscripts debhelper python-bzrlib python-paramiko
|
$ sudo apt-get install build-essential golang-1.11 devscripts debhelper python-bzrlib python-paramiko
|
||||||
|
|
||||||
Create the source packages:
|
Create the source packages:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -800,12 +800,8 @@ func doAndroidArchive(cmdline []string) {
|
||||||
if os.Getenv("ANDROID_HOME") == "" {
|
if os.Getenv("ANDROID_HOME") == "" {
|
||||||
log.Fatal("Please ensure ANDROID_HOME points to your Android SDK")
|
log.Fatal("Please ensure ANDROID_HOME points to your Android SDK")
|
||||||
}
|
}
|
||||||
if os.Getenv("ANDROID_NDK") == "" {
|
|
||||||
log.Fatal("Please ensure ANDROID_NDK points to your Android NDK")
|
|
||||||
}
|
|
||||||
// Build the Android archive and Maven resources
|
// Build the Android archive and Maven resources
|
||||||
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
||||||
build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK")))
|
|
||||||
build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
|
build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
|
||||||
|
|
||||||
if *local {
|
if *local {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ Source: {{.Name}}
|
||||||
Section: science
|
Section: science
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: {{.Author}}
|
Maintainer: {{.Author}}
|
||||||
Build-Depends: debhelper (>= 8.0.0), golang-1.10
|
Build-Depends: debhelper (>= 8.0.0), golang-1.11
|
||||||
Standards-Version: 3.9.5
|
Standards-Version: 3.9.5
|
||||||
Homepage: https://ethereum.org
|
Homepage: https://ethereum.org
|
||||||
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
build/env.sh /usr/lib/go-1.10/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
build/env.sh /usr/lib/go-1.11/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ Source: {{.Name}}
|
||||||
Section: science
|
Section: science
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: {{.Author}}
|
Maintainer: {{.Author}}
|
||||||
Build-Depends: debhelper (>= 8.0.0), golang-1.10
|
Build-Depends: debhelper (>= 8.0.0), golang-1.11
|
||||||
Standards-Version: 3.9.5
|
Standards-Version: 3.9.5
|
||||||
Homepage: https://ethereum.org
|
Homepage: https://ethereum.org
|
||||||
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
build/env.sh /usr/lib/go-1.10/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
build/env.sh /usr/lib/go-1.11/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
|
|
||||||
46
build/travis_keepalive.sh
Executable file
46
build/travis_keepalive.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# travis_keepalive runs the given command and preserves its return value,
|
||||||
|
# while it forks a child process what periodically produces a log line,
|
||||||
|
# so that Travis won't abort the build after 10 minutes.
|
||||||
|
|
||||||
|
# Why?
|
||||||
|
# `t.Log()` in Go holds the buffer until the test does not pass or fail,
|
||||||
|
# and `-race` can increase the execution time by 2-20x.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
readonly KEEPALIVE_INTERVAL=300 # seconds => 5m
|
||||||
|
|
||||||
|
main() {
|
||||||
|
keepalive
|
||||||
|
$@
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keepalive produces a log line in each KEEPALIVE_INTERVAL.
|
||||||
|
keepalive() {
|
||||||
|
local child_pid
|
||||||
|
# Note: We fork here!
|
||||||
|
repeat "keepalive" &
|
||||||
|
child_pid=$!
|
||||||
|
ensureChildOnEXIT "${child_pid}"
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat() {
|
||||||
|
local this="$1"
|
||||||
|
while true; do
|
||||||
|
echo "${this}"
|
||||||
|
sleep "${KEEPALIVE_INTERVAL}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensures that the child gets killed on normal program exit.
|
||||||
|
ensureChildOnEXIT() {
|
||||||
|
# Note: SIGINT and SIGTERM are forwarded to the child process by Bash
|
||||||
|
# automatically, so we don't have to deal with signals.
|
||||||
|
|
||||||
|
local child_pid="$1"
|
||||||
|
trap "kill ${child_pid}" EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
@ -93,6 +93,8 @@ var (
|
||||||
utils.ExitWhenSyncedFlag,
|
utils.ExitWhenSyncedFlag,
|
||||||
utils.GCModeFlag,
|
utils.GCModeFlag,
|
||||||
utils.LightServFlag,
|
utils.LightServFlag,
|
||||||
|
utils.LightBandwidthInFlag,
|
||||||
|
utils.LightBandwidthOutFlag,
|
||||||
utils.LightPeersFlag,
|
utils.LightPeersFlag,
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
utils.WhitelistFlag,
|
utils.WhitelistFlag,
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,8 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
utils.IdentityFlag,
|
utils.IdentityFlag,
|
||||||
utils.LightServFlag,
|
utils.LightServFlag,
|
||||||
|
utils.LightBandwidthInFlag,
|
||||||
|
utils.LightBandwidthOutFlag,
|
||||||
utils.LightPeersFlag,
|
utils.LightPeersFlag,
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
utils.WhitelistFlag,
|
utils.WhitelistFlag,
|
||||||
|
|
|
||||||
|
|
@ -59,31 +59,31 @@ var (
|
||||||
|
|
||||||
//constants for environment variables
|
//constants for environment variables
|
||||||
const (
|
const (
|
||||||
SWARM_ENV_CHEQUEBOOK_ADDR = "SWARM_CHEQUEBOOK_ADDR"
|
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
|
||||||
SWARM_ENV_ACCOUNT = "SWARM_ACCOUNT"
|
SwarmEnvAccount = "SWARM_ACCOUNT"
|
||||||
SWARM_ENV_LISTEN_ADDR = "SWARM_LISTEN_ADDR"
|
SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
|
||||||
SWARM_ENV_PORT = "SWARM_PORT"
|
SwarmEnvPort = "SWARM_PORT"
|
||||||
SWARM_ENV_NETWORK_ID = "SWARM_NETWORK_ID"
|
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
|
||||||
SWARM_ENV_SWAP_ENABLE = "SWARM_SWAP_ENABLE"
|
SwarmEnvSwapEnable = "SWARM_SWAP_ENABLE"
|
||||||
SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
|
SwarmEnvSwapAPI = "SWARM_SWAP_API"
|
||||||
SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE"
|
SwarmEnvSyncDisable = "SWARM_SYNC_DISABLE"
|
||||||
SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY"
|
SwarmEnvSyncUpdateDelay = "SWARM_ENV_SYNC_UPDATE_DELAY"
|
||||||
SWARM_ENV_MAX_STREAM_PEER_SERVERS = "SWARM_ENV_MAX_STREAM_PEER_SERVERS"
|
SwarmEnvMaxStreamPeerServers = "SWARM_ENV_MAX_STREAM_PEER_SERVERS"
|
||||||
SWARM_ENV_LIGHT_NODE_ENABLE = "SWARM_LIGHT_NODE_ENABLE"
|
SwarmEnvLightNodeEnable = "SWARM_LIGHT_NODE_ENABLE"
|
||||||
SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK"
|
SwarmEnvDeliverySkipCheck = "SWARM_DELIVERY_SKIP_CHECK"
|
||||||
SWARM_ENV_ENS_API = "SWARM_ENS_API"
|
SwarmEnvENSAPI = "SWARM_ENS_API"
|
||||||
SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR"
|
SwarmEnvENSAddr = "SWARM_ENS_ADDR"
|
||||||
SWARM_ENV_CORS = "SWARM_CORS"
|
SwarmEnvCORS = "SWARM_CORS"
|
||||||
SWARM_ENV_BOOTNODES = "SWARM_BOOTNODES"
|
SwarmEnvBootnodes = "SWARM_BOOTNODES"
|
||||||
SWARM_ENV_PSS_ENABLE = "SWARM_PSS_ENABLE"
|
SwarmEnvPSSEnable = "SWARM_PSS_ENABLE"
|
||||||
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
|
SwarmEnvStorePath = "SWARM_STORE_PATH"
|
||||||
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY"
|
||||||
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY"
|
||||||
SWARM_ENV_BOOTNODE_MODE = "SWARM_BOOTNODE_MODE"
|
SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE"
|
||||||
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
|
SwarmAccessPassword = "SWARM_ACCESS_PASSWORD"
|
||||||
SWARM_AUTO_DEFAULTPATH = "SWARM_AUTO_DEFAULTPATH"
|
SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH"
|
||||||
SWARM_GLOBALSTORE_API = "SWARM_GLOBALSTORE_API"
|
SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API"
|
||||||
GETH_ENV_DATADIR = "GETH_DATADIR"
|
GethEnvDataDir = "GETH_DATADIR"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These settings ensure that TOML keys use the same names as Go struct fields.
|
// These settings ensure that TOML keys use the same names as Go struct fields.
|
||||||
|
|
@ -227,7 +227,7 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
|
|
||||||
currentConfig.SwapAPI = ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
currentConfig.SwapAPI = ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
||||||
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
||||||
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
|
utils.Fatalf(SwarmErrSwapSetNoAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.GlobalIsSet(EnsAPIFlag.Name) {
|
if ctx.GlobalIsSet(EnsAPIFlag.Name) {
|
||||||
|
|
@ -274,113 +274,113 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
// envVarsOverride overrides the current config with whatver is provided in environment variables
|
// envVarsOverride overrides the current config with whatver is provided in environment variables
|
||||||
// most values are not allowed a zero value (empty string), if not otherwise noted
|
// most values are not allowed a zero value (empty string), if not otherwise noted
|
||||||
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||||
if keyid := os.Getenv(SWARM_ENV_ACCOUNT); keyid != "" {
|
if keyid := os.Getenv(SwarmEnvAccount); keyid != "" {
|
||||||
currentConfig.BzzAccount = keyid
|
currentConfig.BzzAccount = keyid
|
||||||
}
|
}
|
||||||
|
|
||||||
if chbookaddr := os.Getenv(SWARM_ENV_CHEQUEBOOK_ADDR); chbookaddr != "" {
|
if chbookaddr := os.Getenv(SwarmEnvChequebookAddr); chbookaddr != "" {
|
||||||
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" {
|
if networkid := os.Getenv(SwarmEnvNetworkID); networkid != "" {
|
||||||
id, err := strconv.ParseUint(networkid, 10, 64)
|
id, err := strconv.ParseUint(networkid, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_NETWORK_ID, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvNetworkID, err)
|
||||||
}
|
}
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
currentConfig.NetworkID = id
|
currentConfig.NetworkID = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if datadir := os.Getenv(GETH_ENV_DATADIR); datadir != "" {
|
if datadir := os.Getenv(GethEnvDataDir); datadir != "" {
|
||||||
currentConfig.Path = expandPath(datadir)
|
currentConfig.Path = expandPath(datadir)
|
||||||
}
|
}
|
||||||
|
|
||||||
bzzport := os.Getenv(SWARM_ENV_PORT)
|
bzzport := os.Getenv(SwarmEnvPort)
|
||||||
if len(bzzport) > 0 {
|
if len(bzzport) > 0 {
|
||||||
currentConfig.Port = bzzport
|
currentConfig.Port = bzzport
|
||||||
}
|
}
|
||||||
|
|
||||||
if bzzaddr := os.Getenv(SWARM_ENV_LISTEN_ADDR); bzzaddr != "" {
|
if bzzaddr := os.Getenv(SwarmEnvListenAddr); bzzaddr != "" {
|
||||||
currentConfig.ListenAddr = bzzaddr
|
currentConfig.ListenAddr = bzzaddr
|
||||||
}
|
}
|
||||||
|
|
||||||
if swapenable := os.Getenv(SWARM_ENV_SWAP_ENABLE); swapenable != "" {
|
if swapenable := os.Getenv(SwarmEnvSwapEnable); swapenable != "" {
|
||||||
swap, err := strconv.ParseBool(swapenable)
|
swap, err := strconv.ParseBool(swapenable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SWAP_ENABLE, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSwapEnable, err)
|
||||||
}
|
}
|
||||||
currentConfig.SwapEnabled = swap
|
currentConfig.SwapEnabled = swap
|
||||||
}
|
}
|
||||||
|
|
||||||
if syncdisable := os.Getenv(SWARM_ENV_SYNC_DISABLE); syncdisable != "" {
|
if syncdisable := os.Getenv(SwarmEnvSyncDisable); syncdisable != "" {
|
||||||
sync, err := strconv.ParseBool(syncdisable)
|
sync, err := strconv.ParseBool(syncdisable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SYNC_DISABLE, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncDisable, err)
|
||||||
}
|
}
|
||||||
currentConfig.SyncEnabled = !sync
|
currentConfig.SyncEnabled = !sync
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(SWARM_ENV_DELIVERY_SKIP_CHECK); v != "" {
|
if v := os.Getenv(SwarmEnvDeliverySkipCheck); v != "" {
|
||||||
skipCheck, err := strconv.ParseBool(v)
|
skipCheck, err := strconv.ParseBool(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentConfig.DeliverySkipCheck = skipCheck
|
currentConfig.DeliverySkipCheck = skipCheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(SWARM_ENV_SYNC_UPDATE_DELAY); v != "" {
|
if v := os.Getenv(SwarmEnvSyncUpdateDelay); v != "" {
|
||||||
d, err := time.ParseDuration(v)
|
d, err := time.ParseDuration(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SYNC_UPDATE_DELAY, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncUpdateDelay, err)
|
||||||
}
|
}
|
||||||
currentConfig.SyncUpdateDelay = d
|
currentConfig.SyncUpdateDelay = d
|
||||||
}
|
}
|
||||||
|
|
||||||
if max := os.Getenv(SWARM_ENV_MAX_STREAM_PEER_SERVERS); max != "" {
|
if max := os.Getenv(SwarmEnvMaxStreamPeerServers); max != "" {
|
||||||
m, err := strconv.Atoi(max)
|
m, err := strconv.Atoi(max)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_MAX_STREAM_PEER_SERVERS, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvMaxStreamPeerServers, err)
|
||||||
}
|
}
|
||||||
currentConfig.MaxStreamPeerServers = m
|
currentConfig.MaxStreamPeerServers = m
|
||||||
}
|
}
|
||||||
|
|
||||||
if lne := os.Getenv(SWARM_ENV_LIGHT_NODE_ENABLE); lne != "" {
|
if lne := os.Getenv(SwarmEnvLightNodeEnable); lne != "" {
|
||||||
lightnode, err := strconv.ParseBool(lne)
|
lightnode, err := strconv.ParseBool(lne)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_LIGHT_NODE_ENABLE, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvLightNodeEnable, err)
|
||||||
}
|
}
|
||||||
currentConfig.LightNodeEnabled = lightnode
|
currentConfig.LightNodeEnabled = lightnode
|
||||||
}
|
}
|
||||||
|
|
||||||
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
|
if swapapi := os.Getenv(SwarmEnvSwapAPI); swapapi != "" {
|
||||||
currentConfig.SwapAPI = swapapi
|
currentConfig.SwapAPI = swapapi
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
||||||
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
|
utils.Fatalf(SwarmErrSwapSetNoAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ensapi := os.Getenv(SWARM_ENV_ENS_API); ensapi != "" {
|
if ensapi := os.Getenv(SwarmEnvENSAPI); ensapi != "" {
|
||||||
currentConfig.EnsAPIs = strings.Split(ensapi, ",")
|
currentConfig.EnsAPIs = strings.Split(ensapi, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" {
|
if ensaddr := os.Getenv(SwarmEnvENSAddr); ensaddr != "" {
|
||||||
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cors := os.Getenv(SWARM_ENV_CORS); cors != "" {
|
if cors := os.Getenv(SwarmEnvCORS); cors != "" {
|
||||||
currentConfig.Cors = cors
|
currentConfig.Cors = cors
|
||||||
}
|
}
|
||||||
|
|
||||||
if bm := os.Getenv(SWARM_ENV_BOOTNODE_MODE); bm != "" {
|
if bm := os.Getenv(SwarmEnvBootnodeMode); bm != "" {
|
||||||
bootnodeMode, err := strconv.ParseBool(bm)
|
bootnodeMode, err := strconv.ParseBool(bm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_BOOTNODE_MODE, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvBootnodeMode, err)
|
||||||
}
|
}
|
||||||
currentConfig.BootnodeMode = bootnodeMode
|
currentConfig.BootnodeMode = bootnodeMode
|
||||||
}
|
}
|
||||||
|
|
||||||
if api := os.Getenv(SWARM_GLOBALSTORE_API); api != "" {
|
if api := os.Getenv(SwarmGlobalstoreAPI); api != "" {
|
||||||
currentConfig.GlobalStoreAPI = api
|
currentConfig.GlobalStoreAPI = api
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
swarm := runSwarm(t, flags...)
|
swarm := runSwarm(t, flags...)
|
||||||
swarm.Expect("Fatal: " + SWARM_ERR_SWAP_SET_NO_API + "\n")
|
swarm.Expect("Fatal: " + SwarmErrSwapSetNoAPI + "\n")
|
||||||
swarm.ExpectExit()
|
swarm.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ func TestConfigFailsNoBzzAccount(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
swarm := runSwarm(t, flags...)
|
swarm := runSwarm(t, flags...)
|
||||||
swarm.Expect("Fatal: " + SWARM_ERR_NO_BZZACCOUNT + "\n")
|
swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
|
||||||
swarm.ExpectExit()
|
swarm.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,68 +23,68 @@ var (
|
||||||
ChequebookAddrFlag = cli.StringFlag{
|
ChequebookAddrFlag = cli.StringFlag{
|
||||||
Name: "chequebook",
|
Name: "chequebook",
|
||||||
Usage: "chequebook contract address",
|
Usage: "chequebook contract address",
|
||||||
EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
|
EnvVar: SwarmEnvChequebookAddr,
|
||||||
}
|
}
|
||||||
SwarmAccountFlag = cli.StringFlag{
|
SwarmAccountFlag = cli.StringFlag{
|
||||||
Name: "bzzaccount",
|
Name: "bzzaccount",
|
||||||
Usage: "Swarm account key file",
|
Usage: "Swarm account key file",
|
||||||
EnvVar: SWARM_ENV_ACCOUNT,
|
EnvVar: SwarmEnvAccount,
|
||||||
}
|
}
|
||||||
SwarmListenAddrFlag = cli.StringFlag{
|
SwarmListenAddrFlag = cli.StringFlag{
|
||||||
Name: "httpaddr",
|
Name: "httpaddr",
|
||||||
Usage: "Swarm HTTP API listening interface",
|
Usage: "Swarm HTTP API listening interface",
|
||||||
EnvVar: SWARM_ENV_LISTEN_ADDR,
|
EnvVar: SwarmEnvListenAddr,
|
||||||
}
|
}
|
||||||
SwarmPortFlag = cli.StringFlag{
|
SwarmPortFlag = cli.StringFlag{
|
||||||
Name: "bzzport",
|
Name: "bzzport",
|
||||||
Usage: "Swarm local http api port",
|
Usage: "Swarm local http api port",
|
||||||
EnvVar: SWARM_ENV_PORT,
|
EnvVar: SwarmEnvPort,
|
||||||
}
|
}
|
||||||
SwarmNetworkIdFlag = cli.IntFlag{
|
SwarmNetworkIdFlag = cli.IntFlag{
|
||||||
Name: "bzznetworkid",
|
Name: "bzznetworkid",
|
||||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||||
EnvVar: SWARM_ENV_NETWORK_ID,
|
EnvVar: SwarmEnvNetworkID,
|
||||||
}
|
}
|
||||||
SwarmSwapEnabledFlag = cli.BoolFlag{
|
SwarmSwapEnabledFlag = cli.BoolFlag{
|
||||||
Name: "swap",
|
Name: "swap",
|
||||||
Usage: "Swarm SWAP enabled (default false)",
|
Usage: "Swarm SWAP enabled (default false)",
|
||||||
EnvVar: SWARM_ENV_SWAP_ENABLE,
|
EnvVar: SwarmEnvSwapEnable,
|
||||||
}
|
}
|
||||||
SwarmSwapAPIFlag = cli.StringFlag{
|
SwarmSwapAPIFlag = cli.StringFlag{
|
||||||
Name: "swap-api",
|
Name: "swap-api",
|
||||||
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||||
EnvVar: SWARM_ENV_SWAP_API,
|
EnvVar: SwarmEnvSwapAPI,
|
||||||
}
|
}
|
||||||
SwarmSyncDisabledFlag = cli.BoolTFlag{
|
SwarmSyncDisabledFlag = cli.BoolTFlag{
|
||||||
Name: "nosync",
|
Name: "nosync",
|
||||||
Usage: "Disable swarm syncing",
|
Usage: "Disable swarm syncing",
|
||||||
EnvVar: SWARM_ENV_SYNC_DISABLE,
|
EnvVar: SwarmEnvSyncDisable,
|
||||||
}
|
}
|
||||||
SwarmSyncUpdateDelay = cli.DurationFlag{
|
SwarmSyncUpdateDelay = cli.DurationFlag{
|
||||||
Name: "sync-update-delay",
|
Name: "sync-update-delay",
|
||||||
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
|
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
|
||||||
EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY,
|
EnvVar: SwarmEnvSyncUpdateDelay,
|
||||||
}
|
}
|
||||||
SwarmMaxStreamPeerServersFlag = cli.IntFlag{
|
SwarmMaxStreamPeerServersFlag = cli.IntFlag{
|
||||||
Name: "max-stream-peer-servers",
|
Name: "max-stream-peer-servers",
|
||||||
Usage: "Limit of Stream peer servers, 0 denotes unlimited",
|
Usage: "Limit of Stream peer servers, 0 denotes unlimited",
|
||||||
EnvVar: SWARM_ENV_MAX_STREAM_PEER_SERVERS,
|
EnvVar: SwarmEnvMaxStreamPeerServers,
|
||||||
Value: 10000, // A very large default value is possible as stream servers have very small memory footprint
|
Value: 10000, // A very large default value is possible as stream servers have very small memory footprint
|
||||||
}
|
}
|
||||||
SwarmLightNodeEnabled = cli.BoolFlag{
|
SwarmLightNodeEnabled = cli.BoolFlag{
|
||||||
Name: "lightnode",
|
Name: "lightnode",
|
||||||
Usage: "Enable Swarm LightNode (default false)",
|
Usage: "Enable Swarm LightNode (default false)",
|
||||||
EnvVar: SWARM_ENV_LIGHT_NODE_ENABLE,
|
EnvVar: SwarmEnvLightNodeEnable,
|
||||||
}
|
}
|
||||||
SwarmDeliverySkipCheckFlag = cli.BoolFlag{
|
SwarmDeliverySkipCheckFlag = cli.BoolFlag{
|
||||||
Name: "delivery-skip-check",
|
Name: "delivery-skip-check",
|
||||||
Usage: "Skip chunk delivery check (default false)",
|
Usage: "Skip chunk delivery check (default false)",
|
||||||
EnvVar: SWARM_ENV_DELIVERY_SKIP_CHECK,
|
EnvVar: SwarmEnvDeliverySkipCheck,
|
||||||
}
|
}
|
||||||
EnsAPIFlag = cli.StringSliceFlag{
|
EnsAPIFlag = cli.StringSliceFlag{
|
||||||
Name: "ens-api",
|
Name: "ens-api",
|
||||||
Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
|
Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
|
||||||
EnvVar: SWARM_ENV_ENS_API,
|
EnvVar: SwarmEnvENSAPI,
|
||||||
}
|
}
|
||||||
SwarmApiFlag = cli.StringFlag{
|
SwarmApiFlag = cli.StringFlag{
|
||||||
Name: "bzzapi",
|
Name: "bzzapi",
|
||||||
|
|
@ -126,7 +126,7 @@ var (
|
||||||
SwarmAccessPasswordFlag = cli.StringFlag{
|
SwarmAccessPasswordFlag = cli.StringFlag{
|
||||||
Name: "password",
|
Name: "password",
|
||||||
Usage: "Password",
|
Usage: "Password",
|
||||||
EnvVar: SWARM_ACCESS_PASSWORD,
|
EnvVar: SwarmAccessPassword,
|
||||||
}
|
}
|
||||||
SwarmDryRunFlag = cli.BoolFlag{
|
SwarmDryRunFlag = cli.BoolFlag{
|
||||||
Name: "dry-run",
|
Name: "dry-run",
|
||||||
|
|
@ -135,17 +135,17 @@ var (
|
||||||
CorsStringFlag = cli.StringFlag{
|
CorsStringFlag = cli.StringFlag{
|
||||||
Name: "corsdomain",
|
Name: "corsdomain",
|
||||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||||
EnvVar: SWARM_ENV_CORS,
|
EnvVar: SwarmEnvCORS,
|
||||||
}
|
}
|
||||||
SwarmStorePath = cli.StringFlag{
|
SwarmStorePath = cli.StringFlag{
|
||||||
Name: "store.path",
|
Name: "store.path",
|
||||||
Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
|
Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
|
||||||
EnvVar: SWARM_ENV_STORE_PATH,
|
EnvVar: SwarmEnvStorePath,
|
||||||
}
|
}
|
||||||
SwarmStoreCapacity = cli.Uint64Flag{
|
SwarmStoreCapacity = cli.Uint64Flag{
|
||||||
Name: "store.size",
|
Name: "store.size",
|
||||||
Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
|
Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
|
||||||
EnvVar: SWARM_ENV_STORE_CAPACITY,
|
EnvVar: SwarmEnvStoreCapacity,
|
||||||
}
|
}
|
||||||
SwarmStoreCacheCapacity = cli.UintFlag{
|
SwarmStoreCacheCapacity = cli.UintFlag{
|
||||||
Name: "store.cache.size",
|
Name: "store.cache.size",
|
||||||
|
|
@ -180,6 +180,6 @@ var (
|
||||||
SwarmGlobalStoreAPIFlag = cli.StringFlag{
|
SwarmGlobalStoreAPIFlag = cli.StringFlag{
|
||||||
Name: "globalstore-api",
|
Name: "globalstore-api",
|
||||||
Usage: "URL of the Global Store API provider (only for testing)",
|
Usage: "URL of the Global Store API provider (only for testing)",
|
||||||
EnvVar: SWARM_GLOBALSTORE_API,
|
EnvVar: SwarmGlobalstoreAPI,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
66
cmd/swarm/global-store/explorer.go
Normal file
66
cmd/swarm/global-store/explorer.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/mock"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/mock/explorer"
|
||||||
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// serveChunkExplorer starts an http server in background with chunk explorer handler
|
||||||
|
// using the provided global store. Server is started if the returned shutdown function
|
||||||
|
// is not nil.
|
||||||
|
func serveChunkExplorer(ctx *cli.Context, globalStore mock.GlobalStorer) (shutdown func(), err error) {
|
||||||
|
if !ctx.IsSet("explorer-address") {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
corsOrigins := ctx.StringSlice("explorer-cors-origin")
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: explorer.NewHandler(globalStore, corsOrigins),
|
||||||
|
IdleTimeout: 30 * time.Minute,
|
||||||
|
ReadTimeout: 2 * time.Minute,
|
||||||
|
WriteTimeout: 2 * time.Minute,
|
||||||
|
}
|
||||||
|
listener, err := net.Listen("tcp", ctx.String("explorer-address"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("explorer: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("chunk explorer http", "address", listener.Addr().String(), "origins", corsOrigins)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := server.Serve(listener); err != nil {
|
||||||
|
log.Error("chunk explorer", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
log.Error("chunk explorer: shutdown", "err", err)
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
254
cmd/swarm/global-store/explorer_test.go
Normal file
254
cmd/swarm/global-store/explorer_test.go
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/mock/explorer"
|
||||||
|
mockRPC "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestExplorer validates basic chunk explorer functionality by storing
|
||||||
|
// a small set of chunk and making http requests on exposed endpoint.
|
||||||
|
// Full chunk explorer validation is done in mock/explorer package.
|
||||||
|
func TestExplorer(t *testing.T) {
|
||||||
|
addr := findFreeTCPAddress(t)
|
||||||
|
explorerAddr := findFreeTCPAddress(t)
|
||||||
|
testCmd := runGlobalStore(t, "ws", "--addr", addr, "--explorer-address", explorerAddr)
|
||||||
|
defer testCmd.Kill()
|
||||||
|
|
||||||
|
client := websocketClient(t, addr)
|
||||||
|
|
||||||
|
store := mockRPC.NewGlobalStore(client)
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
nodeKeys := map[string][]string{
|
||||||
|
"a1": {"b1", "b2", "b3"},
|
||||||
|
"a2": {"b3", "b4", "b5"},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyNodes := make(map[string][]string)
|
||||||
|
|
||||||
|
for addr, keys := range nodeKeys {
|
||||||
|
for _, key := range keys {
|
||||||
|
keyNodes[key] = append(keyNodes[key], addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidAddr := "c1"
|
||||||
|
invalidKey := "d1"
|
||||||
|
|
||||||
|
for addr, keys := range nodeKeys {
|
||||||
|
for _, key := range keys {
|
||||||
|
err := store.Put(common.HexToAddress(addr), common.Hex2Bytes(key), []byte("data"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := "http://" + explorerAddr
|
||||||
|
|
||||||
|
t.Run("has key", func(t *testing.T) {
|
||||||
|
for addr, keys := range nodeKeys {
|
||||||
|
for _, key := range keys {
|
||||||
|
testStatusResponse(t, endpoint+"/api/has-key/"+addr+"/"+key, http.StatusOK)
|
||||||
|
testStatusResponse(t, endpoint+"/api/has-key/"+invalidAddr+"/"+key, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
testStatusResponse(t, endpoint+"/api/has-key/"+addr+"/"+invalidKey, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
testStatusResponse(t, endpoint+"/api/has-key/"+invalidAddr+"/"+invalidKey, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("keys", func(t *testing.T) {
|
||||||
|
var keys []string
|
||||||
|
for key := range keyNodes {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
testKeysResponse(t, endpoint+"/api/keys", explorer.KeysResponse{
|
||||||
|
Keys: keys,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nodes", func(t *testing.T) {
|
||||||
|
var nodes []string
|
||||||
|
for addr := range nodeKeys {
|
||||||
|
nodes = append(nodes, common.HexToAddress(addr).Hex())
|
||||||
|
}
|
||||||
|
sort.Strings(nodes)
|
||||||
|
testNodesResponse(t, endpoint+"/api/nodes", explorer.NodesResponse{
|
||||||
|
Nodes: nodes,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("node keys", func(t *testing.T) {
|
||||||
|
for addr, keys := range nodeKeys {
|
||||||
|
testKeysResponse(t, endpoint+"/api/keys?node="+addr, explorer.KeysResponse{
|
||||||
|
Keys: keys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
testKeysResponse(t, endpoint+"/api/keys?node="+invalidAddr, explorer.KeysResponse{})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("key nodes", func(t *testing.T) {
|
||||||
|
for key, addrs := range keyNodes {
|
||||||
|
var nodes []string
|
||||||
|
for _, addr := range addrs {
|
||||||
|
nodes = append(nodes, common.HexToAddress(addr).Hex())
|
||||||
|
}
|
||||||
|
sort.Strings(nodes)
|
||||||
|
testNodesResponse(t, endpoint+"/api/nodes?key="+key, explorer.NodesResponse{
|
||||||
|
Nodes: nodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
testNodesResponse(t, endpoint+"/api/nodes?key="+invalidKey, explorer.NodesResponse{})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExplorer_CORSOrigin validates if chunk explorer returns
|
||||||
|
// correct CORS origin header in GET and OPTIONS requests.
|
||||||
|
func TestExplorer_CORSOrigin(t *testing.T) {
|
||||||
|
origin := "http://localhost/"
|
||||||
|
addr := findFreeTCPAddress(t)
|
||||||
|
explorerAddr := findFreeTCPAddress(t)
|
||||||
|
testCmd := runGlobalStore(t, "ws",
|
||||||
|
"--addr", addr,
|
||||||
|
"--explorer-address", explorerAddr,
|
||||||
|
"--explorer-cors-origin", origin,
|
||||||
|
)
|
||||||
|
defer testCmd.Kill()
|
||||||
|
|
||||||
|
// wait until the server is started
|
||||||
|
waitHTTPEndpoint(t, explorerAddr)
|
||||||
|
|
||||||
|
url := "http://" + explorerAddr + "/api/keys"
|
||||||
|
|
||||||
|
t.Run("get", func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Origin", origin)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
header := resp.Header.Get("Access-Control-Allow-Origin")
|
||||||
|
if header != origin {
|
||||||
|
t.Errorf("got Access-Control-Allow-Origin header %q, want %q", header, origin)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("preflight", func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodOptions, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Origin", origin)
|
||||||
|
req.Header.Set("Access-Control-Request-Method", "GET")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
header := resp.Header.Get("Access-Control-Allow-Origin")
|
||||||
|
if header != origin {
|
||||||
|
t.Errorf("got Access-Control-Allow-Origin header %q, want %q", header, origin)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// testStatusResponse makes an http request to provided url
|
||||||
|
// and validates if response is explorer.StatusResponse for
|
||||||
|
// the expected status code.
|
||||||
|
func testStatusResponse(t *testing.T, url string, code int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != code {
|
||||||
|
t.Errorf("got status code %v, want %v", resp.StatusCode, code)
|
||||||
|
}
|
||||||
|
var r explorer.StatusResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r.Code != code {
|
||||||
|
t.Errorf("got response code %v, want %v", r.Code, code)
|
||||||
|
}
|
||||||
|
if r.Message != http.StatusText(code) {
|
||||||
|
t.Errorf("got response message %q, want %q", r.Message, http.StatusText(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testKeysResponse makes an http request to provided url
|
||||||
|
// and validates if response machhes expected explorer.KeysResponse.
|
||||||
|
func testKeysResponse(t *testing.T, url string, want explorer.KeysResponse) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
var r explorer.KeysResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if fmt.Sprint(r.Keys) != fmt.Sprint(want.Keys) {
|
||||||
|
t.Errorf("got keys %v, want %v", r.Keys, want.Keys)
|
||||||
|
}
|
||||||
|
if r.Next != want.Next {
|
||||||
|
t.Errorf("got next %s, want %s", r.Next, want.Next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNodeResponse makes an http request to provided url
|
||||||
|
// and validates if response machhes expected explorer.NodeResponse.
|
||||||
|
func testNodesResponse(t *testing.T, url string, want explorer.NodesResponse) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
var r explorer.NodesResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if fmt.Sprint(r.Nodes) != fmt.Sprint(want.Nodes) {
|
||||||
|
t.Errorf("got nodes %v, want %v", r.Nodes, want.Nodes)
|
||||||
|
}
|
||||||
|
if r.Next != want.Next {
|
||||||
|
t.Errorf("got next %s, want %s", r.Next, want.Next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -66,7 +67,7 @@ func startWS(ctx *cli.Context) (err error) {
|
||||||
return http.Serve(listener, server.WebsocketHandler(origins))
|
return http.Serve(listener, server.WebsocketHandler(origins))
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServer creates a global store and returns its RPC server.
|
// newServer creates a global store and starts a chunk explorer server if configured.
|
||||||
// Returned cleanup function should be called only if err is nil.
|
// Returned cleanup function should be called only if err is nil.
|
||||||
func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error) {
|
func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error) {
|
||||||
log.PrintOrigins(true)
|
log.PrintOrigins(true)
|
||||||
|
|
@ -81,7 +82,9 @@ func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
cleanup = func() {
|
cleanup = func() {
|
||||||
dbStore.Close()
|
if err := dbStore.Close(); err != nil {
|
||||||
|
log.Error("global store: close", "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
globalStore = dbStore
|
globalStore = dbStore
|
||||||
log.Info("database global store", "dir", dir)
|
log.Info("database global store", "dir", dir)
|
||||||
|
|
@ -96,5 +99,22 @@ func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shutdown, err := serveChunkExplorer(ctx, globalStore)
|
||||||
|
if err != nil {
|
||||||
|
cleanup()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if shutdown != nil {
|
||||||
|
cleanup = func() {
|
||||||
|
shutdown()
|
||||||
|
|
||||||
|
if c, ok := globalStore.(io.Closer); ok {
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
log.Error("global store: close", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return server, cleanup, nil
|
return server, cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,7 @@ func testHTTP(t *testing.T, put bool, args ...string) {
|
||||||
|
|
||||||
// wait until global store process is started as
|
// wait until global store process is started as
|
||||||
// rpc.DialHTTP is actually not connecting
|
// rpc.DialHTTP is actually not connecting
|
||||||
for i := 0; i < 1000; i++ {
|
waitHTTPEndpoint(t, addr)
|
||||||
_, err = http.DefaultClient.Get("http://" + addr)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
store := mockRPC.NewGlobalStore(client)
|
store := mockRPC.NewGlobalStore(client)
|
||||||
defer store.Close()
|
defer store.Close()
|
||||||
|
|
@ -137,19 +128,7 @@ func testWebsocket(t *testing.T, put bool, args ...string) {
|
||||||
testCmd := runGlobalStore(t, append([]string{"ws", "--addr", addr}, args...)...)
|
testCmd := runGlobalStore(t, append([]string{"ws", "--addr", addr}, args...)...)
|
||||||
defer testCmd.Kill()
|
defer testCmd.Kill()
|
||||||
|
|
||||||
var client *rpc.Client
|
client := websocketClient(t, addr)
|
||||||
var err error
|
|
||||||
// wait until global store process is started
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
client, err = rpc.DialWebsocket(context.Background(), "ws://"+addr, "")
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
store := mockRPC.NewGlobalStore(client)
|
store := mockRPC.NewGlobalStore(client)
|
||||||
defer store.Close()
|
defer store.Close()
|
||||||
|
|
@ -160,7 +139,7 @@ func testWebsocket(t *testing.T, put bool, args ...string) {
|
||||||
wantValue := "value"
|
wantValue := "value"
|
||||||
|
|
||||||
if put {
|
if put {
|
||||||
err = node.Put([]byte(wantKey), []byte(wantValue))
|
err := node.Put([]byte(wantKey), []byte(wantValue))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -189,3 +168,40 @@ func findFreeTCPAddress(t *testing.T) (addr string) {
|
||||||
|
|
||||||
return listener.Addr().String()
|
return listener.Addr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// websocketClient waits until global store process is started
|
||||||
|
// and returns rpc client.
|
||||||
|
func websocketClient(t *testing.T, addr string) (client *rpc.Client) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
client, err = rpc.DialWebsocket(context.Background(), "ws://"+addr, "")
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitHTTPEndpoint retries http requests to a provided
|
||||||
|
// address until the connection is established.
|
||||||
|
func waitHTTPEndpoint(t *testing.T, addr string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
_, err = http.Get("http://" + addr)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
cli "gopkg.in/urfave/cli.v1"
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
var (
|
||||||
|
version = "0.1"
|
||||||
|
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := newApp().Run(os.Args)
|
err := newApp().Run(os.Args)
|
||||||
|
|
@ -37,16 +39,30 @@ func main() {
|
||||||
// newApp construct a new instance of Swarm Global Store.
|
// newApp construct a new instance of Swarm Global Store.
|
||||||
// Method Run is called on it in the main function and in tests.
|
// Method Run is called on it in the main function and in tests.
|
||||||
func newApp() (app *cli.App) {
|
func newApp() (app *cli.App) {
|
||||||
app = utils.NewApp(gitCommit, "Swarm Global Store")
|
app = cli.NewApp()
|
||||||
|
|
||||||
app.Name = "global-store"
|
app.Name = "global-store"
|
||||||
|
app.Version = version
|
||||||
|
if len(gitCommit) >= 8 {
|
||||||
|
app.Version += "-" + gitCommit[:8]
|
||||||
|
}
|
||||||
|
app.Usage = "Swarm Global Store"
|
||||||
|
|
||||||
// app flags (for all commands)
|
// app flags (for all commands)
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "verbosity",
|
Name: "verbosity",
|
||||||
Value: 3,
|
Value: 3,
|
||||||
Usage: "verbosity level",
|
Usage: "Verbosity level.",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "explorer-address",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Chunk explorer HTTP listener address.",
|
||||||
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "explorer-cors-origin",
|
||||||
|
Value: nil,
|
||||||
|
Usage: "Chunk explorer CORS origin (can be specified multiple times).",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +70,7 @@ func newApp() (app *cli.App) {
|
||||||
{
|
{
|
||||||
Name: "http",
|
Name: "http",
|
||||||
Aliases: []string{"h"},
|
Aliases: []string{"h"},
|
||||||
Usage: "start swarm global store with http server",
|
Usage: "Start swarm global store with HTTP server.",
|
||||||
Action: startHTTP,
|
Action: startHTTP,
|
||||||
// Flags only for "start" command.
|
// Flags only for "start" command.
|
||||||
// Allow app flags to be specified after the
|
// Allow app flags to be specified after the
|
||||||
|
|
@ -63,19 +79,19 @@ func newApp() (app *cli.App) {
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "dir",
|
Name: "dir",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "data directory",
|
Usage: "Data directory.",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "addr",
|
Name: "addr",
|
||||||
Value: "0.0.0.0:3033",
|
Value: "0.0.0.0:3033",
|
||||||
Usage: "address to listen for http connection",
|
Usage: "Address to listen for HTTP connections.",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "websocket",
|
Name: "websocket",
|
||||||
Aliases: []string{"ws"},
|
Aliases: []string{"ws"},
|
||||||
Usage: "start swarm global store with websocket server",
|
Usage: "Start swarm global store with WebSocket server.",
|
||||||
Action: startWS,
|
Action: startWS,
|
||||||
// Flags only for "start" command.
|
// Flags only for "start" command.
|
||||||
// Allow app flags to be specified after the
|
// Allow app flags to be specified after the
|
||||||
|
|
@ -84,17 +100,17 @@ func newApp() (app *cli.App) {
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "dir",
|
Name: "dir",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "data directory",
|
Usage: "Data directory.",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "addr",
|
Name: "addr",
|
||||||
Value: "0.0.0.0:3033",
|
Value: "0.0.0.0:3033",
|
||||||
Usage: "address to listen for websocket connection",
|
Usage: "Address to listen for WebSocket connections.",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "origins",
|
Name: "origin",
|
||||||
Value: &cli.StringSlice{"*"},
|
Value: nil,
|
||||||
Usage: "websocket origins",
|
Usage: "WebSocket CORS origin (can be specified multiple times).",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,8 @@ var gitCommit string
|
||||||
|
|
||||||
//declare a few constant error messages, useful for later error check comparisons in test
|
//declare a few constant error messages, useful for later error check comparisons in test
|
||||||
var (
|
var (
|
||||||
SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
SwarmErrNoBZZAccount = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
||||||
SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
|
SwarmErrSwapSetNoAPI = "SWAP is enabled but --swap-api is not set"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this help command gets added to any subcommand that does not define it explicitly
|
// this help command gets added to any subcommand that does not define it explicitly
|
||||||
|
|
@ -351,7 +351,7 @@ func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
|
||||||
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||||
//an account is mandatory
|
//an account is mandatory
|
||||||
if bzzaccount == "" {
|
if bzzaccount == "" {
|
||||||
utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
|
utils.Fatalf(SwarmErrNoBZZAccount)
|
||||||
}
|
}
|
||||||
// Try to load the arg as a hex key file.
|
// Try to load the arg as a hex key file.
|
||||||
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
|
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,10 @@ func upload(ctx *cli.Context) {
|
||||||
autoDefaultPath = false
|
autoDefaultPath = false
|
||||||
file string
|
file string
|
||||||
)
|
)
|
||||||
if autoDefaultPathString := os.Getenv(SWARM_AUTO_DEFAULTPATH); autoDefaultPathString != "" {
|
if autoDefaultPathString := os.Getenv(SwarmAutoDefaultPath); autoDefaultPathString != "" {
|
||||||
b, err := strconv.ParseBool(autoDefaultPathString)
|
b, err := strconv.ParseBool(autoDefaultPathString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("invalid environment variable %s: %v", SWARM_AUTO_DEFAULTPATH, err)
|
utils.Fatalf("invalid environment variable %s: %v", SwarmAutoDefaultPath, err)
|
||||||
}
|
}
|
||||||
autoDefaultPath = b
|
autoDefaultPath = b
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,9 +199,19 @@ var (
|
||||||
}
|
}
|
||||||
LightServFlag = cli.IntFlag{
|
LightServFlag = cli.IntFlag{
|
||||||
Name: "lightserv",
|
Name: "lightserv",
|
||||||
Usage: "Maximum percentage of time allowed for serving LES requests (0-90)",
|
Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
}
|
}
|
||||||
|
LightBandwidthInFlag = cli.IntFlag{
|
||||||
|
Name: "lightbwin",
|
||||||
|
Usage: "Incoming bandwidth limit for light server (1000 bytes/sec, 0 = unlimited)",
|
||||||
|
Value: 1000,
|
||||||
|
}
|
||||||
|
LightBandwidthOutFlag = cli.IntFlag{
|
||||||
|
Name: "lightbwout",
|
||||||
|
Usage: "Outgoing bandwidth limit for light server (1000 bytes/sec, 0 = unlimited)",
|
||||||
|
Value: 5000,
|
||||||
|
}
|
||||||
LightPeersFlag = cli.IntFlag{
|
LightPeersFlag = cli.IntFlag{
|
||||||
Name: "lightpeers",
|
Name: "lightpeers",
|
||||||
Usage: "Maximum number of LES client peers",
|
Usage: "Maximum number of LES client peers",
|
||||||
|
|
@ -1305,6 +1315,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
if ctx.GlobalIsSet(LightServFlag.Name) {
|
if ctx.GlobalIsSet(LightServFlag.Name) {
|
||||||
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
|
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
|
||||||
}
|
}
|
||||||
|
cfg.LightBandwidthIn = ctx.GlobalInt(LightBandwidthInFlag.Name)
|
||||||
|
cfg.LightBandwidthOut = ctx.GlobalInt(LightBandwidthOutFlag.Name)
|
||||||
if ctx.GlobalIsSet(LightPeersFlag.Name) {
|
if ctx.GlobalIsSet(LightPeersFlag.Name) {
|
||||||
cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name)
|
cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
common/fdlimit/fdlimit_darwin.go
Normal file
71
common/fdlimit/fdlimit_darwin.go
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package fdlimit
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// hardlimit is the number of file descriptors allowed at max by the kernel.
|
||||||
|
const hardlimit = 10240
|
||||||
|
|
||||||
|
// Raise tries to maximize the file descriptor allowance of this process
|
||||||
|
// to the maximum hard-limit allowed by the OS.
|
||||||
|
// Returns the size it was set to (may differ from the desired 'max')
|
||||||
|
func Raise(max uint64) (uint64, error) {
|
||||||
|
// Get the current limit
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Try to update the limit to the max allowance
|
||||||
|
limit.Cur = limit.Max
|
||||||
|
if limit.Cur > max {
|
||||||
|
limit.Cur = max
|
||||||
|
}
|
||||||
|
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// MacOS can silently apply further caps, so retrieve the actually set limit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return limit.Cur, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current retrieves the number of file descriptors allowed to be opened by this
|
||||||
|
// process.
|
||||||
|
func Current() (int, error) {
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(limit.Cur), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func Maximum() (int, error) {
|
||||||
|
// Retrieve the maximum allowed by dynamic OS limits
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Cap it to OPEN_MAX (10240) because macos is a special snowflake
|
||||||
|
if limit.Max > hardlimit {
|
||||||
|
limit.Max = hardlimit
|
||||||
|
}
|
||||||
|
return int(limit.Max), nil
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// +build linux darwin netbsd openbsd solaris
|
// +build linux netbsd openbsd solaris
|
||||||
|
|
||||||
package fdlimit
|
package fdlimit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package fdlimit
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// hardlimit is the number of file descriptors allowed at max by the kernel.
|
||||||
const hardlimit = 16384
|
const hardlimit = 16384
|
||||||
|
|
||||||
// Raise tries to maximize the file descriptor allowance of this process
|
// Raise tries to maximize the file descriptor allowance of this process
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
FROM alpine:3.7
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
apk add --update go git make gcc musl-dev linux-headers ca-certificates && \
|
|
||||||
git clone --depth 1 https://github.com/ethereum/go-ethereum && \
|
|
||||||
(cd go-ethereum && make geth) && \
|
|
||||||
cp go-ethereum/build/bin/geth /geth && \
|
|
||||||
apk del go git make gcc musl-dev linux-headers && \
|
|
||||||
rm -rf /go-ethereum && rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
EXPOSE 8545
|
|
||||||
EXPOSE 30303
|
|
||||||
|
|
||||||
ENTRYPOINT ["/geth"]
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
FROM ubuntu:xenial
|
|
||||||
|
|
||||||
ENV PATH=/usr/lib/go-1.9/bin:$PATH
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
apt-get update && apt-get upgrade -q -y && \
|
|
||||||
apt-get install -y --no-install-recommends golang-1.9 git make gcc libc-dev ca-certificates && \
|
|
||||||
git clone --depth 1 https://github.com/ethereum/go-ethereum && \
|
|
||||||
(cd go-ethereum && make geth) && \
|
|
||||||
cp go-ethereum/build/bin/geth /geth && \
|
|
||||||
apt-get remove -y golang-1.9 git make gcc libc-dev && apt autoremove -y && apt-get clean && \
|
|
||||||
rm -rf /go-ethereum
|
|
||||||
|
|
||||||
EXPOSE 8545
|
|
||||||
EXPOSE 30303
|
|
||||||
|
|
||||||
ENTRYPOINT ["/geth"]
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
FROM alpine:3.7
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
apk add --update go git make gcc musl-dev linux-headers ca-certificates && \
|
|
||||||
git clone --depth 1 --branch release/1.8 https://github.com/ethereum/go-ethereum && \
|
|
||||||
(cd go-ethereum && make geth) && \
|
|
||||||
cp go-ethereum/build/bin/geth /geth && \
|
|
||||||
apk del go git make gcc musl-dev linux-headers && \
|
|
||||||
rm -rf /go-ethereum && rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
EXPOSE 8545
|
|
||||||
EXPOSE 30303
|
|
||||||
|
|
||||||
ENTRYPOINT ["/geth"]
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
FROM ubuntu:xenial
|
|
||||||
|
|
||||||
ENV PATH=/usr/lib/go-1.9/bin:$PATH
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
apt-get update && apt-get upgrade -q -y && \
|
|
||||||
apt-get install -y --no-install-recommends golang-1.9 git make gcc libc-dev ca-certificates && \
|
|
||||||
git clone --depth 1 --branch release/1.8 https://github.com/ethereum/go-ethereum && \
|
|
||||||
(cd go-ethereum && make geth) && \
|
|
||||||
cp go-ethereum/build/bin/geth /geth && \
|
|
||||||
apt-get remove -y golang-1.9 git make gcc libc-dev && apt autoremove -y && apt-get clean && \
|
|
||||||
rm -rf /go-ethereum
|
|
||||||
|
|
||||||
EXPOSE 8545
|
|
||||||
EXPOSE 30303
|
|
||||||
|
|
||||||
ENTRYPOINT ["/geth"]
|
|
||||||
|
|
@ -111,6 +111,7 @@ type BlockChain struct {
|
||||||
chainSideFeed event.Feed
|
chainSideFeed event.Feed
|
||||||
chainHeadFeed event.Feed
|
chainHeadFeed event.Feed
|
||||||
logsFeed event.Feed
|
logsFeed event.Feed
|
||||||
|
blockProcFeed event.Feed
|
||||||
scope event.SubscriptionScope
|
scope event.SubscriptionScope
|
||||||
genesisBlock *types.Block
|
genesisBlock *types.Block
|
||||||
|
|
||||||
|
|
@ -1090,6 +1091,10 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||||
if len(chain) == 0 {
|
if len(chain) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bc.blockProcFeed.Send(true)
|
||||||
|
defer bc.blockProcFeed.Send(false)
|
||||||
|
|
||||||
// Remove already known canon-blocks
|
// Remove already known canon-blocks
|
||||||
var (
|
var (
|
||||||
block, prev *types.Block
|
block, prev *types.Block
|
||||||
|
|
@ -1725,3 +1730,9 @@ func (bc *BlockChain) SubscribeChainSideEvent(ch chan<- ChainSideEvent) event.Su
|
||||||
func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||||
return bc.scope.Track(bc.logsFeed.Subscribe(ch))
|
return bc.scope.Track(bc.logsFeed.Subscribe(ch))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeBlockProcessingEvent registers a subscription of bool where true means
|
||||||
|
// block processing has started while false means it has stopped.
|
||||||
|
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
|
||||||
|
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ func validateEvents(events chan NewTxsEvent, count int) error {
|
||||||
case ev := <-events:
|
case ev := <-events:
|
||||||
received = append(received, ev.Txs...)
|
received = append(received, ev.Txs...)
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
return fmt.Errorf("event #%d not fired", received)
|
return fmt.Errorf("event #%d not fired", len(received))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(received) > count {
|
if len(received) > count {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ func TestRandomG2Marshal(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Logf("%d: %x\n", n, g2.Marshal())
|
t.Logf("%v: %x\n", n, g2.Marshal())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ func TestRandomG2Marshal(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Logf("%d: %x\n", n, g2.Marshal())
|
t.Logf("%v: %x\n", n, g2.Marshal())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ import (
|
||||||
type LesServer interface {
|
type LesServer interface {
|
||||||
Start(srvr *p2p.Server)
|
Start(srvr *p2p.Server)
|
||||||
Stop()
|
Stop()
|
||||||
|
APIs() []rpc.API
|
||||||
Protocols() []p2p.Protocol
|
Protocols() []p2p.Protocol
|
||||||
SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
|
SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
|
||||||
}
|
}
|
||||||
|
|
@ -267,6 +268,10 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo
|
||||||
func (s *Ethereum) APIs() []rpc.API {
|
func (s *Ethereum) APIs() []rpc.API {
|
||||||
apis := ethapi.GetAPIs(s.APIBackend)
|
apis := ethapi.GetAPIs(s.APIBackend)
|
||||||
|
|
||||||
|
// Append any APIs exposed explicitly by the les server
|
||||||
|
if s.lesServer != nil {
|
||||||
|
apis = append(apis, s.lesServer.APIs()...)
|
||||||
|
}
|
||||||
// Append any APIs exposed explicitly by the consensus engine
|
// Append any APIs exposed explicitly by the consensus engine
|
||||||
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ type Config struct {
|
||||||
|
|
||||||
// Light client options
|
// Light client options
|
||||||
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
|
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
|
||||||
|
LightBandwidthIn int `toml:",omitempty"` // Incoming bandwidth limit for light servers
|
||||||
|
LightBandwidthOut int `toml:",omitempty"` // Outgoing bandwidth limit for light servers
|
||||||
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
|
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
|
||||||
OnlyAnnounce bool // Maximum number of LES client peers
|
OnlyAnnounce bool // Maximum number of LES client peers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
SyncMode downloader.SyncMode
|
SyncMode downloader.SyncMode
|
||||||
NoPruning bool
|
NoPruning bool
|
||||||
LightServ int `toml:",omitempty"`
|
LightServ int `toml:",omitempty"`
|
||||||
|
LightBandwidthIn int `toml:",omitempty"`
|
||||||
|
LightBandwidthOut int `toml:",omitempty"`
|
||||||
LightPeers int `toml:",omitempty"`
|
LightPeers int `toml:",omitempty"`
|
||||||
OnlyAnnounce bool
|
OnlyAnnounce bool
|
||||||
ULC *ULCConfig `toml:",omitempty"`
|
ULC *ULCConfig `toml:",omitempty"`
|
||||||
|
|
@ -55,6 +57,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.SyncMode = c.SyncMode
|
enc.SyncMode = c.SyncMode
|
||||||
enc.NoPruning = c.NoPruning
|
enc.NoPruning = c.NoPruning
|
||||||
enc.LightServ = c.LightServ
|
enc.LightServ = c.LightServ
|
||||||
|
enc.LightBandwidthIn = c.LightBandwidthIn
|
||||||
|
enc.LightBandwidthOut = c.LightBandwidthOut
|
||||||
enc.LightPeers = c.LightPeers
|
enc.LightPeers = c.LightPeers
|
||||||
enc.OnlyAnnounce = c.OnlyAnnounce
|
enc.OnlyAnnounce = c.OnlyAnnounce
|
||||||
enc.ULC = c.ULC
|
enc.ULC = c.ULC
|
||||||
|
|
@ -91,6 +95,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
SyncMode *downloader.SyncMode
|
SyncMode *downloader.SyncMode
|
||||||
NoPruning *bool
|
NoPruning *bool
|
||||||
LightServ *int `toml:",omitempty"`
|
LightServ *int `toml:",omitempty"`
|
||||||
|
LightBandwidthIn *int `toml:",omitempty"`
|
||||||
|
LightBandwidthOut *int `toml:",omitempty"`
|
||||||
LightPeers *int `toml:",omitempty"`
|
LightPeers *int `toml:",omitempty"`
|
||||||
OnlyAnnounce *bool
|
OnlyAnnounce *bool
|
||||||
ULC *ULCConfig `toml:",omitempty"`
|
ULC *ULCConfig `toml:",omitempty"`
|
||||||
|
|
@ -135,6 +141,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.LightServ != nil {
|
if dec.LightServ != nil {
|
||||||
c.LightServ = *dec.LightServ
|
c.LightServ = *dec.LightServ
|
||||||
}
|
}
|
||||||
|
if dec.LightBandwidthIn != nil {
|
||||||
|
c.LightBandwidthIn = *dec.LightBandwidthIn
|
||||||
|
}
|
||||||
|
if dec.LightBandwidthOut != nil {
|
||||||
|
c.LightBandwidthOut = *dec.LightBandwidthOut
|
||||||
|
}
|
||||||
if dec.LightPeers != nil {
|
if dec.LightPeers != nil {
|
||||||
c.LightPeers = *dec.LightPeers
|
c.LightPeers = *dec.LightPeers
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes {
|
||||||
return hexutil.Bytes(l.log.Data)
|
return hexutil.Bytes(l.log.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transactionn represents an Ethereum transaction.
|
// Transaction represents an Ethereum transaction.
|
||||||
// backend and hash are mandatory; all others will be fetched when required.
|
// backend and hash are mandatory; all others will be fetched when required.
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
backend *eth.EthAPIBackend
|
backend *eth.EthAPIBackend
|
||||||
|
|
@ -916,7 +916,7 @@ func (r *Resolver) EstimateGas(ctx context.Context, args struct {
|
||||||
return gas, err
|
return gas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterCritera encapsulates the arguments to `logs` on the root resolver object.
|
// FilterCriteria encapsulates the arguments to `logs` on the root resolver object.
|
||||||
type FilterCriteria struct {
|
type FilterCriteria struct {
|
||||||
FromBlock *hexutil.Uint64 // beginning of the queried range, nil means genesis block
|
FromBlock *hexutil.Uint64 // beginning of the queried range, nil means genesis block
|
||||||
ToBlock *hexutil.Uint64 // end of the range, nil means latest block
|
ToBlock *hexutil.Uint64 // end of the range, nil means latest block
|
||||||
|
|
|
||||||
|
|
@ -143,9 +143,9 @@ func CopyFile(dst, src string, mode os.FileMode) {
|
||||||
// so that go commands executed by build use the same version of Go as the 'host' that runs
|
// so that go commands executed by build use the same version of Go as the 'host' that runs
|
||||||
// build code. e.g.
|
// build code. e.g.
|
||||||
//
|
//
|
||||||
// /usr/lib/go-1.11/bin/go run build/ci.go ...
|
// /usr/lib/go-1.12/bin/go run build/ci.go ...
|
||||||
//
|
//
|
||||||
// runs using go 1.11 and invokes go 1.11 tools from the same GOROOT. This is also important
|
// runs using go 1.12 and invokes go 1.12 tools from the same GOROOT. This is also important
|
||||||
// because runtime.Version checks on the host should match the tools that are run.
|
// because runtime.Version checks on the host should match the tools that are run.
|
||||||
func GoTool(tool string, args ...string) *exec.Cmd {
|
func GoTool(tool string, args ...string) *exec.Cmd {
|
||||||
args = append([]string{tool}, args...)
|
args = append([]string{tool}, args...)
|
||||||
|
|
|
||||||
454
les/api.go
Normal file
454
les/api.go
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrMinCap = errors.New("capacity too small")
|
||||||
|
ErrTotalCap = errors.New("total capacity exceeded")
|
||||||
|
ErrUnknownBenchmarkType = errors.New("unknown benchmark type")
|
||||||
|
|
||||||
|
dropCapacityDelay = time.Second // delay applied to decreasing capacity changes
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivateLightServerAPI provides an API to access the LES light server.
|
||||||
|
// It offers only methods that operate on public data that is freely available to anyone.
|
||||||
|
type PrivateLightServerAPI struct {
|
||||||
|
server *LesServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateLightServerAPI creates a new LES light server API.
|
||||||
|
func NewPrivateLightServerAPI(server *LesServer) *PrivateLightServerAPI {
|
||||||
|
return &PrivateLightServerAPI{
|
||||||
|
server: server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalCapacity queries total available capacity for all clients
|
||||||
|
func (api *PrivateLightServerAPI) TotalCapacity() hexutil.Uint64 {
|
||||||
|
return hexutil.Uint64(api.server.priorityClientPool.totalCapacity())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeTotalCapacity subscribes to changed total capacity events.
|
||||||
|
// If onlyUnderrun is true then notification is sent only if the total capacity
|
||||||
|
// drops under the total capacity of connected priority clients.
|
||||||
|
//
|
||||||
|
// Note: actually applying decreasing total capacity values is delayed while the
|
||||||
|
// notification is sent instantly. This allows lowering the capacity of a priority client
|
||||||
|
// or choosing which one to drop before the system drops some of them automatically.
|
||||||
|
func (api *PrivateLightServerAPI) SubscribeTotalCapacity(ctx context.Context, onlyUnderrun bool) (*rpc.Subscription, error) {
|
||||||
|
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||||
|
if !supported {
|
||||||
|
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
||||||
|
}
|
||||||
|
rpcSub := notifier.CreateSubscription()
|
||||||
|
api.server.priorityClientPool.subscribeTotalCapacity(&tcSubscription{notifier, rpcSub, onlyUnderrun})
|
||||||
|
return rpcSub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// tcSubscription represents a total capacity subscription
|
||||||
|
tcSubscription struct {
|
||||||
|
notifier *rpc.Notifier
|
||||||
|
rpcSub *rpc.Subscription
|
||||||
|
onlyUnderrun bool
|
||||||
|
}
|
||||||
|
tcSubs map[*tcSubscription]struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// send sends a changed total capacity event to the subscribers
|
||||||
|
func (s tcSubs) send(tc uint64, underrun bool) {
|
||||||
|
for sub := range s {
|
||||||
|
select {
|
||||||
|
case <-sub.rpcSub.Err():
|
||||||
|
delete(s, sub)
|
||||||
|
case <-sub.notifier.Closed():
|
||||||
|
delete(s, sub)
|
||||||
|
default:
|
||||||
|
if underrun || !sub.onlyUnderrun {
|
||||||
|
sub.notifier.Notify(sub.rpcSub.ID, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimumCapacity queries minimum assignable capacity for a single client
|
||||||
|
func (api *PrivateLightServerAPI) MinimumCapacity() hexutil.Uint64 {
|
||||||
|
return hexutil.Uint64(minCapacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeClientCapacity queries the capacity provided for free clients
|
||||||
|
func (api *PrivateLightServerAPI) FreeClientCapacity() hexutil.Uint64 {
|
||||||
|
return hexutil.Uint64(api.server.freeClientCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClientCapacity sets the priority capacity assigned to a given client.
|
||||||
|
// If the assigned capacity is bigger than zero then connection is always
|
||||||
|
// guaranteed. The sum of capacity assigned to priority clients can not exceed
|
||||||
|
// the total available capacity.
|
||||||
|
//
|
||||||
|
// Note: assigned capacity can be changed while the client is connected with
|
||||||
|
// immediate effect.
|
||||||
|
func (api *PrivateLightServerAPI) SetClientCapacity(id enode.ID, cap uint64) error {
|
||||||
|
if cap != 0 && cap < minCapacity {
|
||||||
|
return ErrMinCap
|
||||||
|
}
|
||||||
|
return api.server.priorityClientPool.setClientCapacity(id, cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientCapacity returns the capacity assigned to a given client
|
||||||
|
func (api *PrivateLightServerAPI) GetClientCapacity(id enode.ID) hexutil.Uint64 {
|
||||||
|
api.server.priorityClientPool.lock.Lock()
|
||||||
|
defer api.server.priorityClientPool.lock.Unlock()
|
||||||
|
|
||||||
|
return hexutil.Uint64(api.server.priorityClientPool.clients[id].cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientPool is implemented by both the free and priority client pools
|
||||||
|
type clientPool interface {
|
||||||
|
peerSetNotify
|
||||||
|
setLimits(count int, totalCap uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// priorityClientPool stores information about prioritized clients
|
||||||
|
type priorityClientPool struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
child clientPool
|
||||||
|
ps *peerSet
|
||||||
|
clients map[enode.ID]priorityClientInfo
|
||||||
|
totalCap, totalCapAnnounced uint64
|
||||||
|
totalConnectedCap, freeClientCap uint64
|
||||||
|
maxPeers, priorityCount int
|
||||||
|
|
||||||
|
subs tcSubs
|
||||||
|
updateSchedule []scheduledUpdate
|
||||||
|
scheduleCounter uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduledUpdate represents a delayed total capacity update
|
||||||
|
type scheduledUpdate struct {
|
||||||
|
time mclock.AbsTime
|
||||||
|
totalCap, id uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// priorityClientInfo entries exist for all prioritized clients and currently connected non-priority clients
|
||||||
|
type priorityClientInfo struct {
|
||||||
|
cap uint64 // zero for non-priority clients
|
||||||
|
connected bool
|
||||||
|
peer *peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPriorityClientPool creates a new priority client pool
|
||||||
|
func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool) *priorityClientPool {
|
||||||
|
return &priorityClientPool{
|
||||||
|
clients: make(map[enode.ID]priorityClientInfo),
|
||||||
|
freeClientCap: freeClientCap,
|
||||||
|
ps: ps,
|
||||||
|
child: child,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerPeer is called when a new client is connected. If the client has no
|
||||||
|
// priority assigned then it is passed to the child pool which may either keep it
|
||||||
|
// or disconnect it.
|
||||||
|
//
|
||||||
|
// Note: priorityClientPool also stores a record about free clients while they are
|
||||||
|
// connected in order to be able to assign priority to them later.
|
||||||
|
func (v *priorityClientPool) registerPeer(p *peer) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
id := p.ID()
|
||||||
|
c := v.clients[id]
|
||||||
|
if c.connected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.cap == 0 && v.child != nil {
|
||||||
|
v.child.registerPeer(p)
|
||||||
|
}
|
||||||
|
if c.cap != 0 && v.totalConnectedCap+c.cap > v.totalCap {
|
||||||
|
go v.ps.Unregister(p.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.connected = true
|
||||||
|
c.peer = p
|
||||||
|
v.clients[id] = c
|
||||||
|
if c.cap != 0 {
|
||||||
|
v.priorityCount++
|
||||||
|
v.totalConnectedCap += c.cap
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
||||||
|
}
|
||||||
|
p.updateCapacity(c.cap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregisterPeer is called when a client is disconnected. If the client has no
|
||||||
|
// priority assigned then it is also removed from the child pool.
|
||||||
|
func (v *priorityClientPool) unregisterPeer(p *peer) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
id := p.ID()
|
||||||
|
c := v.clients[id]
|
||||||
|
if !c.connected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.cap != 0 {
|
||||||
|
c.connected = false
|
||||||
|
v.clients[id] = c
|
||||||
|
v.priorityCount--
|
||||||
|
v.totalConnectedCap -= c.cap
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.unregisterPeer(p)
|
||||||
|
}
|
||||||
|
delete(v.clients, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLimits updates the allowed peer count and total capacity of the priority
|
||||||
|
// client pool. Since the free client pool is a child of the priority pool the
|
||||||
|
// remaining peer count and capacity is assigned to the free pool by calling its
|
||||||
|
// own setLimits function.
|
||||||
|
//
|
||||||
|
// Note: a decreasing change of the total capacity is applied with a delay.
|
||||||
|
func (v *priorityClientPool) setLimits(count int, totalCap uint64) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
v.totalCapAnnounced = totalCap
|
||||||
|
if totalCap > v.totalCap {
|
||||||
|
v.setLimitsNow(count, totalCap)
|
||||||
|
v.subs.send(totalCap, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.setLimitsNow(count, v.totalCap)
|
||||||
|
if totalCap < v.totalCap {
|
||||||
|
v.subs.send(totalCap, totalCap < v.totalConnectedCap)
|
||||||
|
for i, s := range v.updateSchedule {
|
||||||
|
if totalCap >= s.totalCap {
|
||||||
|
s.totalCap = totalCap
|
||||||
|
v.updateSchedule = v.updateSchedule[:i+1]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.updateSchedule = append(v.updateSchedule, scheduledUpdate{time: mclock.Now() + mclock.AbsTime(dropCapacityDelay), totalCap: totalCap})
|
||||||
|
if len(v.updateSchedule) == 1 {
|
||||||
|
v.scheduleCounter++
|
||||||
|
id := v.scheduleCounter
|
||||||
|
v.updateSchedule[0].id = id
|
||||||
|
time.AfterFunc(dropCapacityDelay, func() { v.checkUpdate(id) })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.updateSchedule = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkUpdate performs the next scheduled update if possible and schedules
|
||||||
|
// the one after that
|
||||||
|
func (v *priorityClientPool) checkUpdate(id uint64) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
if len(v.updateSchedule) == 0 || v.updateSchedule[0].id != id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.setLimitsNow(v.maxPeers, v.updateSchedule[0].totalCap)
|
||||||
|
v.updateSchedule = v.updateSchedule[1:]
|
||||||
|
if len(v.updateSchedule) != 0 {
|
||||||
|
v.scheduleCounter++
|
||||||
|
id := v.scheduleCounter
|
||||||
|
v.updateSchedule[0].id = id
|
||||||
|
dt := time.Duration(v.updateSchedule[0].time - mclock.Now())
|
||||||
|
time.AfterFunc(dt, func() { v.checkUpdate(id) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLimits updates the allowed peer count and total capacity immediately
|
||||||
|
func (v *priorityClientPool) setLimitsNow(count int, totalCap uint64) {
|
||||||
|
if v.priorityCount > count || v.totalConnectedCap > totalCap {
|
||||||
|
for id, c := range v.clients {
|
||||||
|
if c.connected {
|
||||||
|
c.connected = false
|
||||||
|
v.totalConnectedCap -= c.cap
|
||||||
|
v.priorityCount--
|
||||||
|
v.clients[id] = c
|
||||||
|
go v.ps.Unregister(c.peer.id)
|
||||||
|
if v.priorityCount <= count && v.totalConnectedCap <= totalCap {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.maxPeers = count
|
||||||
|
v.totalCap = totalCap
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// totalCapacity queries total available capacity for all clients
|
||||||
|
func (v *priorityClientPool) totalCapacity() uint64 {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
return v.totalCapAnnounced
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribeTotalCapacity subscribes to changed total capacity events
|
||||||
|
func (v *priorityClientPool) subscribeTotalCapacity(sub *tcSubscription) {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
v.subs[sub] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setClientCapacity sets the priority capacity assigned to a given client
|
||||||
|
func (v *priorityClientPool) setClientCapacity(id enode.ID, cap uint64) error {
|
||||||
|
v.lock.Lock()
|
||||||
|
defer v.lock.Unlock()
|
||||||
|
|
||||||
|
c := v.clients[id]
|
||||||
|
if c.cap == cap {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.connected {
|
||||||
|
if v.totalConnectedCap+cap > v.totalCap+c.cap {
|
||||||
|
return ErrTotalCap
|
||||||
|
}
|
||||||
|
if c.cap == 0 {
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.unregisterPeer(c.peer)
|
||||||
|
}
|
||||||
|
v.priorityCount++
|
||||||
|
}
|
||||||
|
if cap == 0 {
|
||||||
|
v.priorityCount--
|
||||||
|
}
|
||||||
|
v.totalConnectedCap += cap - c.cap
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
||||||
|
}
|
||||||
|
if cap == 0 {
|
||||||
|
if v.child != nil {
|
||||||
|
v.child.registerPeer(c.peer)
|
||||||
|
}
|
||||||
|
c.peer.updateCapacity(v.freeClientCap)
|
||||||
|
} else {
|
||||||
|
c.peer.updateCapacity(cap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cap != 0 || c.connected {
|
||||||
|
c.cap = cap
|
||||||
|
v.clients[id] = c
|
||||||
|
} else {
|
||||||
|
delete(v.clients, id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark runs a request performance benchmark with a given set of measurement setups
|
||||||
|
// in multiple passes specified by passCount. The measurement time for each setup in each
|
||||||
|
// pass is specified in milliseconds by length.
|
||||||
|
//
|
||||||
|
// Note: measurement time is adjusted for each pass depending on the previous ones.
|
||||||
|
// Therefore a controlled total measurement time is achievable in multiple passes.
|
||||||
|
func (api *PrivateLightServerAPI) Benchmark(setups []map[string]interface{}, passCount, length int) ([]map[string]interface{}, error) {
|
||||||
|
benchmarks := make([]requestBenchmark, len(setups))
|
||||||
|
for i, setup := range setups {
|
||||||
|
if t, ok := setup["type"].(string); ok {
|
||||||
|
getInt := func(field string, def int) int {
|
||||||
|
if value, ok := setup[field].(float64); ok {
|
||||||
|
return int(value)
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
getBool := func(field string, def bool) bool {
|
||||||
|
if value, ok := setup[field].(bool); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case "header":
|
||||||
|
benchmarks[i] = &benchmarkBlockHeaders{
|
||||||
|
amount: getInt("amount", 1),
|
||||||
|
skip: getInt("skip", 1),
|
||||||
|
byHash: getBool("byHash", false),
|
||||||
|
reverse: getBool("reverse", false),
|
||||||
|
}
|
||||||
|
case "body":
|
||||||
|
benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: false}
|
||||||
|
case "receipts":
|
||||||
|
benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: true}
|
||||||
|
case "proof":
|
||||||
|
benchmarks[i] = &benchmarkProofsOrCode{code: false}
|
||||||
|
case "code":
|
||||||
|
benchmarks[i] = &benchmarkProofsOrCode{code: true}
|
||||||
|
case "cht":
|
||||||
|
benchmarks[i] = &benchmarkHelperTrie{
|
||||||
|
bloom: false,
|
||||||
|
reqCount: getInt("amount", 1),
|
||||||
|
}
|
||||||
|
case "bloom":
|
||||||
|
benchmarks[i] = &benchmarkHelperTrie{
|
||||||
|
bloom: true,
|
||||||
|
reqCount: getInt("amount", 1),
|
||||||
|
}
|
||||||
|
case "txSend":
|
||||||
|
benchmarks[i] = &benchmarkTxSend{}
|
||||||
|
case "txStatus":
|
||||||
|
benchmarks[i] = &benchmarkTxStatus{}
|
||||||
|
default:
|
||||||
|
return nil, ErrUnknownBenchmarkType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrUnknownBenchmarkType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rs := api.server.protocolManager.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length))
|
||||||
|
result := make([]map[string]interface{}, len(setups))
|
||||||
|
for i, r := range rs {
|
||||||
|
res := make(map[string]interface{})
|
||||||
|
if r.err == nil {
|
||||||
|
res["totalCount"] = r.totalCount
|
||||||
|
res["avgTime"] = r.avgTime
|
||||||
|
res["maxInSize"] = r.maxInSize
|
||||||
|
res["maxOutSize"] = r.maxOutSize
|
||||||
|
} else {
|
||||||
|
res["error"] = r.err.Error()
|
||||||
|
}
|
||||||
|
result[i] = res
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
525
les/api_test.go
Normal file
525
les/api_test.go
Normal file
|
|
@ -0,0 +1,525 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test is not meant to be a part of the automatic testing process because it
|
||||||
|
runs for a long time and also requires a large database in order to do a meaningful
|
||||||
|
request performance test. When testServerDataDir is empty, the test is skipped.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
testServerDataDir = "" // should always be empty on the master branch
|
||||||
|
testServerCapacity = 200
|
||||||
|
testMaxClients = 10
|
||||||
|
testTolerance = 0.1
|
||||||
|
minRelCap = 0.2
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCapacityAPI3(t *testing.T) {
|
||||||
|
testCapacityAPI(t, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapacityAPI6(t *testing.T) {
|
||||||
|
testCapacityAPI(t, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapacityAPI10(t *testing.T) {
|
||||||
|
testCapacityAPI(t, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testCapacityAPI runs an end-to-end simulation test connecting one server with
|
||||||
|
// a given number of clients. It sets different priority capacities to all clients
|
||||||
|
// except a randomly selected one which runs in free client mode. All clients send
|
||||||
|
// similar requests at the maximum allowed rate and the test verifies whether the
|
||||||
|
// ratio of processed requests is close enough to the ratio of assigned capacities.
|
||||||
|
// Running multiple rounds with different settings ensures that changing capacity
|
||||||
|
// while connected and going back and forth between free and priority mode with
|
||||||
|
// the supplied API calls is also thoroughly tested.
|
||||||
|
func testCapacityAPI(t *testing.T, clientCount int) {
|
||||||
|
if testServerDataDir == "" {
|
||||||
|
// Skip test if no data dir specified
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool {
|
||||||
|
if len(servers) != 1 {
|
||||||
|
t.Fatalf("Invalid number of servers: %d", len(servers))
|
||||||
|
}
|
||||||
|
server := servers[0]
|
||||||
|
|
||||||
|
clientRpcClients := make([]*rpc.Client, len(clients))
|
||||||
|
|
||||||
|
serverRpcClient, err := server.Client()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to obtain rpc client: %v", err)
|
||||||
|
}
|
||||||
|
headNum, headHash := getHead(ctx, t, serverRpcClient)
|
||||||
|
totalCap := getTotalCap(ctx, t, serverRpcClient)
|
||||||
|
minCap := getMinCap(ctx, t, serverRpcClient)
|
||||||
|
testCap := totalCap * 3 / 4
|
||||||
|
fmt.Printf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash)
|
||||||
|
reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1)))
|
||||||
|
if minCap > reqMinCap {
|
||||||
|
t.Fatalf("Minimum client capacity (%d) bigger than required minimum for this test (%d)", minCap, reqMinCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
freeIdx := rand.Intn(len(clients))
|
||||||
|
freeCap := getFreeCap(ctx, t, serverRpcClient)
|
||||||
|
|
||||||
|
for i, client := range clients {
|
||||||
|
var err error
|
||||||
|
clientRpcClients[i], err = client.Client()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to obtain rpc client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("connecting client", i)
|
||||||
|
if i != freeIdx {
|
||||||
|
setCapacity(ctx, t, serverRpcClient, client.ID(), testCap/uint64(len(clients)))
|
||||||
|
}
|
||||||
|
net.Connect(client.ID(), server.ID())
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Fatalf("Timeout")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
num, hash := getHead(ctx, t, clientRpcClients[i])
|
||||||
|
if num == headNum && hash == headHash {
|
||||||
|
fmt.Println("client", i, "synced")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
stop := make(chan struct{})
|
||||||
|
|
||||||
|
reqCount := make([]uint64, len(clientRpcClients))
|
||||||
|
|
||||||
|
for i, c := range clientRpcClients {
|
||||||
|
wg.Add(1)
|
||||||
|
i, c := i, c
|
||||||
|
go func() {
|
||||||
|
queue := make(chan struct{}, 100)
|
||||||
|
var count uint64
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case queue <- struct{}{}:
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
ok := testRequest(ctx, t, c)
|
||||||
|
wg.Done()
|
||||||
|
<-queue
|
||||||
|
if ok {
|
||||||
|
count++
|
||||||
|
atomic.StoreUint64(&reqCount[i], count)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
case <-stop:
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
processedSince := func(start []uint64) []uint64 {
|
||||||
|
res := make([]uint64, len(reqCount))
|
||||||
|
for i := range reqCount {
|
||||||
|
res[i] = atomic.LoadUint64(&reqCount[i])
|
||||||
|
if start != nil {
|
||||||
|
res[i] -= start[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
weights := make([]float64, len(clients))
|
||||||
|
for c := 0; c < 5; c++ {
|
||||||
|
setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), freeCap)
|
||||||
|
freeIdx = rand.Intn(len(clients))
|
||||||
|
var sum float64
|
||||||
|
for i := range clients {
|
||||||
|
if i == freeIdx {
|
||||||
|
weights[i] = 0
|
||||||
|
} else {
|
||||||
|
weights[i] = rand.Float64()*(1-minRelCap) + minRelCap
|
||||||
|
}
|
||||||
|
sum += weights[i]
|
||||||
|
}
|
||||||
|
for i, client := range clients {
|
||||||
|
weights[i] *= float64(testCap-freeCap-100) / sum
|
||||||
|
capacity := uint64(weights[i])
|
||||||
|
if i != freeIdx && capacity < getCapacity(ctx, t, serverRpcClient, client.ID()) {
|
||||||
|
setCapacity(ctx, t, serverRpcClient, client.ID(), capacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), 0)
|
||||||
|
for i, client := range clients {
|
||||||
|
capacity := uint64(weights[i])
|
||||||
|
if i != freeIdx && capacity > getCapacity(ctx, t, serverRpcClient, client.ID()) {
|
||||||
|
setCapacity(ctx, t, serverRpcClient, client.ID(), capacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
weights[freeIdx] = float64(freeCap)
|
||||||
|
for i := range clients {
|
||||||
|
weights[i] /= float64(testCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(flowcontrol.DecParamDelay)
|
||||||
|
fmt.Println("Starting measurement")
|
||||||
|
fmt.Printf("Relative weights:")
|
||||||
|
for i := range clients {
|
||||||
|
fmt.Printf(" %f", weights[i])
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
start := processedSince(nil)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Fatalf("Timeout")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCap = getTotalCap(ctx, t, serverRpcClient)
|
||||||
|
if totalCap < testCap {
|
||||||
|
fmt.Println("Total capacity underrun")
|
||||||
|
close(stop)
|
||||||
|
wg.Wait()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
processed := processedSince(start)
|
||||||
|
var avg uint64
|
||||||
|
fmt.Printf("Processed")
|
||||||
|
for i, p := range processed {
|
||||||
|
fmt.Printf(" %d", p)
|
||||||
|
processed[i] = uint64(float64(p) / weights[i])
|
||||||
|
avg += processed[i]
|
||||||
|
}
|
||||||
|
avg /= uint64(len(processed))
|
||||||
|
|
||||||
|
if avg >= 10000 {
|
||||||
|
var maxDev float64
|
||||||
|
for _, p := range processed {
|
||||||
|
dev := float64(int64(p-avg)) / float64(avg)
|
||||||
|
fmt.Printf(" %7.4f", dev)
|
||||||
|
if dev < 0 {
|
||||||
|
dev = -dev
|
||||||
|
}
|
||||||
|
if dev > maxDev {
|
||||||
|
maxDev = dev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf(" max deviation: %f totalCap: %d\n", maxDev, totalCap)
|
||||||
|
if maxDev <= testTolerance {
|
||||||
|
fmt.Println("success")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(stop)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for i, count := range reqCount {
|
||||||
|
fmt.Println("client", i, "processed", count)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}) {
|
||||||
|
fmt.Println("restarting test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHead(ctx context.Context, t *testing.T, client *rpc.Client) (uint64, common.Hash) {
|
||||||
|
res := make(map[string]interface{})
|
||||||
|
if err := client.CallContext(ctx, &res, "eth_getBlockByNumber", "latest", false); err != nil {
|
||||||
|
t.Fatalf("Failed to obtain head block: %v", err)
|
||||||
|
}
|
||||||
|
numStr, ok := res["number"].(string)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("RPC block number field invalid")
|
||||||
|
}
|
||||||
|
num, err := hexutil.DecodeUint64(numStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode RPC block number: %v", err)
|
||||||
|
}
|
||||||
|
hashStr, ok := res["hash"].(string)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("RPC block number field invalid")
|
||||||
|
}
|
||||||
|
hash := common.HexToHash(hashStr)
|
||||||
|
return num, hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool {
|
||||||
|
//res := make(map[string]interface{})
|
||||||
|
var res string
|
||||||
|
var addr common.Address
|
||||||
|
rand.Read(addr[:])
|
||||||
|
c, _ := context.WithTimeout(ctx, time.Second*12)
|
||||||
|
// if err := client.CallContext(ctx, &res, "eth_getProof", addr, nil, "latest"); err != nil {
|
||||||
|
err := client.CallContext(c, &res, "eth_getBalance", addr, "latest")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("request error:", err)
|
||||||
|
}
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID, cap uint64) {
|
||||||
|
if err := server.CallContext(ctx, nil, "les_setClientCapacity", clientID, cap); err != nil {
|
||||||
|
t.Fatalf("Failed to set client capacity: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) uint64 {
|
||||||
|
var s string
|
||||||
|
if err := server.CallContext(ctx, &s, "les_getClientCapacity", clientID); err != nil {
|
||||||
|
t.Fatalf("Failed to get client capacity: %v", err)
|
||||||
|
}
|
||||||
|
cap, err := hexutil.DecodeUint64(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode client capacity: %v", err)
|
||||||
|
}
|
||||||
|
return cap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTotalCap(ctx context.Context, t *testing.T, server *rpc.Client) uint64 {
|
||||||
|
var s string
|
||||||
|
if err := server.CallContext(ctx, &s, "les_totalCapacity"); err != nil {
|
||||||
|
t.Fatalf("Failed to query total capacity: %v", err)
|
||||||
|
}
|
||||||
|
total, err := hexutil.DecodeUint64(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode total capacity: %v", err)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMinCap(ctx context.Context, t *testing.T, server *rpc.Client) uint64 {
|
||||||
|
var s string
|
||||||
|
if err := server.CallContext(ctx, &s, "les_minimumCapacity"); err != nil {
|
||||||
|
t.Fatalf("Failed to query minimum capacity: %v", err)
|
||||||
|
}
|
||||||
|
min, err := hexutil.DecodeUint64(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode minimum capacity: %v", err)
|
||||||
|
}
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFreeCap(ctx context.Context, t *testing.T, server *rpc.Client) uint64 {
|
||||||
|
var s string
|
||||||
|
if err := server.CallContext(ctx, &s, "les_freeClientCapacity"); err != nil {
|
||||||
|
t.Fatalf("Failed to query free client capacity: %v", err)
|
||||||
|
}
|
||||||
|
free, err := hexutil.DecodeUint64(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode free client capacity: %v", err)
|
||||||
|
}
|
||||||
|
return free
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
// register the Delivery service which will run as a devp2p
|
||||||
|
// protocol when using the exec adapter
|
||||||
|
adapters.RegisterServices(services)
|
||||||
|
|
||||||
|
log.PrintOrigins(true)
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapter = flag.String("adapter", "exec", "type of simulation: sim|socket|exec|docker")
|
||||||
|
loglevel = flag.Int("loglevel", 0, "verbosity of logs")
|
||||||
|
nodes = flag.Int("nodes", 0, "number of nodes")
|
||||||
|
)
|
||||||
|
|
||||||
|
var services = adapters.Services{
|
||||||
|
"lesclient": newLesClientService,
|
||||||
|
"lesserver": newLesServerService,
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetwork() (*simulations.Network, func(), error) {
|
||||||
|
adapter, adapterTeardown, err := NewAdapter(*adapter, services)
|
||||||
|
if err != nil {
|
||||||
|
return nil, adapterTeardown, err
|
||||||
|
}
|
||||||
|
defaultService := "streamer"
|
||||||
|
net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
|
||||||
|
ID: "0",
|
||||||
|
DefaultService: defaultService,
|
||||||
|
})
|
||||||
|
teardown := func() {
|
||||||
|
adapterTeardown()
|
||||||
|
net.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
return net, teardown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(adapterType string, services adapters.Services) (adapter adapters.NodeAdapter, teardown func(), err error) {
|
||||||
|
teardown = func() {}
|
||||||
|
switch adapterType {
|
||||||
|
case "sim":
|
||||||
|
adapter = adapters.NewSimAdapter(services)
|
||||||
|
// case "socket":
|
||||||
|
// adapter = adapters.NewSocketAdapter(services)
|
||||||
|
case "exec":
|
||||||
|
baseDir, err0 := ioutil.TempDir("", "les-test")
|
||||||
|
if err0 != nil {
|
||||||
|
return nil, teardown, err0
|
||||||
|
}
|
||||||
|
teardown = func() { os.RemoveAll(baseDir) }
|
||||||
|
adapter = adapters.NewExecAdapter(baseDir)
|
||||||
|
/*case "docker":
|
||||||
|
adapter, err = adapters.NewDockerAdapter()
|
||||||
|
if err != nil {
|
||||||
|
return nil, teardown, err
|
||||||
|
}*/
|
||||||
|
default:
|
||||||
|
return nil, teardown, errors.New("adapter needs to be one of sim, socket, exec, docker")
|
||||||
|
}
|
||||||
|
return adapter, teardown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []string, test func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool) bool {
|
||||||
|
net, teardown, err := NewNetwork()
|
||||||
|
defer teardown()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create network: %v", err)
|
||||||
|
}
|
||||||
|
timeout := 1800 * time.Second
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
servers := make([]*simulations.Node, serverCount)
|
||||||
|
clients := make([]*simulations.Node, clientCount)
|
||||||
|
|
||||||
|
for i := range clients {
|
||||||
|
clientconf := adapters.RandomNodeConfig()
|
||||||
|
clientconf.Services = []string{"lesclient"}
|
||||||
|
if len(clientDir) == clientCount {
|
||||||
|
clientconf.DataDir = clientDir[i]
|
||||||
|
}
|
||||||
|
client, err := net.NewNodeWithConfig(clientconf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
clients[i] = client
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range servers {
|
||||||
|
serverconf := adapters.RandomNodeConfig()
|
||||||
|
serverconf.Services = []string{"lesserver"}
|
||||||
|
if len(serverDir) == serverCount {
|
||||||
|
serverconf.DataDir = serverDir[i]
|
||||||
|
}
|
||||||
|
server, err := net.NewNodeWithConfig(serverconf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create server: %v", err)
|
||||||
|
}
|
||||||
|
servers[i] = server
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
if err := net.Start(client.ID()); err != nil {
|
||||||
|
t.Fatalf("Failed to start client node: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, server := range servers {
|
||||||
|
if err := net.Start(server.ID()); err != nil {
|
||||||
|
t.Fatalf("Failed to start server node: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return test(ctx, net, servers, clients)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLesClientService(ctx *adapters.ServiceContext) (node.Service, error) {
|
||||||
|
config := eth.DefaultConfig
|
||||||
|
config.SyncMode = downloader.LightSync
|
||||||
|
config.Ethash.PowMode = ethash.ModeFake
|
||||||
|
return New(ctx.NodeContext, &config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) {
|
||||||
|
config := eth.DefaultConfig
|
||||||
|
config.SyncMode = downloader.FullSync
|
||||||
|
config.LightServ = testServerCapacity
|
||||||
|
config.LightPeers = testMaxClients
|
||||||
|
ethereum, err := eth.New(ctx.NodeContext, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := NewLesServer(ethereum, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ethereum.AddLesServer(server)
|
||||||
|
return ethereum, nil
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
|
|
@ -100,7 +101,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
eventMux: ctx.EventMux,
|
eventMux: ctx.EventMux,
|
||||||
peers: peers,
|
peers: peers,
|
||||||
reqDist: newRequestDistributor(peers, quitSync),
|
reqDist: newRequestDistributor(peers, quitSync, &mclock.System{}),
|
||||||
accountManager: ctx.AccountManager,
|
accountManager: ctx.AccountManager,
|
||||||
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
|
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
|
||||||
shutdownChan: make(chan bool),
|
shutdownChan: make(chan bool),
|
||||||
|
|
|
||||||
353
les/benchmark.go
Normal file
353
les/benchmark.go
Normal file
|
|
@ -0,0 +1,353 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestBenchmark is an interface for different randomized request generators
|
||||||
|
type requestBenchmark interface {
|
||||||
|
// init initializes the generator for generating the given number of randomized requests
|
||||||
|
init(pm *ProtocolManager, count int) error
|
||||||
|
// request initiates sending a single request to the given peer
|
||||||
|
request(peer *peer, index int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkBlockHeaders implements requestBenchmark
|
||||||
|
type benchmarkBlockHeaders struct {
|
||||||
|
amount, skip int
|
||||||
|
reverse, byHash bool
|
||||||
|
offset, randMax int64
|
||||||
|
hashes []common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkBlockHeaders) init(pm *ProtocolManager, count int) error {
|
||||||
|
d := int64(b.amount-1) * int64(b.skip+1)
|
||||||
|
b.offset = 0
|
||||||
|
b.randMax = pm.blockchain.CurrentHeader().Number.Int64() + 1 - d
|
||||||
|
if b.randMax < 0 {
|
||||||
|
return fmt.Errorf("chain is too short")
|
||||||
|
}
|
||||||
|
if b.reverse {
|
||||||
|
b.offset = d
|
||||||
|
}
|
||||||
|
if b.byHash {
|
||||||
|
b.hashes = make([]common.Hash, count)
|
||||||
|
for i := range b.hashes {
|
||||||
|
b.hashes[i] = rawdb.ReadCanonicalHash(pm.chainDb, uint64(b.offset+rand.Int63n(b.randMax)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkBlockHeaders) request(peer *peer, index int) error {
|
||||||
|
if b.byHash {
|
||||||
|
return peer.RequestHeadersByHash(0, 0, b.hashes[index], b.amount, b.skip, b.reverse)
|
||||||
|
} else {
|
||||||
|
return peer.RequestHeadersByNumber(0, 0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkBodiesOrReceipts implements requestBenchmark
|
||||||
|
type benchmarkBodiesOrReceipts struct {
|
||||||
|
receipts bool
|
||||||
|
hashes []common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkBodiesOrReceipts) init(pm *ProtocolManager, count int) error {
|
||||||
|
randMax := pm.blockchain.CurrentHeader().Number.Int64() + 1
|
||||||
|
b.hashes = make([]common.Hash, count)
|
||||||
|
for i := range b.hashes {
|
||||||
|
b.hashes[i] = rawdb.ReadCanonicalHash(pm.chainDb, uint64(rand.Int63n(randMax)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkBodiesOrReceipts) request(peer *peer, index int) error {
|
||||||
|
if b.receipts {
|
||||||
|
return peer.RequestReceipts(0, 0, []common.Hash{b.hashes[index]})
|
||||||
|
} else {
|
||||||
|
return peer.RequestBodies(0, 0, []common.Hash{b.hashes[index]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkProofsOrCode implements requestBenchmark
|
||||||
|
type benchmarkProofsOrCode struct {
|
||||||
|
code bool
|
||||||
|
headHash common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkProofsOrCode) init(pm *ProtocolManager, count int) error {
|
||||||
|
b.headHash = pm.blockchain.CurrentHeader().Hash()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkProofsOrCode) request(peer *peer, index int) error {
|
||||||
|
key := make([]byte, 32)
|
||||||
|
rand.Read(key)
|
||||||
|
if b.code {
|
||||||
|
return peer.RequestCode(0, 0, []CodeReq{{BHash: b.headHash, AccKey: key}})
|
||||||
|
} else {
|
||||||
|
return peer.RequestProofs(0, 0, []ProofReq{{BHash: b.headHash, Key: key}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkHelperTrie implements requestBenchmark
|
||||||
|
type benchmarkHelperTrie struct {
|
||||||
|
bloom bool
|
||||||
|
reqCount int
|
||||||
|
sectionCount, headNum uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkHelperTrie) init(pm *ProtocolManager, count int) error {
|
||||||
|
if b.bloom {
|
||||||
|
b.sectionCount, b.headNum, _ = pm.server.bloomTrieIndexer.Sections()
|
||||||
|
} else {
|
||||||
|
b.sectionCount, _, _ = pm.server.chtIndexer.Sections()
|
||||||
|
b.sectionCount /= (params.CHTFrequencyClient / params.CHTFrequencyServer)
|
||||||
|
b.headNum = b.sectionCount*params.CHTFrequencyClient - 1
|
||||||
|
}
|
||||||
|
if b.sectionCount == 0 {
|
||||||
|
return fmt.Errorf("no processed sections available")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkHelperTrie) request(peer *peer, index int) error {
|
||||||
|
reqs := make([]HelperTrieReq, b.reqCount)
|
||||||
|
|
||||||
|
if b.bloom {
|
||||||
|
bitIdx := uint16(rand.Intn(2048))
|
||||||
|
for i := range reqs {
|
||||||
|
key := make([]byte, 10)
|
||||||
|
binary.BigEndian.PutUint16(key[:2], bitIdx)
|
||||||
|
binary.BigEndian.PutUint64(key[2:], uint64(rand.Int63n(int64(b.sectionCount))))
|
||||||
|
reqs[i] = HelperTrieReq{Type: htBloomBits, TrieIdx: b.sectionCount - 1, Key: key}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range reqs {
|
||||||
|
key := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum))))
|
||||||
|
reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: auxHeader}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer.RequestHelperTrieProofs(0, 0, reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkTxSend implements requestBenchmark
|
||||||
|
type benchmarkTxSend struct {
|
||||||
|
txs types.Transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkTxSend) init(pm *ProtocolManager, count int) error {
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
signer := types.NewEIP155Signer(big.NewInt(18))
|
||||||
|
b.txs = make(types.Transactions, count)
|
||||||
|
|
||||||
|
for i := range b.txs {
|
||||||
|
data := make([]byte, txSizeCostLimit)
|
||||||
|
rand.Read(data)
|
||||||
|
tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b.txs[i] = tx
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkTxSend) request(peer *peer, index int) error {
|
||||||
|
enc, _ := rlp.EncodeToBytes(types.Transactions{b.txs[index]})
|
||||||
|
return peer.SendTxs(0, 0, enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkTxStatus implements requestBenchmark
|
||||||
|
type benchmarkTxStatus struct{}
|
||||||
|
|
||||||
|
func (b *benchmarkTxStatus) init(pm *ProtocolManager, count int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkTxStatus) request(peer *peer, index int) error {
|
||||||
|
var hash common.Hash
|
||||||
|
rand.Read(hash[:])
|
||||||
|
return peer.RequestTxStatus(0, 0, []common.Hash{hash})
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchmarkSetup stores measurement data for a single benchmark type
|
||||||
|
type benchmarkSetup struct {
|
||||||
|
req requestBenchmark
|
||||||
|
totalCount int
|
||||||
|
totalTime, avgTime time.Duration
|
||||||
|
maxInSize, maxOutSize uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// runBenchmark runs a benchmark cycle for all benchmark types in the specified
|
||||||
|
// number of passes
|
||||||
|
func (pm *ProtocolManager) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup {
|
||||||
|
setup := make([]*benchmarkSetup, len(benchmarks))
|
||||||
|
for i, b := range benchmarks {
|
||||||
|
setup[i] = &benchmarkSetup{req: b}
|
||||||
|
}
|
||||||
|
for i := 0; i < passCount; i++ {
|
||||||
|
log.Info("Running benchmark", "pass", i+1, "total", passCount)
|
||||||
|
todo := make([]*benchmarkSetup, len(benchmarks))
|
||||||
|
copy(todo, setup)
|
||||||
|
for len(todo) > 0 {
|
||||||
|
// select a random element
|
||||||
|
index := rand.Intn(len(todo))
|
||||||
|
next := todo[index]
|
||||||
|
todo[index] = todo[len(todo)-1]
|
||||||
|
todo = todo[:len(todo)-1]
|
||||||
|
|
||||||
|
if next.err == nil {
|
||||||
|
// calculate request count
|
||||||
|
count := 50
|
||||||
|
if next.totalTime > 0 {
|
||||||
|
count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime))
|
||||||
|
}
|
||||||
|
if err := pm.measure(next, count); err != nil {
|
||||||
|
next.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("Benchmark completed")
|
||||||
|
|
||||||
|
for _, s := range setup {
|
||||||
|
if s.err == nil {
|
||||||
|
s.avgTime = s.totalTime / time.Duration(s.totalCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setup
|
||||||
|
}
|
||||||
|
|
||||||
|
// meteredPipe implements p2p.MsgReadWriter and remembers the largest single
|
||||||
|
// message size sent through the pipe
|
||||||
|
type meteredPipe struct {
|
||||||
|
rw p2p.MsgReadWriter
|
||||||
|
maxSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *meteredPipe) ReadMsg() (p2p.Msg, error) {
|
||||||
|
return m.rw.ReadMsg()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *meteredPipe) WriteMsg(msg p2p.Msg) error {
|
||||||
|
if msg.Size > m.maxSize {
|
||||||
|
m.maxSize = msg.Size
|
||||||
|
}
|
||||||
|
return m.rw.WriteMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// measure runs a benchmark for a single type in a single pass, with the given
|
||||||
|
// number of requests
|
||||||
|
func (pm *ProtocolManager) measure(setup *benchmarkSetup, count int) error {
|
||||||
|
clientPipe, serverPipe := p2p.MsgPipe()
|
||||||
|
clientMeteredPipe := &meteredPipe{rw: clientPipe}
|
||||||
|
serverMeteredPipe := &meteredPipe{rw: serverPipe}
|
||||||
|
var id enode.ID
|
||||||
|
rand.Read(id[:])
|
||||||
|
clientPeer := pm.newPeer(lpv2, NetworkId, p2p.NewPeer(id, "client", nil), clientMeteredPipe)
|
||||||
|
serverPeer := pm.newPeer(lpv2, NetworkId, p2p.NewPeer(id, "server", nil), serverMeteredPipe)
|
||||||
|
serverPeer.sendQueue = newExecQueue(count)
|
||||||
|
serverPeer.announceType = announceTypeNone
|
||||||
|
serverPeer.fcCosts = make(requestCostTable)
|
||||||
|
c := &requestCosts{}
|
||||||
|
for code := range requests {
|
||||||
|
serverPeer.fcCosts[code] = c
|
||||||
|
}
|
||||||
|
serverPeer.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1}
|
||||||
|
serverPeer.fcClient = flowcontrol.NewClientNode(pm.server.fcManager, serverPeer.fcParams)
|
||||||
|
defer serverPeer.fcClient.Disconnect()
|
||||||
|
|
||||||
|
if err := setup.req.init(pm, count); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
errCh := make(chan error, 10)
|
||||||
|
start := mclock.Now()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if err := setup.req.request(clientPeer, i); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if err := pm.handleMsg(serverPeer); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
msg, err := clientPipe.ReadMsg()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var i interface{}
|
||||||
|
msg.Decode(&i)
|
||||||
|
}
|
||||||
|
// at this point we can be sure that the other two
|
||||||
|
// goroutines finished successfully too
|
||||||
|
close(errCh)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-pm.quitSync:
|
||||||
|
clientPipe.Close()
|
||||||
|
serverPipe.Close()
|
||||||
|
return fmt.Errorf("Benchmark cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
setup.totalTime += time.Duration(mclock.Now() - start)
|
||||||
|
setup.totalCount += count
|
||||||
|
setup.maxInSize = clientMeteredPipe.maxSize
|
||||||
|
setup.maxOutSize = serverMeteredPipe.maxSize
|
||||||
|
clientPipe.Close()
|
||||||
|
serverPipe.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
388
les/costtracker.go
Normal file
388
les/costtracker.go
Normal file
|
|
@ -0,0 +1,388 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more detailct.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const makeCostStats = false // make request cost statistics during operation
|
||||||
|
|
||||||
|
var (
|
||||||
|
// average request cost estimates based on serving time
|
||||||
|
reqAvgTimeCost = requestCostTable{
|
||||||
|
GetBlockHeadersMsg: {150000, 30000},
|
||||||
|
GetBlockBodiesMsg: {0, 700000},
|
||||||
|
GetReceiptsMsg: {0, 1000000},
|
||||||
|
GetCodeMsg: {0, 450000},
|
||||||
|
GetProofsV1Msg: {0, 600000},
|
||||||
|
GetProofsV2Msg: {0, 600000},
|
||||||
|
GetHeaderProofsMsg: {0, 1000000},
|
||||||
|
GetHelperTrieProofsMsg: {0, 1000000},
|
||||||
|
SendTxMsg: {0, 450000},
|
||||||
|
SendTxV2Msg: {0, 450000},
|
||||||
|
GetTxStatusMsg: {0, 250000},
|
||||||
|
}
|
||||||
|
// maximum incoming message size estimates
|
||||||
|
reqMaxInSize = requestCostTable{
|
||||||
|
GetBlockHeadersMsg: {40, 0},
|
||||||
|
GetBlockBodiesMsg: {0, 40},
|
||||||
|
GetReceiptsMsg: {0, 40},
|
||||||
|
GetCodeMsg: {0, 80},
|
||||||
|
GetProofsV1Msg: {0, 80},
|
||||||
|
GetProofsV2Msg: {0, 80},
|
||||||
|
GetHeaderProofsMsg: {0, 20},
|
||||||
|
GetHelperTrieProofsMsg: {0, 20},
|
||||||
|
SendTxMsg: {0, 66000},
|
||||||
|
SendTxV2Msg: {0, 66000},
|
||||||
|
GetTxStatusMsg: {0, 50},
|
||||||
|
}
|
||||||
|
// maximum outgoing message size estimates
|
||||||
|
reqMaxOutSize = requestCostTable{
|
||||||
|
GetBlockHeadersMsg: {0, 556},
|
||||||
|
GetBlockBodiesMsg: {0, 100000},
|
||||||
|
GetReceiptsMsg: {0, 200000},
|
||||||
|
GetCodeMsg: {0, 50000},
|
||||||
|
GetProofsV1Msg: {0, 4000},
|
||||||
|
GetProofsV2Msg: {0, 4000},
|
||||||
|
GetHeaderProofsMsg: {0, 4000},
|
||||||
|
GetHelperTrieProofsMsg: {0, 4000},
|
||||||
|
SendTxMsg: {0, 0},
|
||||||
|
SendTxV2Msg: {0, 100},
|
||||||
|
GetTxStatusMsg: {0, 100},
|
||||||
|
}
|
||||||
|
minBufLimit = uint64(50000000 * maxCostFactor) // minimum buffer limit allowed for a client
|
||||||
|
minCapacity = (minBufLimit-1)/bufLimitRatio + 1 // minimum capacity allowed for a client
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxCostFactor = 2 // ratio of maximum and average cost estimates
|
||||||
|
gfInitWeight = time.Second * 10
|
||||||
|
gfMaxWeight = time.Hour
|
||||||
|
gfUsageThreshold = 0.5
|
||||||
|
gfUsageTC = time.Second
|
||||||
|
gfDbKey = "_globalCostFactor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// costTracker is responsible for calculating costs and cost estimates on the
|
||||||
|
// server side. It continuously updates the global cost factor which is defined
|
||||||
|
// as the number of cost units per nanosecond of serving time in a single thread.
|
||||||
|
// It is based on statistics collected during serving requests in high-load periods
|
||||||
|
// and practically acts as a one-dimension request price scaling factor over the
|
||||||
|
// pre-defined cost estimate table. Instead of scaling the cost values, the real
|
||||||
|
// value of cost units is changed by applying the factor to the serving times. This
|
||||||
|
// is more convenient because the changes in the cost factor can be applied immediately
|
||||||
|
// without always notifying the clients about the changed cost tables.
|
||||||
|
type costTracker struct {
|
||||||
|
db ethdb.Database
|
||||||
|
stopCh chan chan struct{}
|
||||||
|
|
||||||
|
inSizeFactor, outSizeFactor float64
|
||||||
|
gf, utilTarget float64
|
||||||
|
|
||||||
|
gfUpdateCh chan gfUpdate
|
||||||
|
gfLock sync.RWMutex
|
||||||
|
totalRechargeCh chan uint64
|
||||||
|
|
||||||
|
stats map[uint64][]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCostTracker creates a cost tracker and loads the cost factor statistics from the database
|
||||||
|
func newCostTracker(db ethdb.Database, config *eth.Config) *costTracker {
|
||||||
|
utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
|
||||||
|
ct := &costTracker{
|
||||||
|
db: db,
|
||||||
|
stopCh: make(chan chan struct{}),
|
||||||
|
utilTarget: utilTarget,
|
||||||
|
}
|
||||||
|
if config.LightBandwidthIn > 0 {
|
||||||
|
ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn)
|
||||||
|
}
|
||||||
|
if config.LightBandwidthOut > 0 {
|
||||||
|
ct.outSizeFactor = utilTarget / float64(config.LightBandwidthOut)
|
||||||
|
}
|
||||||
|
if makeCostStats {
|
||||||
|
ct.stats = make(map[uint64][]uint64)
|
||||||
|
for code := range reqAvgTimeCost {
|
||||||
|
ct.stats[code] = make([]uint64, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ct.gfLoop()
|
||||||
|
return ct
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops the cost tracker and saves the cost factor statistics to the database
|
||||||
|
func (ct *costTracker) stop() {
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
ct.stopCh <- stopCh
|
||||||
|
<-stopCh
|
||||||
|
if makeCostStats {
|
||||||
|
ct.printStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCostList returns upper cost estimates based on the hardcoded cost estimate
|
||||||
|
// tables and the optionally specified incoming/outgoing bandwidth limits
|
||||||
|
func (ct *costTracker) makeCostList() RequestCostList {
|
||||||
|
maxCost := func(avgTime, inSize, outSize uint64) uint64 {
|
||||||
|
globalFactor := ct.globalFactor()
|
||||||
|
|
||||||
|
cost := avgTime * maxCostFactor
|
||||||
|
inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor * maxCostFactor)
|
||||||
|
if inSizeCost > cost {
|
||||||
|
cost = inSizeCost
|
||||||
|
}
|
||||||
|
outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor * maxCostFactor)
|
||||||
|
if outSizeCost > cost {
|
||||||
|
cost = outSizeCost
|
||||||
|
}
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
var list RequestCostList
|
||||||
|
for code, data := range reqAvgTimeCost {
|
||||||
|
list = append(list, requestCostListItem{
|
||||||
|
MsgCode: code,
|
||||||
|
BaseCost: maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost),
|
||||||
|
ReqCost: maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
type gfUpdate struct {
|
||||||
|
avgTime, servingTime float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// gfLoop starts an event loop which updates the global cost factor which is
|
||||||
|
// calculated as a weighted average of the average estimate / serving time ratio.
|
||||||
|
// The applied weight equals the serving time if gfUsage is over a threshold,
|
||||||
|
// zero otherwise. gfUsage is the recent average serving time per time unit in
|
||||||
|
// an exponential moving window. This ensures that statistics are collected only
|
||||||
|
// under high-load circumstances where the measured serving times are relevant.
|
||||||
|
// The total recharge parameter of the flow control system which controls the
|
||||||
|
// total allowed serving time per second but nominated in cost units, should
|
||||||
|
// also be scaled with the cost factor and is also updated by this loop.
|
||||||
|
func (ct *costTracker) gfLoop() {
|
||||||
|
var gfUsage, gfSum, gfWeight float64
|
||||||
|
lastUpdate := mclock.Now()
|
||||||
|
expUpdate := lastUpdate
|
||||||
|
|
||||||
|
data, _ := ct.db.Get([]byte(gfDbKey))
|
||||||
|
if len(data) == 16 {
|
||||||
|
gfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
|
||||||
|
gfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
|
||||||
|
}
|
||||||
|
if gfWeight < float64(gfInitWeight) {
|
||||||
|
gfSum = float64(gfInitWeight)
|
||||||
|
gfWeight = float64(gfInitWeight)
|
||||||
|
}
|
||||||
|
gf := gfSum / gfWeight
|
||||||
|
ct.gf = gf
|
||||||
|
ct.gfUpdateCh = make(chan gfUpdate, 100)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case r := <-ct.gfUpdateCh:
|
||||||
|
now := mclock.Now()
|
||||||
|
max := r.servingTime * gf
|
||||||
|
if r.avgTime > max {
|
||||||
|
max = r.avgTime
|
||||||
|
}
|
||||||
|
dt := float64(now - expUpdate)
|
||||||
|
expUpdate = now
|
||||||
|
gfUsage = gfUsage*math.Exp(-dt/float64(gfUsageTC)) + max*1000000/float64(gfUsageTC)
|
||||||
|
|
||||||
|
if gfUsage >= gfUsageThreshold*ct.utilTarget*gf {
|
||||||
|
gfSum += r.avgTime
|
||||||
|
gfWeight += r.servingTime
|
||||||
|
if time.Duration(now-lastUpdate) > time.Second {
|
||||||
|
gf = gfSum / gfWeight
|
||||||
|
if gfWeight >= float64(gfMaxWeight) {
|
||||||
|
gfSum = gf * float64(gfMaxWeight)
|
||||||
|
gfWeight = float64(gfMaxWeight)
|
||||||
|
}
|
||||||
|
lastUpdate = now
|
||||||
|
ct.gfLock.Lock()
|
||||||
|
ct.gf = gf
|
||||||
|
ch := ct.totalRechargeCh
|
||||||
|
ct.gfLock.Unlock()
|
||||||
|
if ch != nil {
|
||||||
|
select {
|
||||||
|
case ct.totalRechargeCh <- uint64(ct.utilTarget * gf):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debug("global cost factor updated", "gf", gf, "weight", time.Duration(gfWeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case stopCh := <-ct.stopCh:
|
||||||
|
var data [16]byte
|
||||||
|
binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gfSum))
|
||||||
|
binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gfWeight))
|
||||||
|
ct.db.Put([]byte(gfDbKey), data[:])
|
||||||
|
log.Debug("global cost factor saved", "sum", time.Duration(gfSum), "weight", time.Duration(gfWeight))
|
||||||
|
close(stopCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalFactor returns the current value of the global cost factor
|
||||||
|
func (ct *costTracker) globalFactor() float64 {
|
||||||
|
ct.gfLock.RLock()
|
||||||
|
defer ct.gfLock.RUnlock()
|
||||||
|
|
||||||
|
return ct.gf
|
||||||
|
}
|
||||||
|
|
||||||
|
// totalRecharge returns the current total recharge parameter which is used by
|
||||||
|
// flowcontrol.ClientManager and is scaled by the global cost factor
|
||||||
|
func (ct *costTracker) totalRecharge() uint64 {
|
||||||
|
ct.gfLock.RLock()
|
||||||
|
defer ct.gfLock.RUnlock()
|
||||||
|
|
||||||
|
return uint64(ct.gf * ct.utilTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribeTotalRecharge returns all future updates to the total recharge value
|
||||||
|
// through a channel and also returns the current value
|
||||||
|
func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 {
|
||||||
|
ct.gfLock.Lock()
|
||||||
|
defer ct.gfLock.Unlock()
|
||||||
|
|
||||||
|
ct.totalRechargeCh = ch
|
||||||
|
return uint64(ct.gf * ct.utilTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateStats updates the global cost factor and (if enabled) the real cost vs.
|
||||||
|
// average estimate statistics
|
||||||
|
func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) {
|
||||||
|
avg := reqAvgTimeCost[code]
|
||||||
|
avgTime := avg.baseCost + amount*avg.reqCost
|
||||||
|
select {
|
||||||
|
case ct.gfUpdateCh <- gfUpdate{float64(avgTime), float64(servingTime)}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if makeCostStats {
|
||||||
|
realCost <<= 4
|
||||||
|
l := 0
|
||||||
|
for l < 9 && realCost > avgTime {
|
||||||
|
l++
|
||||||
|
realCost >>= 1
|
||||||
|
}
|
||||||
|
atomic.AddUint64(&ct.stats[code][l], 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// realCost calculates the final cost of a request based on actual serving time,
|
||||||
|
// incoming and outgoing message size
|
||||||
|
//
|
||||||
|
// Note: message size is only taken into account if bandwidth limitation is applied
|
||||||
|
// and the cost based on either message size is greater than the cost based on
|
||||||
|
// serving time. A maximum of the three costs is applied instead of their sum
|
||||||
|
// because the three limited resources (serving thread time and i/o bandwidth) can
|
||||||
|
// also be maxed out simultaneously.
|
||||||
|
func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 {
|
||||||
|
cost := float64(servingTime)
|
||||||
|
inSizeCost := float64(inSize) * ct.inSizeFactor
|
||||||
|
if inSizeCost > cost {
|
||||||
|
cost = inSizeCost
|
||||||
|
}
|
||||||
|
outSizeCost := float64(outSize) * ct.outSizeFactor
|
||||||
|
if outSizeCost > cost {
|
||||||
|
cost = outSizeCost
|
||||||
|
}
|
||||||
|
return uint64(cost * ct.globalFactor())
|
||||||
|
}
|
||||||
|
|
||||||
|
// printStats prints the distribution of real request cost relative to the average estimates
|
||||||
|
func (ct *costTracker) printStats() {
|
||||||
|
if ct.stats == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for code, arr := range ct.stats {
|
||||||
|
log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// requestCostTable assigns a cost estimate function to each request type
|
||||||
|
// which is a linear function of the requested amount
|
||||||
|
// (cost = baseCost + reqCost * amount)
|
||||||
|
requestCostTable map[uint64]*requestCosts
|
||||||
|
requestCosts struct {
|
||||||
|
baseCost, reqCost uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestCostList is a list representation of request costs which is used for
|
||||||
|
// database storage and communication through the network
|
||||||
|
RequestCostList []requestCostListItem
|
||||||
|
requestCostListItem struct {
|
||||||
|
MsgCode, BaseCost, ReqCost uint64
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// getCost calculates the estimated cost for a given request type and amount
|
||||||
|
func (table requestCostTable) getCost(code, amount uint64) uint64 {
|
||||||
|
costs := table[code]
|
||||||
|
return costs.baseCost + amount*costs.reqCost
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode converts a cost list to a cost table
|
||||||
|
func (list RequestCostList) decode() requestCostTable {
|
||||||
|
table := make(requestCostTable)
|
||||||
|
for _, e := range list {
|
||||||
|
table[e.MsgCode] = &requestCosts{
|
||||||
|
baseCost: e.BaseCost,
|
||||||
|
reqCost: e.ReqCost,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
|
// testCostList returns a dummy request cost list used by tests
|
||||||
|
func testCostList() RequestCostList {
|
||||||
|
cl := make(RequestCostList, len(reqAvgTimeCost))
|
||||||
|
var max uint64
|
||||||
|
for code := range reqAvgTimeCost {
|
||||||
|
if code > max {
|
||||||
|
max = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for code := uint64(0); code <= max; code++ {
|
||||||
|
if _, ok := reqAvgTimeCost[code]; ok {
|
||||||
|
cl[i].MsgCode = code
|
||||||
|
cl[i].BaseCost = 0
|
||||||
|
cl[i].ReqCost = 0
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cl
|
||||||
|
}
|
||||||
|
|
@ -14,20 +14,21 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// requestDistributor implements a mechanism that distributes requests to
|
// requestDistributor implements a mechanism that distributes requests to
|
||||||
// suitable peers, obeying flow control rules and prioritizing them in creation
|
// suitable peers, obeying flow control rules and prioritizing them in creation
|
||||||
// order (even when a resend is necessary).
|
// order (even when a resend is necessary).
|
||||||
type requestDistributor struct {
|
type requestDistributor struct {
|
||||||
|
clock mclock.Clock
|
||||||
reqQueue *list.List
|
reqQueue *list.List
|
||||||
lastReqOrder uint64
|
lastReqOrder uint64
|
||||||
peers map[distPeer]struct{}
|
peers map[distPeer]struct{}
|
||||||
|
|
@ -67,8 +68,9 @@ type distReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRequestDistributor creates a new request distributor
|
// newRequestDistributor creates a new request distributor
|
||||||
func newRequestDistributor(peers *peerSet, stopChn chan struct{}) *requestDistributor {
|
func newRequestDistributor(peers *peerSet, stopChn chan struct{}, clock mclock.Clock) *requestDistributor {
|
||||||
d := &requestDistributor{
|
d := &requestDistributor{
|
||||||
|
clock: clock,
|
||||||
reqQueue: list.New(),
|
reqQueue: list.New(),
|
||||||
loopChn: make(chan struct{}, 2),
|
loopChn: make(chan struct{}, 2),
|
||||||
stopChn: stopChn,
|
stopChn: stopChn,
|
||||||
|
|
@ -148,7 +150,7 @@ func (d *requestDistributor) loop() {
|
||||||
wait = distMaxWait
|
wait = distMaxWait
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(wait)
|
d.clock.Sleep(wait)
|
||||||
d.loopChn <- struct{}{}
|
d.loopChn <- struct{}{}
|
||||||
}()
|
}()
|
||||||
break loop
|
break loop
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -23,6 +21,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testDistReq struct {
|
type testDistReq struct {
|
||||||
|
|
@ -121,7 +121,7 @@ func testRequestDistributor(t *testing.T, resend bool) {
|
||||||
stop := make(chan struct{})
|
stop := make(chan struct{})
|
||||||
defer close(stop)
|
defer close(stop)
|
||||||
|
|
||||||
dist := newRequestDistributor(nil, stop)
|
dist := newRequestDistributor(nil, stop, &mclock.System{})
|
||||||
var peers [testDistPeerCount]*testDistPeer
|
var peers [testDistPeerCount]*testDistPeer
|
||||||
for i := range peers {
|
for i := range peers {
|
||||||
peers[i] = &testDistPeer{}
|
peers[i] = &testDistPeer{}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -559,7 +558,7 @@ func (f *lightFetcher) newFetcherDistReq(bestHash common.Hash, reqID uint64, bes
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
|
||||||
cost := p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount))
|
cost := p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount))
|
||||||
p.fcServer.QueueRequest(reqID, cost)
|
p.fcServer.QueuedRequest(reqID, cost)
|
||||||
f.reqMu.Lock()
|
f.reqMu.Lock()
|
||||||
f.requested[reqID] = fetchRequest{hash: bestHash, amount: bestAmount, peer: p, sent: mclock.Now()}
|
f.requested[reqID] = fetchRequest{hash: bestHash, amount: bestAmount, peer: p, sent: mclock.Now()}
|
||||||
f.reqMu.Unlock()
|
f.reqMu.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFetcherULCPeerSelector(t *testing.T) {
|
func TestFetcherULCPeerSelector(t *testing.T) {
|
||||||
|
id1 := newNodeID(t).ID()
|
||||||
var (
|
id2 := newNodeID(t).ID()
|
||||||
id1 enode.ID = newNodeID(t).ID()
|
id3 := newNodeID(t).ID()
|
||||||
id2 enode.ID = newNodeID(t).ID()
|
id4 := newNodeID(t).ID()
|
||||||
id3 enode.ID = newNodeID(t).ID()
|
|
||||||
id4 enode.ID = newNodeID(t).ID()
|
|
||||||
)
|
|
||||||
|
|
||||||
ftn1 := &fetcherTreeNode{
|
ftn1 := &fetcherTreeNode{
|
||||||
hash: common.HexToHash("1"),
|
hash: common.HexToHash("1"),
|
||||||
|
|
|
||||||
|
|
@ -18,166 +18,339 @@
|
||||||
package flowcontrol
|
package flowcontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const fcTimeConst = time.Millisecond
|
const (
|
||||||
|
// fcTimeConst is the time constant applied for MinRecharge during linear
|
||||||
|
// buffer recharge period
|
||||||
|
fcTimeConst = time.Millisecond
|
||||||
|
// DecParamDelay is applied at server side when decreasing capacity in order to
|
||||||
|
// avoid a buffer underrun error due to requests sent by the client before
|
||||||
|
// receiving the capacity update announcement
|
||||||
|
DecParamDelay = time.Second * 2
|
||||||
|
// keepLogs is the duration of keeping logs; logging is not used if zero
|
||||||
|
keepLogs = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerParams are the flow control parameters specified by a server for a client
|
||||||
|
//
|
||||||
|
// Note: a server can assign different amounts of capacity to each client by giving
|
||||||
|
// different parameters to them.
|
||||||
type ServerParams struct {
|
type ServerParams struct {
|
||||||
BufLimit, MinRecharge uint64
|
BufLimit, MinRecharge uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientNode struct {
|
// scheduledUpdate represents a delayed flow control parameter update
|
||||||
params *ServerParams
|
type scheduledUpdate struct {
|
||||||
bufValue uint64
|
time mclock.AbsTime
|
||||||
lastTime mclock.AbsTime
|
params ServerParams
|
||||||
lock sync.Mutex
|
|
||||||
cm *ClientManager
|
|
||||||
cmNode *cmNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientNode(cm *ClientManager, params *ServerParams) *ClientNode {
|
// ClientNode is the flow control system's representation of a client
|
||||||
|
// (used in server mode only)
|
||||||
|
type ClientNode struct {
|
||||||
|
params ServerParams
|
||||||
|
bufValue uint64
|
||||||
|
lastTime mclock.AbsTime
|
||||||
|
updateSchedule []scheduledUpdate
|
||||||
|
sumCost uint64 // sum of req costs received from this client
|
||||||
|
accepted map[uint64]uint64 // value = sumCost after accepting the given req
|
||||||
|
lock sync.Mutex
|
||||||
|
cm *ClientManager
|
||||||
|
log *logger
|
||||||
|
cmNodeFields
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientNode returns a new ClientNode
|
||||||
|
func NewClientNode(cm *ClientManager, params ServerParams) *ClientNode {
|
||||||
node := &ClientNode{
|
node := &ClientNode{
|
||||||
cm: cm,
|
cm: cm,
|
||||||
params: params,
|
params: params,
|
||||||
bufValue: params.BufLimit,
|
bufValue: params.BufLimit,
|
||||||
lastTime: mclock.Now(),
|
lastTime: cm.clock.Now(),
|
||||||
|
accepted: make(map[uint64]uint64),
|
||||||
}
|
}
|
||||||
node.cmNode = cm.addNode(node)
|
if keepLogs > 0 {
|
||||||
|
node.log = newLogger(keepLogs)
|
||||||
|
}
|
||||||
|
cm.connect(node)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *ClientNode) Remove(cm *ClientManager) {
|
// Disconnect should be called when a client is disconnected
|
||||||
cm.removeNode(peer.cmNode)
|
func (node *ClientNode) Disconnect() {
|
||||||
|
node.cm.disconnect(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *ClientNode) recalcBV(time mclock.AbsTime) {
|
// update recalculates the buffer value at a specified time while also performing
|
||||||
dt := uint64(time - peer.lastTime)
|
// scheduled flow control parameter updates if necessary
|
||||||
if time < peer.lastTime {
|
func (node *ClientNode) update(now mclock.AbsTime) {
|
||||||
|
for len(node.updateSchedule) > 0 && node.updateSchedule[0].time <= now {
|
||||||
|
node.recalcBV(node.updateSchedule[0].time)
|
||||||
|
node.updateParams(node.updateSchedule[0].params, now)
|
||||||
|
node.updateSchedule = node.updateSchedule[1:]
|
||||||
|
}
|
||||||
|
node.recalcBV(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalcBV recalculates the buffer value at a specified time
|
||||||
|
func (node *ClientNode) recalcBV(now mclock.AbsTime) {
|
||||||
|
dt := uint64(now - node.lastTime)
|
||||||
|
if now < node.lastTime {
|
||||||
dt = 0
|
dt = 0
|
||||||
}
|
}
|
||||||
peer.bufValue += peer.params.MinRecharge * dt / uint64(fcTimeConst)
|
node.bufValue += node.params.MinRecharge * dt / uint64(fcTimeConst)
|
||||||
if peer.bufValue > peer.params.BufLimit {
|
if node.bufValue > node.params.BufLimit {
|
||||||
peer.bufValue = peer.params.BufLimit
|
node.bufValue = node.params.BufLimit
|
||||||
}
|
}
|
||||||
peer.lastTime = time
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("updated bv=%d MRR=%d BufLimit=%d", node.bufValue, node.params.MinRecharge, node.params.BufLimit))
|
||||||
|
}
|
||||||
|
node.lastTime = now
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *ClientNode) AcceptRequest() (uint64, bool) {
|
// UpdateParams updates the flow control parameters of a client node
|
||||||
peer.lock.Lock()
|
func (node *ClientNode) UpdateParams(params ServerParams) {
|
||||||
defer peer.lock.Unlock()
|
node.lock.Lock()
|
||||||
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
time := mclock.Now()
|
now := node.cm.clock.Now()
|
||||||
peer.recalcBV(time)
|
node.update(now)
|
||||||
return peer.bufValue, peer.cm.accept(peer.cmNode, time)
|
if params.MinRecharge >= node.params.MinRecharge {
|
||||||
}
|
node.updateSchedule = nil
|
||||||
|
node.updateParams(params, now)
|
||||||
func (peer *ClientNode) RequestProcessed(cost uint64) (bv, realCost uint64) {
|
} else {
|
||||||
peer.lock.Lock()
|
for i, s := range node.updateSchedule {
|
||||||
defer peer.lock.Unlock()
|
if params.MinRecharge >= s.params.MinRecharge {
|
||||||
|
s.params = params
|
||||||
time := mclock.Now()
|
node.updateSchedule = node.updateSchedule[:i+1]
|
||||||
peer.recalcBV(time)
|
return
|
||||||
peer.bufValue -= cost
|
|
||||||
rcValue, rcost := peer.cm.processed(peer.cmNode, time)
|
|
||||||
if rcValue < peer.params.BufLimit {
|
|
||||||
bv := peer.params.BufLimit - rcValue
|
|
||||||
if bv > peer.bufValue {
|
|
||||||
peer.bufValue = bv
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return peer.bufValue, rcost
|
node.updateSchedule = append(node.updateSchedule, scheduledUpdate{time: now + mclock.AbsTime(DecParamDelay), params: params})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateParams updates the flow control parameters of the node
|
||||||
|
func (node *ClientNode) updateParams(params ServerParams, now mclock.AbsTime) {
|
||||||
|
diff := params.BufLimit - node.params.BufLimit
|
||||||
|
if int64(diff) > 0 {
|
||||||
|
node.bufValue += diff
|
||||||
|
} else if node.bufValue > params.BufLimit {
|
||||||
|
node.bufValue = params.BufLimit
|
||||||
|
}
|
||||||
|
node.cm.updateParams(node, params, now)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptRequest returns whether a new request can be accepted and the missing
|
||||||
|
// buffer amount if it was rejected due to a buffer underrun. If accepted, maxCost
|
||||||
|
// is deducted from the flow control buffer.
|
||||||
|
func (node *ClientNode) AcceptRequest(reqID, index, maxCost uint64) (accepted bool, bufShort uint64, priority int64) {
|
||||||
|
node.lock.Lock()
|
||||||
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
|
now := node.cm.clock.Now()
|
||||||
|
node.update(now)
|
||||||
|
if maxCost > node.bufValue {
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("rejected reqID=%d bv=%d maxCost=%d", reqID, node.bufValue, maxCost))
|
||||||
|
node.log.dump(now)
|
||||||
|
}
|
||||||
|
return false, maxCost - node.bufValue, 0
|
||||||
|
}
|
||||||
|
node.bufValue -= maxCost
|
||||||
|
node.sumCost += maxCost
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("accepted reqID=%d bv=%d maxCost=%d sumCost=%d", reqID, node.bufValue, maxCost, node.sumCost))
|
||||||
|
}
|
||||||
|
node.accepted[index] = node.sumCost
|
||||||
|
return true, 0, node.cm.accepted(node, maxCost, now)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestProcessed should be called when the request has been processed
|
||||||
|
func (node *ClientNode) RequestProcessed(reqID, index, maxCost, realCost uint64) (bv uint64) {
|
||||||
|
node.lock.Lock()
|
||||||
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
|
now := node.cm.clock.Now()
|
||||||
|
node.update(now)
|
||||||
|
node.cm.processed(node, maxCost, realCost, now)
|
||||||
|
bv = node.bufValue + node.sumCost - node.accepted[index]
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("processed reqID=%d bv=%d maxCost=%d realCost=%d sumCost=%d oldSumCost=%d reportedBV=%d", reqID, node.bufValue, maxCost, realCost, node.sumCost, node.accepted[index], bv))
|
||||||
|
}
|
||||||
|
delete(node.accepted, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerNode is the flow control system's representation of a server
|
||||||
|
// (used in client mode only)
|
||||||
type ServerNode struct {
|
type ServerNode struct {
|
||||||
|
clock mclock.Clock
|
||||||
bufEstimate uint64
|
bufEstimate uint64
|
||||||
|
bufRecharge bool
|
||||||
lastTime mclock.AbsTime
|
lastTime mclock.AbsTime
|
||||||
params *ServerParams
|
params ServerParams
|
||||||
sumCost uint64 // sum of req costs sent to this server
|
sumCost uint64 // sum of req costs sent to this server
|
||||||
pending map[uint64]uint64 // value = sumCost after sending the given req
|
pending map[uint64]uint64 // value = sumCost after sending the given req
|
||||||
|
log *logger
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerNode(params *ServerParams) *ServerNode {
|
// NewServerNode returns a new ServerNode
|
||||||
return &ServerNode{
|
func NewServerNode(params ServerParams, clock mclock.Clock) *ServerNode {
|
||||||
|
node := &ServerNode{
|
||||||
|
clock: clock,
|
||||||
bufEstimate: params.BufLimit,
|
bufEstimate: params.BufLimit,
|
||||||
lastTime: mclock.Now(),
|
bufRecharge: false,
|
||||||
|
lastTime: clock.Now(),
|
||||||
params: params,
|
params: params,
|
||||||
pending: make(map[uint64]uint64),
|
pending: make(map[uint64]uint64),
|
||||||
}
|
}
|
||||||
|
if keepLogs > 0 {
|
||||||
|
node.log = newLogger(keepLogs)
|
||||||
|
}
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *ServerNode) recalcBLE(time mclock.AbsTime) {
|
// UpdateParams updates the flow control parameters of the node
|
||||||
dt := uint64(time - peer.lastTime)
|
func (node *ServerNode) UpdateParams(params ServerParams) {
|
||||||
if time < peer.lastTime {
|
node.lock.Lock()
|
||||||
dt = 0
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
|
node.recalcBLE(mclock.Now())
|
||||||
|
if params.BufLimit > node.params.BufLimit {
|
||||||
|
node.bufEstimate += params.BufLimit - node.params.BufLimit
|
||||||
|
} else {
|
||||||
|
if node.bufEstimate > params.BufLimit {
|
||||||
|
node.bufEstimate = params.BufLimit
|
||||||
}
|
}
|
||||||
peer.bufEstimate += peer.params.MinRecharge * dt / uint64(fcTimeConst)
|
|
||||||
if peer.bufEstimate > peer.params.BufLimit {
|
|
||||||
peer.bufEstimate = peer.params.BufLimit
|
|
||||||
}
|
}
|
||||||
peer.lastTime = time
|
node.params = params
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalcBLE recalculates the lowest estimate for the client's buffer value at
|
||||||
|
// the given server at the specified time
|
||||||
|
func (node *ServerNode) recalcBLE(now mclock.AbsTime) {
|
||||||
|
if now < node.lastTime {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if node.bufRecharge {
|
||||||
|
dt := uint64(now - node.lastTime)
|
||||||
|
node.bufEstimate += node.params.MinRecharge * dt / uint64(fcTimeConst)
|
||||||
|
if node.bufEstimate >= node.params.BufLimit {
|
||||||
|
node.bufEstimate = node.params.BufLimit
|
||||||
|
node.bufRecharge = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.lastTime = now
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("updated bufEst=%d MRR=%d BufLimit=%d", node.bufEstimate, node.params.MinRecharge, node.params.BufLimit))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// safetyMargin is added to the flow control waiting time when estimated buffer value is low
|
// safetyMargin is added to the flow control waiting time when estimated buffer value is low
|
||||||
const safetyMargin = time.Millisecond
|
const safetyMargin = time.Millisecond
|
||||||
|
|
||||||
func (peer *ServerNode) canSend(maxCost uint64) (time.Duration, float64) {
|
|
||||||
peer.recalcBLE(mclock.Now())
|
|
||||||
maxCost += uint64(safetyMargin) * peer.params.MinRecharge / uint64(fcTimeConst)
|
|
||||||
if maxCost > peer.params.BufLimit {
|
|
||||||
maxCost = peer.params.BufLimit
|
|
||||||
}
|
|
||||||
if peer.bufEstimate >= maxCost {
|
|
||||||
return 0, float64(peer.bufEstimate-maxCost) / float64(peer.params.BufLimit)
|
|
||||||
}
|
|
||||||
return time.Duration((maxCost - peer.bufEstimate) * uint64(fcTimeConst) / peer.params.MinRecharge), 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanSend returns the minimum waiting time required before sending a request
|
// CanSend returns the minimum waiting time required before sending a request
|
||||||
// with the given maximum estimated cost. Second return value is the relative
|
// with the given maximum estimated cost. Second return value is the relative
|
||||||
// estimated buffer level after sending the request (divided by BufLimit).
|
// estimated buffer level after sending the request (divided by BufLimit).
|
||||||
func (peer *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) {
|
func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) {
|
||||||
peer.lock.RLock()
|
node.lock.RLock()
|
||||||
defer peer.lock.RUnlock()
|
defer node.lock.RUnlock()
|
||||||
|
|
||||||
return peer.canSend(maxCost)
|
now := node.clock.Now()
|
||||||
|
node.recalcBLE(now)
|
||||||
|
maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst)
|
||||||
|
if maxCost > node.params.BufLimit {
|
||||||
|
maxCost = node.params.BufLimit
|
||||||
|
}
|
||||||
|
if node.bufEstimate >= maxCost {
|
||||||
|
relBuf := float64(node.bufEstimate-maxCost) / float64(node.params.BufLimit)
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d true relBuf=%f", node.bufEstimate, maxCost, relBuf))
|
||||||
|
}
|
||||||
|
return 0, relBuf
|
||||||
|
}
|
||||||
|
timeLeft := time.Duration((maxCost - node.bufEstimate) * uint64(fcTimeConst) / node.params.MinRecharge)
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d false timeLeft=%v", node.bufEstimate, maxCost, timeLeft))
|
||||||
|
}
|
||||||
|
return timeLeft, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueueRequest should be called when the request has been assigned to the given
|
// QueuedRequest should be called when the request has been assigned to the given
|
||||||
// server node, before putting it in the send queue. It is mandatory that requests
|
// server node, before putting it in the send queue. It is mandatory that requests
|
||||||
// are sent in the same order as the QueueRequest calls are made.
|
// are sent in the same order as the QueuedRequest calls are made.
|
||||||
func (peer *ServerNode) QueueRequest(reqID, maxCost uint64) {
|
func (node *ServerNode) QueuedRequest(reqID, maxCost uint64) {
|
||||||
peer.lock.Lock()
|
node.lock.Lock()
|
||||||
defer peer.lock.Unlock()
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
peer.bufEstimate -= maxCost
|
now := node.clock.Now()
|
||||||
peer.sumCost += maxCost
|
node.recalcBLE(now)
|
||||||
peer.pending[reqID] = peer.sumCost
|
// Note: we do not know when requests actually arrive to the server so bufRecharge
|
||||||
|
// is not turned on here if buffer was full; in this case it is going to be turned
|
||||||
|
// on by the first reply's bufValue feedback
|
||||||
|
if node.bufEstimate >= maxCost {
|
||||||
|
node.bufEstimate -= maxCost
|
||||||
|
} else {
|
||||||
|
log.Error("Queued request with insufficient buffer estimate")
|
||||||
|
node.bufEstimate = 0
|
||||||
|
}
|
||||||
|
node.sumCost += maxCost
|
||||||
|
node.pending[reqID] = node.sumCost
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("queued reqID=%d bufEst=%d maxCost=%d sumCost=%d", reqID, node.bufEstimate, maxCost, node.sumCost))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GotReply adjusts estimated buffer value according to the value included in
|
// ReceivedReply adjusts estimated buffer value according to the value included in
|
||||||
// the latest request reply.
|
// the latest request reply.
|
||||||
func (peer *ServerNode) GotReply(reqID, bv uint64) {
|
func (node *ServerNode) ReceivedReply(reqID, bv uint64) {
|
||||||
|
node.lock.Lock()
|
||||||
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
peer.lock.Lock()
|
now := node.clock.Now()
|
||||||
defer peer.lock.Unlock()
|
node.recalcBLE(now)
|
||||||
|
if bv > node.params.BufLimit {
|
||||||
if bv > peer.params.BufLimit {
|
bv = node.params.BufLimit
|
||||||
bv = peer.params.BufLimit
|
|
||||||
}
|
}
|
||||||
sc, ok := peer.pending[reqID]
|
sc, ok := node.pending[reqID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(peer.pending, reqID)
|
delete(node.pending, reqID)
|
||||||
cc := peer.sumCost - sc
|
cc := node.sumCost - sc
|
||||||
peer.bufEstimate = 0
|
newEstimate := uint64(0)
|
||||||
if bv > cc {
|
if bv > cc {
|
||||||
peer.bufEstimate = bv - cc
|
newEstimate = bv - cc
|
||||||
|
}
|
||||||
|
if newEstimate > node.bufEstimate {
|
||||||
|
// Note: we never reduce the buffer estimate based on the reported value because
|
||||||
|
// this can only happen because of the delayed delivery of the latest reply.
|
||||||
|
// The lowest estimate based on the previous reply can still be considered valid.
|
||||||
|
node.bufEstimate = newEstimate
|
||||||
|
}
|
||||||
|
|
||||||
|
node.bufRecharge = node.bufEstimate < node.params.BufLimit
|
||||||
|
node.lastTime = now
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("received reqID=%d bufEst=%d reportedBv=%d sumCost=%d oldSumCost=%d", reqID, node.bufEstimate, bv, node.sumCost, sc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpLogs dumps the event log if logging is used
|
||||||
|
func (node *ServerNode) DumpLogs() {
|
||||||
|
node.lock.Lock()
|
||||||
|
defer node.lock.Unlock()
|
||||||
|
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.dump(node.clock.Now())
|
||||||
}
|
}
|
||||||
peer.lastTime = mclock.Now()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
les/flowcontrol/logger.go
Normal file
65
les/flowcontrol/logger.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package flowcontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// logger collects events in string format and discards events older than the
|
||||||
|
// "keep" parameter
|
||||||
|
type logger struct {
|
||||||
|
events map[uint64]logEvent
|
||||||
|
writePtr, delPtr uint64
|
||||||
|
keep time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// logEvent describes a single event
|
||||||
|
type logEvent struct {
|
||||||
|
time mclock.AbsTime
|
||||||
|
event string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLogger creates a new logger
|
||||||
|
func newLogger(keep time.Duration) *logger {
|
||||||
|
return &logger{
|
||||||
|
events: make(map[uint64]logEvent),
|
||||||
|
keep: keep,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds a new event and discards old events if possible
|
||||||
|
func (l *logger) add(now mclock.AbsTime, event string) {
|
||||||
|
keepAfter := now - mclock.AbsTime(l.keep)
|
||||||
|
for l.delPtr < l.writePtr && l.events[l.delPtr].time <= keepAfter {
|
||||||
|
delete(l.events, l.delPtr)
|
||||||
|
l.delPtr++
|
||||||
|
}
|
||||||
|
l.events[l.writePtr] = logEvent{now, event}
|
||||||
|
l.writePtr++
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump prints all stored events
|
||||||
|
func (l *logger) dump(now mclock.AbsTime) {
|
||||||
|
for i := l.delPtr; i < l.writePtr; i++ {
|
||||||
|
e := l.events[i]
|
||||||
|
fmt.Println(time.Duration(e.time-now), e.event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2018 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -14,211 +14,388 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package flowcontrol implements a client side flow control mechanism
|
|
||||||
package flowcontrol
|
package flowcontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
)
|
)
|
||||||
|
|
||||||
const rcConst = 1000000
|
// cmNodeFields are ClientNode fields used by the client manager
|
||||||
|
// Note: these fields are locked by the client manager's mutex
|
||||||
type cmNode struct {
|
type cmNodeFields struct {
|
||||||
node *ClientNode
|
corrBufValue int64 // buffer value adjusted with the extra recharge amount
|
||||||
lastUpdate mclock.AbsTime
|
rcLastIntValue int64 // past recharge integrator value when corrBufValue was last updated
|
||||||
serving, recharging bool
|
rcFullIntValue int64 // future recharge integrator value when corrBufValue will reach maximum
|
||||||
rcWeight uint64
|
queueIndex int // position in the recharge queue (-1 if not queued)
|
||||||
rcValue, rcDelta, startValue int64
|
|
||||||
finishRecharge mclock.AbsTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *cmNode) update(time mclock.AbsTime) {
|
// FixedPointMultiplier is applied to the recharge integrator and the recharge curve.
|
||||||
dt := int64(time - node.lastUpdate)
|
//
|
||||||
node.rcValue += node.rcDelta * dt / rcConst
|
// Note: fixed point arithmetic is required for the integrator because it is a
|
||||||
node.lastUpdate = time
|
// constantly increasing value that can wrap around int64 limits (which behavior is
|
||||||
if node.recharging && time >= node.finishRecharge {
|
// also supported by the priority queue). A floating point value would gradually lose
|
||||||
node.recharging = false
|
// precision in this application.
|
||||||
node.rcDelta = 0
|
// The recharge curve and all recharge values are encoded as fixed point because
|
||||||
node.rcValue = 0
|
// sumRecharge is frequently updated by adding or subtracting individual recharge
|
||||||
}
|
// values and perfect precision is required.
|
||||||
}
|
const FixedPointMultiplier = 1000000
|
||||||
|
|
||||||
func (node *cmNode) set(serving bool, simReqCnt, sumWeight uint64) {
|
var (
|
||||||
if node.serving && !serving {
|
capFactorDropTC = 1 / float64(time.Second*10) // time constant for dropping the capacity factor
|
||||||
node.recharging = true
|
capFactorRaiseTC = 1 / float64(time.Hour) // time constant for raising the capacity factor
|
||||||
sumWeight += node.rcWeight
|
capFactorRaiseThreshold = 0.75 // connected / total capacity ratio threshold for raising the capacity factor
|
||||||
}
|
)
|
||||||
node.serving = serving
|
|
||||||
if node.recharging && serving {
|
|
||||||
node.recharging = false
|
|
||||||
sumWeight -= node.rcWeight
|
|
||||||
}
|
|
||||||
|
|
||||||
node.rcDelta = 0
|
|
||||||
if serving {
|
|
||||||
node.rcDelta = int64(rcConst / simReqCnt)
|
|
||||||
}
|
|
||||||
if node.recharging {
|
|
||||||
node.rcDelta = -int64(node.node.cm.rcRecharge * node.rcWeight / sumWeight)
|
|
||||||
node.finishRecharge = node.lastUpdate + mclock.AbsTime(node.rcValue*rcConst/(-node.rcDelta))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// ClientManager controls the capacity assigned to the clients of a server.
|
||||||
|
// Since ServerParams guarantee a safe lower estimate for processable requests
|
||||||
|
// even in case of all clients being active, ClientManager calculates a
|
||||||
|
// corrigated buffer value and usually allows a higher remaining buffer value
|
||||||
|
// to be returned with each reply.
|
||||||
type ClientManager struct {
|
type ClientManager struct {
|
||||||
|
clock mclock.Clock
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
nodes map[*cmNode]struct{}
|
enabledCh chan struct{}
|
||||||
simReqCnt, sumWeight, rcSumValue uint64
|
|
||||||
maxSimReq, maxRcSum uint64
|
curve PieceWiseLinear
|
||||||
rcRecharge uint64
|
sumRecharge, totalRecharge, totalConnected uint64
|
||||||
resumeQueue chan chan bool
|
capLogFactor, totalCapacity float64
|
||||||
time mclock.AbsTime
|
capLastUpdate mclock.AbsTime
|
||||||
|
totalCapacityCh chan uint64
|
||||||
|
|
||||||
|
// recharge integrator is increasing in each moment with a rate of
|
||||||
|
// (totalRecharge / sumRecharge)*FixedPointMultiplier or 0 if sumRecharge==0
|
||||||
|
rcLastUpdate mclock.AbsTime // last time the recharge integrator was updated
|
||||||
|
rcLastIntValue int64 // last updated value of the recharge integrator
|
||||||
|
// recharge queue is a priority queue with currently recharging client nodes
|
||||||
|
// as elements. The priority value is rcFullIntValue which allows to quickly
|
||||||
|
// determine which client will first finish recharge.
|
||||||
|
rcQueue *prque.Prque
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientManager(rcTarget, maxSimReq, maxRcSum uint64) *ClientManager {
|
// NewClientManager returns a new client manager.
|
||||||
|
// Client manager enhances flow control performance by allowing client buffers
|
||||||
|
// to recharge quicker than the minimum guaranteed recharge rate if possible.
|
||||||
|
// The sum of all minimum recharge rates (sumRecharge) is updated each time
|
||||||
|
// a clients starts or finishes buffer recharging. Then an adjusted total
|
||||||
|
// recharge rate is calculated using a piecewise linear recharge curve:
|
||||||
|
//
|
||||||
|
// totalRecharge = curve(sumRecharge)
|
||||||
|
// (totalRecharge >= sumRecharge is enforced)
|
||||||
|
//
|
||||||
|
// Then the "bonus" buffer recharge is distributed between currently recharging
|
||||||
|
// clients proportionally to their minimum recharge rates.
|
||||||
|
//
|
||||||
|
// Note: total recharge is proportional to the average number of parallel running
|
||||||
|
// serving threads. A recharge value of 1000000 corresponds to one thread in average.
|
||||||
|
// The maximum number of allowed serving threads should always be considerably
|
||||||
|
// higher than the targeted average number.
|
||||||
|
//
|
||||||
|
// Note 2: although it is possible to specify a curve allowing the total target
|
||||||
|
// recharge starting from zero sumRecharge, it makes sense to add a linear ramp
|
||||||
|
// starting from zero in order to not let a single low-priority client use up
|
||||||
|
// the entire server capacity and thus ensure quick availability for others at
|
||||||
|
// any moment.
|
||||||
|
func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager {
|
||||||
cm := &ClientManager{
|
cm := &ClientManager{
|
||||||
nodes: make(map[*cmNode]struct{}),
|
clock: clock,
|
||||||
resumeQueue: make(chan chan bool),
|
rcQueue: prque.New(func(a interface{}, i int) { a.(*ClientNode).queueIndex = i }),
|
||||||
rcRecharge: rcConst * rcConst / (100*rcConst/rcTarget - rcConst),
|
capLastUpdate: clock.Now(),
|
||||||
maxSimReq: maxSimReq,
|
}
|
||||||
maxRcSum: maxRcSum,
|
if curve != nil {
|
||||||
|
cm.SetRechargeCurve(curve)
|
||||||
}
|
}
|
||||||
go cm.queueProc()
|
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ClientManager) Stop() {
|
// SetRechargeCurve updates the recharge curve
|
||||||
self.lock.Lock()
|
func (cm *ClientManager) SetRechargeCurve(curve PieceWiseLinear) {
|
||||||
defer self.lock.Unlock()
|
cm.lock.Lock()
|
||||||
|
defer cm.lock.Unlock()
|
||||||
|
|
||||||
// signal any waiting accept routines to return false
|
now := cm.clock.Now()
|
||||||
self.nodes = make(map[*cmNode]struct{})
|
cm.updateRecharge(now)
|
||||||
close(self.resumeQueue)
|
cm.updateCapFactor(now, false)
|
||||||
}
|
cm.curve = curve
|
||||||
|
if len(curve) > 0 {
|
||||||
func (self *ClientManager) addNode(cnode *ClientNode) *cmNode {
|
cm.totalRecharge = curve[len(curve)-1].Y
|
||||||
time := mclock.Now()
|
|
||||||
node := &cmNode{
|
|
||||||
node: cnode,
|
|
||||||
lastUpdate: time,
|
|
||||||
finishRecharge: time,
|
|
||||||
rcWeight: 1,
|
|
||||||
}
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
|
|
||||||
self.nodes[node] = struct{}{}
|
|
||||||
self.update(mclock.Now())
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ClientManager) removeNode(node *cmNode) {
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
|
|
||||||
time := mclock.Now()
|
|
||||||
self.stop(node, time)
|
|
||||||
delete(self.nodes, node)
|
|
||||||
self.update(time)
|
|
||||||
}
|
|
||||||
|
|
||||||
// recalc sumWeight
|
|
||||||
func (self *ClientManager) updateNodes(time mclock.AbsTime) (rce bool) {
|
|
||||||
var sumWeight, rcSum uint64
|
|
||||||
for node := range self.nodes {
|
|
||||||
rc := node.recharging
|
|
||||||
node.update(time)
|
|
||||||
if rc && !node.recharging {
|
|
||||||
rce = true
|
|
||||||
}
|
|
||||||
if node.recharging {
|
|
||||||
sumWeight += node.rcWeight
|
|
||||||
}
|
|
||||||
rcSum += uint64(node.rcValue)
|
|
||||||
}
|
|
||||||
self.sumWeight = sumWeight
|
|
||||||
self.rcSumValue = rcSum
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ClientManager) update(time mclock.AbsTime) {
|
|
||||||
for {
|
|
||||||
firstTime := time
|
|
||||||
for node := range self.nodes {
|
|
||||||
if node.recharging && node.finishRecharge < firstTime {
|
|
||||||
firstTime = node.finishRecharge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.updateNodes(firstTime) {
|
|
||||||
for node := range self.nodes {
|
|
||||||
if node.recharging {
|
|
||||||
node.set(node.serving, self.simReqCnt, self.sumWeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.time = time
|
cm.totalRecharge = 0
|
||||||
|
}
|
||||||
|
cm.refreshCapacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect should be called when a client is connected, before passing it to any
|
||||||
|
// other ClientManager function
|
||||||
|
func (cm *ClientManager) connect(node *ClientNode) {
|
||||||
|
cm.lock.Lock()
|
||||||
|
defer cm.lock.Unlock()
|
||||||
|
|
||||||
|
now := cm.clock.Now()
|
||||||
|
cm.updateRecharge(now)
|
||||||
|
node.corrBufValue = int64(node.params.BufLimit)
|
||||||
|
node.rcLastIntValue = cm.rcLastIntValue
|
||||||
|
node.queueIndex = -1
|
||||||
|
cm.updateCapFactor(now, true)
|
||||||
|
cm.totalConnected += node.params.MinRecharge
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnect should be called when a client is disconnected
|
||||||
|
func (cm *ClientManager) disconnect(node *ClientNode) {
|
||||||
|
cm.lock.Lock()
|
||||||
|
defer cm.lock.Unlock()
|
||||||
|
|
||||||
|
now := cm.clock.Now()
|
||||||
|
cm.updateRecharge(cm.clock.Now())
|
||||||
|
cm.updateCapFactor(now, true)
|
||||||
|
cm.totalConnected -= node.params.MinRecharge
|
||||||
|
}
|
||||||
|
|
||||||
|
// accepted is called when a request with given maximum cost is accepted.
|
||||||
|
// It returns a priority indicator for the request which is used to determine placement
|
||||||
|
// in the serving queue. Older requests have higher priority by default. If the client
|
||||||
|
// is almost out of buffer, request priority is reduced.
|
||||||
|
func (cm *ClientManager) accepted(node *ClientNode, maxCost uint64, now mclock.AbsTime) (priority int64) {
|
||||||
|
cm.lock.Lock()
|
||||||
|
defer cm.lock.Unlock()
|
||||||
|
|
||||||
|
cm.updateNodeRc(node, -int64(maxCost), &node.params, now)
|
||||||
|
rcTime := (node.params.BufLimit - uint64(node.corrBufValue)) * FixedPointMultiplier / node.params.MinRecharge
|
||||||
|
return -int64(now) - int64(rcTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processed updates the client buffer according to actual request cost after
|
||||||
|
// serving has been finished.
|
||||||
|
//
|
||||||
|
// Note: processed should always be called for all accepted requests
|
||||||
|
func (cm *ClientManager) processed(node *ClientNode, maxCost, realCost uint64, now mclock.AbsTime) {
|
||||||
|
cm.lock.Lock()
|
||||||
|
defer cm.lock.Unlock()
|
||||||
|
|
||||||
|
if realCost > maxCost {
|
||||||
|
realCost = maxCost
|
||||||
|
}
|
||||||
|
cm.updateNodeRc(node, int64(maxCost-realCost), &node.params, now)
|
||||||
|
if uint64(node.corrBufValue) > node.bufValue {
|
||||||
|
if node.log != nil {
|
||||||
|
node.log.add(now, fmt.Sprintf("corrected bv=%d oldBv=%d", node.corrBufValue, node.bufValue))
|
||||||
|
}
|
||||||
|
node.bufValue = uint64(node.corrBufValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateParams updates the flow control parameters of a client node
|
||||||
|
func (cm *ClientManager) updateParams(node *ClientNode, params ServerParams, now mclock.AbsTime) {
|
||||||
|
cm.lock.Lock()
|
||||||
|
defer cm.lock.Unlock()
|
||||||
|
|
||||||
|
cm.updateRecharge(now)
|
||||||
|
cm.updateCapFactor(now, true)
|
||||||
|
cm.totalConnected += params.MinRecharge - node.params.MinRecharge
|
||||||
|
cm.updateNodeRc(node, 0, ¶ms, now)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRecharge updates the recharge integrator and checks the recharge queue
|
||||||
|
// for nodes with recently filled buffers
|
||||||
|
func (cm *ClientManager) updateRecharge(now mclock.AbsTime) {
|
||||||
|
lastUpdate := cm.rcLastUpdate
|
||||||
|
cm.rcLastUpdate = now
|
||||||
|
// updating is done in multiple steps if node buffers are filled and sumRecharge
|
||||||
|
// is decreased before the given target time
|
||||||
|
for cm.sumRecharge > 0 {
|
||||||
|
bonusRatio := cm.curve.ValueAt(cm.sumRecharge) / float64(cm.sumRecharge)
|
||||||
|
if bonusRatio < 1 {
|
||||||
|
bonusRatio = 1
|
||||||
|
}
|
||||||
|
dt := now - lastUpdate
|
||||||
|
// fetch the client that finishes first
|
||||||
|
rcqNode := cm.rcQueue.PopItem().(*ClientNode) // if sumRecharge > 0 then the queue cannot be empty
|
||||||
|
// check whether it has already finished
|
||||||
|
dtNext := mclock.AbsTime(float64(rcqNode.rcFullIntValue-cm.rcLastIntValue) / bonusRatio)
|
||||||
|
if dt < dtNext {
|
||||||
|
// not finished yet, put it back, update integrator according
|
||||||
|
// to current bonusRatio and return
|
||||||
|
cm.rcQueue.Push(rcqNode, -rcqNode.rcFullIntValue)
|
||||||
|
cm.rcLastIntValue += int64(bonusRatio * float64(dt))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
lastUpdate += dtNext
|
||||||
|
// finished recharging, update corrBufValue and sumRecharge if necessary and do next step
|
||||||
|
if rcqNode.corrBufValue < int64(rcqNode.params.BufLimit) {
|
||||||
|
rcqNode.corrBufValue = int64(rcqNode.params.BufLimit)
|
||||||
|
cm.updateCapFactor(lastUpdate, true)
|
||||||
|
cm.sumRecharge -= rcqNode.params.MinRecharge
|
||||||
|
}
|
||||||
|
cm.rcLastIntValue = rcqNode.rcFullIntValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ClientManager) canStartReq() bool {
|
// updateNodeRc updates a node's corrBufValue and adds an external correction value.
|
||||||
return self.simReqCnt < self.maxSimReq && self.rcSumValue < self.maxRcSum
|
// It also adds or removes the rcQueue entry and updates ServerParams and sumRecharge if necessary.
|
||||||
|
func (cm *ClientManager) updateNodeRc(node *ClientNode, bvc int64, params *ServerParams, now mclock.AbsTime) {
|
||||||
|
cm.updateRecharge(now)
|
||||||
|
wasFull := true
|
||||||
|
if node.corrBufValue != int64(node.params.BufLimit) {
|
||||||
|
wasFull = false
|
||||||
|
node.corrBufValue += (cm.rcLastIntValue - node.rcLastIntValue) * int64(node.params.MinRecharge) / FixedPointMultiplier
|
||||||
|
if node.corrBufValue > int64(node.params.BufLimit) {
|
||||||
|
node.corrBufValue = int64(node.params.BufLimit)
|
||||||
|
}
|
||||||
|
node.rcLastIntValue = cm.rcLastIntValue
|
||||||
|
}
|
||||||
|
node.corrBufValue += bvc
|
||||||
|
if node.corrBufValue < 0 {
|
||||||
|
node.corrBufValue = 0
|
||||||
|
}
|
||||||
|
diff := int64(params.BufLimit - node.params.BufLimit)
|
||||||
|
if diff > 0 {
|
||||||
|
node.corrBufValue += diff
|
||||||
|
}
|
||||||
|
isFull := false
|
||||||
|
if node.corrBufValue >= int64(params.BufLimit) {
|
||||||
|
node.corrBufValue = int64(params.BufLimit)
|
||||||
|
isFull = true
|
||||||
|
}
|
||||||
|
sumRecharge := cm.sumRecharge
|
||||||
|
if !wasFull {
|
||||||
|
sumRecharge -= node.params.MinRecharge
|
||||||
|
}
|
||||||
|
if params != &node.params {
|
||||||
|
node.params = *params
|
||||||
|
}
|
||||||
|
if !isFull {
|
||||||
|
sumRecharge += node.params.MinRecharge
|
||||||
|
if node.queueIndex != -1 {
|
||||||
|
cm.rcQueue.Remove(node.queueIndex)
|
||||||
|
}
|
||||||
|
node.rcLastIntValue = cm.rcLastIntValue
|
||||||
|
node.rcFullIntValue = cm.rcLastIntValue + (int64(node.params.BufLimit)-node.corrBufValue)*FixedPointMultiplier/int64(node.params.MinRecharge)
|
||||||
|
cm.rcQueue.Push(node, -node.rcFullIntValue)
|
||||||
|
}
|
||||||
|
if sumRecharge != cm.sumRecharge {
|
||||||
|
cm.updateCapFactor(now, true)
|
||||||
|
cm.sumRecharge = sumRecharge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ClientManager) queueProc() {
|
}
|
||||||
for rc := range self.resumeQueue {
|
|
||||||
for {
|
// updateCapFactor updates the total capacity factor. The capacity factor allows
|
||||||
time.Sleep(time.Millisecond * 10)
|
// the total capacity of the system to go over the allowed total recharge value
|
||||||
self.lock.Lock()
|
// if the sum of momentarily recharging clients only exceeds the total recharge
|
||||||
self.update(mclock.Now())
|
// allowance in a very small fraction of time.
|
||||||
cs := self.canStartReq()
|
// The capacity factor is dropped quickly (with a small time constant) if sumRecharge
|
||||||
self.lock.Unlock()
|
// exceeds totalRecharge. It is raised slowly (with a large time constant) if most
|
||||||
if cs {
|
// of the total capacity is used by connected clients (totalConnected is larger than
|
||||||
break
|
// totalCapacity*capFactorRaiseThreshold) and sumRecharge stays under
|
||||||
|
// totalRecharge*totalConnected/totalCapacity.
|
||||||
|
func (cm *ClientManager) updateCapFactor(now mclock.AbsTime, refresh bool) {
|
||||||
|
if cm.totalRecharge == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dt := now - cm.capLastUpdate
|
||||||
|
cm.capLastUpdate = now
|
||||||
|
|
||||||
|
var d float64
|
||||||
|
if cm.sumRecharge > cm.totalRecharge {
|
||||||
|
d = (1 - float64(cm.sumRecharge)/float64(cm.totalRecharge)) * capFactorDropTC
|
||||||
|
} else {
|
||||||
|
totalConnected := float64(cm.totalConnected)
|
||||||
|
var connRatio float64
|
||||||
|
if totalConnected < cm.totalCapacity {
|
||||||
|
connRatio = totalConnected / cm.totalCapacity
|
||||||
|
} else {
|
||||||
|
connRatio = 1
|
||||||
|
}
|
||||||
|
if connRatio > capFactorRaiseThreshold {
|
||||||
|
sumRecharge := float64(cm.sumRecharge)
|
||||||
|
limit := float64(cm.totalRecharge) * connRatio
|
||||||
|
if sumRecharge < limit {
|
||||||
|
d = (1 - sumRecharge/limit) * (connRatio - capFactorRaiseThreshold) * (1 / (1 - capFactorRaiseThreshold)) * capFactorRaiseTC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(rc)
|
}
|
||||||
|
if d != 0 {
|
||||||
|
cm.capLogFactor += d * float64(dt)
|
||||||
|
if cm.capLogFactor < 0 {
|
||||||
|
cm.capLogFactor = 0
|
||||||
|
}
|
||||||
|
if refresh {
|
||||||
|
cm.refreshCapacity()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ClientManager) accept(node *cmNode, time mclock.AbsTime) bool {
|
// refreshCapacity recalculates the total capacity value and sends an update to the subscription
|
||||||
self.lock.Lock()
|
// channel if the relative change of the value since the last update is more than 0.1 percent
|
||||||
defer self.lock.Unlock()
|
func (cm *ClientManager) refreshCapacity() {
|
||||||
|
totalCapacity := float64(cm.totalRecharge) * math.Exp(cm.capLogFactor)
|
||||||
|
if totalCapacity >= cm.totalCapacity*0.999 && totalCapacity <= cm.totalCapacity*1.001 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cm.totalCapacity = totalCapacity
|
||||||
|
if cm.totalCapacityCh != nil {
|
||||||
|
select {
|
||||||
|
case cm.totalCapacityCh <- uint64(cm.totalCapacity):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.update(time)
|
// SubscribeTotalCapacity returns all future updates to the total capacity value
|
||||||
if !self.canStartReq() {
|
// through a channel and also returns the current value
|
||||||
resume := make(chan bool)
|
func (cm *ClientManager) SubscribeTotalCapacity(ch chan uint64) uint64 {
|
||||||
self.lock.Unlock()
|
cm.lock.Lock()
|
||||||
self.resumeQueue <- resume
|
defer cm.lock.Unlock()
|
||||||
<-resume
|
|
||||||
self.lock.Lock()
|
cm.totalCapacityCh = ch
|
||||||
if _, ok := self.nodes[node]; !ok {
|
return uint64(cm.totalCapacity)
|
||||||
return false // reject if node has been removed or manager has been stopped
|
}
|
||||||
|
|
||||||
|
// PieceWiseLinear is used to describe recharge curves
|
||||||
|
type PieceWiseLinear []struct{ X, Y uint64 }
|
||||||
|
|
||||||
|
// ValueAt returns the curve's value at a given point
|
||||||
|
func (pwl PieceWiseLinear) ValueAt(x uint64) float64 {
|
||||||
|
l := 0
|
||||||
|
h := len(pwl)
|
||||||
|
if h == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for h != l {
|
||||||
|
m := (l + h) / 2
|
||||||
|
if x > pwl[m].X {
|
||||||
|
l = m + 1
|
||||||
|
} else {
|
||||||
|
h = m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.simReqCnt++
|
if l == 0 {
|
||||||
node.set(true, self.simReqCnt, self.sumWeight)
|
return float64(pwl[0].Y)
|
||||||
node.startValue = node.rcValue
|
}
|
||||||
self.update(self.time)
|
l--
|
||||||
|
if h == len(pwl) {
|
||||||
|
return float64(pwl[l].Y)
|
||||||
|
}
|
||||||
|
dx := pwl[h].X - pwl[l].X
|
||||||
|
if dx < 1 {
|
||||||
|
return float64(pwl[l].Y)
|
||||||
|
}
|
||||||
|
return float64(pwl[l].Y) + float64(pwl[h].Y-pwl[l].Y)*float64(x-pwl[l].X)/float64(dx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if the X coordinates of the curve points are non-strictly monotonic
|
||||||
|
func (pwl PieceWiseLinear) Valid() bool {
|
||||||
|
var lastX uint64
|
||||||
|
for _, i := range pwl {
|
||||||
|
if i.X < lastX {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lastX = i.X
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ClientManager) stop(node *cmNode, time mclock.AbsTime) {
|
|
||||||
if node.serving {
|
|
||||||
self.update(time)
|
|
||||||
self.simReqCnt--
|
|
||||||
node.set(false, self.simReqCnt, self.sumWeight)
|
|
||||||
self.update(time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ClientManager) processed(node *cmNode, time mclock.AbsTime) (rcValue, rcCost uint64) {
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
|
|
||||||
self.stop(node, time)
|
|
||||||
return uint64(node.rcValue), uint64(node.rcValue - node.startValue)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
123
les/flowcontrol/manager_test.go
Normal file
123
les/flowcontrol/manager_test.go
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package flowcontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testNode struct {
|
||||||
|
node *ClientNode
|
||||||
|
bufLimit, capacity uint64
|
||||||
|
waitUntil mclock.AbsTime
|
||||||
|
index, totalCost uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testMaxCost = 1000000
|
||||||
|
testLength = 100000
|
||||||
|
)
|
||||||
|
|
||||||
|
// testConstantTotalCapacity simulates multiple request sender nodes and verifies
|
||||||
|
// whether the total amount of served requests matches the expected value based on
|
||||||
|
// the total capacity and the duration of the test.
|
||||||
|
// Some nodes are sending requests occasionally so that their buffer should regularly
|
||||||
|
// reach the maximum while other nodes (the "max capacity nodes") are sending at the
|
||||||
|
// maximum permitted rate. The max capacity nodes are changed multiple times during
|
||||||
|
// a single test.
|
||||||
|
func TestConstantTotalCapacity(t *testing.T) {
|
||||||
|
testConstantTotalCapacity(t, 10, 1, 0)
|
||||||
|
testConstantTotalCapacity(t, 10, 1, 1)
|
||||||
|
testConstantTotalCapacity(t, 30, 1, 0)
|
||||||
|
testConstantTotalCapacity(t, 30, 2, 3)
|
||||||
|
testConstantTotalCapacity(t, 100, 1, 0)
|
||||||
|
testConstantTotalCapacity(t, 100, 3, 5)
|
||||||
|
testConstantTotalCapacity(t, 100, 5, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConstantTotalCapacity(t *testing.T, nodeCount, maxCapacityNodes, randomSend int) {
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
nodes := make([]*testNode, nodeCount)
|
||||||
|
var totalCapacity uint64
|
||||||
|
for i := range nodes {
|
||||||
|
nodes[i] = &testNode{capacity: uint64(50000 + rand.Intn(100000))}
|
||||||
|
totalCapacity += nodes[i].capacity
|
||||||
|
}
|
||||||
|
m := NewClientManager(PieceWiseLinear{{0, totalCapacity}}, clock)
|
||||||
|
for _, n := range nodes {
|
||||||
|
n.bufLimit = n.capacity * 6000 //uint64(2000+rand.Intn(10000))
|
||||||
|
n.node = NewClientNode(m, ServerParams{BufLimit: n.bufLimit, MinRecharge: n.capacity})
|
||||||
|
}
|
||||||
|
maxNodes := make([]int, maxCapacityNodes)
|
||||||
|
for i := range maxNodes {
|
||||||
|
// we don't care if some indexes are selected multiple times
|
||||||
|
// in that case we have fewer max nodes
|
||||||
|
maxNodes[i] = rand.Intn(nodeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < testLength; i++ {
|
||||||
|
now := clock.Now()
|
||||||
|
for _, idx := range maxNodes {
|
||||||
|
for nodes[idx].send(t, now) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rand.Intn(testLength) < maxCapacityNodes*3 {
|
||||||
|
maxNodes[rand.Intn(maxCapacityNodes)] = rand.Intn(nodeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCount := randomSend
|
||||||
|
for sendCount > 0 {
|
||||||
|
if nodes[rand.Intn(nodeCount)].send(t, now) {
|
||||||
|
sendCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clock.Run(time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCost uint64
|
||||||
|
for _, n := range nodes {
|
||||||
|
totalCost += n.totalCost
|
||||||
|
}
|
||||||
|
ratio := float64(totalCost) / float64(totalCapacity) / testLength
|
||||||
|
if ratio < 0.98 || ratio > 1.02 {
|
||||||
|
t.Errorf("totalCost/totalCapacity/testLength ratio incorrect (expected: 1, got: %f)", ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *testNode) send(t *testing.T, now mclock.AbsTime) bool {
|
||||||
|
if now < n.waitUntil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n.index++
|
||||||
|
if ok, _, _ := n.node.AcceptRequest(0, n.index, testMaxCost); !ok {
|
||||||
|
t.Fatalf("Rejected request after expected waiting time has passed")
|
||||||
|
}
|
||||||
|
rcost := uint64(rand.Int63n(testMaxCost))
|
||||||
|
bv := n.node.RequestProcessed(0, n.index, testMaxCost, rcost)
|
||||||
|
if bv < testMaxCost {
|
||||||
|
n.waitUntil = now + mclock.AbsTime((testMaxCost-bv)*1001000/n.capacity)
|
||||||
|
}
|
||||||
|
//n.waitUntil = now + mclock.AbsTime(float64(testMaxCost)*1001000/float64(n.capacity)*(1-float64(bv)/float64(n.bufLimit)))
|
||||||
|
n.totalCost += rcost
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -14,12 +14,12 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -48,8 +48,10 @@ type freeClientPool struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
clock mclock.Clock
|
clock mclock.Clock
|
||||||
closed bool
|
closed bool
|
||||||
|
removePeer func(string)
|
||||||
|
|
||||||
connectedLimit, totalLimit int
|
connectedLimit, totalLimit int
|
||||||
|
freeClientCap uint64
|
||||||
|
|
||||||
addressMap map[string]*freeClientPoolEntry
|
addressMap map[string]*freeClientPoolEntry
|
||||||
connPool, disconnPool *prque.Prque
|
connPool, disconnPool *prque.Prque
|
||||||
|
|
@ -64,15 +66,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// newFreeClientPool creates a new free client pool
|
// newFreeClientPool creates a new free client pool
|
||||||
func newFreeClientPool(db ethdb.Database, connectedLimit, totalLimit int, clock mclock.Clock) *freeClientPool {
|
func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string)) *freeClientPool {
|
||||||
pool := &freeClientPool{
|
pool := &freeClientPool{
|
||||||
db: db,
|
db: db,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
addressMap: make(map[string]*freeClientPoolEntry),
|
addressMap: make(map[string]*freeClientPoolEntry),
|
||||||
connPool: prque.New(poolSetIndex),
|
connPool: prque.New(poolSetIndex),
|
||||||
disconnPool: prque.New(poolSetIndex),
|
disconnPool: prque.New(poolSetIndex),
|
||||||
connectedLimit: connectedLimit,
|
freeClientCap: freeClientCap,
|
||||||
totalLimit: totalLimit,
|
totalLimit: totalLimit,
|
||||||
|
removePeer: removePeer,
|
||||||
}
|
}
|
||||||
pool.loadFromDb()
|
pool.loadFromDb()
|
||||||
return pool
|
return pool
|
||||||
|
|
@ -85,22 +88,34 @@ func (f *freeClientPool) stop() {
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// registerPeer implements clientPool
|
||||||
|
func (f *freeClientPool) registerPeer(p *peer) {
|
||||||
|
if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
if !f.connect(addr.IP.String(), p.id) {
|
||||||
|
f.removePeer(p.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// connect should be called after a successful handshake. If the connection was
|
// connect should be called after a successful handshake. If the connection was
|
||||||
// rejected, there is no need to call disconnect.
|
// rejected, there is no need to call disconnect.
|
||||||
//
|
func (f *freeClientPool) connect(address, id string) bool {
|
||||||
// Note: the disconnectFn callback should not block.
|
|
||||||
func (f *freeClientPool) connect(address string, disconnectFn func()) bool {
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
if f.closed {
|
if f.closed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.connectedLimit == 0 {
|
||||||
|
log.Debug("Client rejected", "address", address)
|
||||||
|
return false
|
||||||
|
}
|
||||||
e := f.addressMap[address]
|
e := f.addressMap[address]
|
||||||
now := f.clock.Now()
|
now := f.clock.Now()
|
||||||
var recentUsage int64
|
var recentUsage int64
|
||||||
if e == nil {
|
if e == nil {
|
||||||
e = &freeClientPoolEntry{address: address, index: -1}
|
e = &freeClientPoolEntry{address: address, index: -1, id: id}
|
||||||
f.addressMap[address] = e
|
f.addressMap[address] = e
|
||||||
} else {
|
} else {
|
||||||
if e.connected {
|
if e.connected {
|
||||||
|
|
@ -115,12 +130,7 @@ func (f *freeClientPool) connect(address string, disconnectFn func()) bool {
|
||||||
i := f.connPool.PopItem().(*freeClientPoolEntry)
|
i := f.connPool.PopItem().(*freeClientPoolEntry)
|
||||||
if e.linUsage+int64(connectedBias)-i.linUsage < 0 {
|
if e.linUsage+int64(connectedBias)-i.linUsage < 0 {
|
||||||
// kick it out and accept the new client
|
// kick it out and accept the new client
|
||||||
f.connPool.Remove(i.index)
|
f.dropClient(i, now)
|
||||||
f.calcLogUsage(i, now)
|
|
||||||
i.connected = false
|
|
||||||
f.disconnPool.Push(i, -i.logUsage)
|
|
||||||
log.Debug("Client kicked out", "address", i.address)
|
|
||||||
i.disconnectFn()
|
|
||||||
} else {
|
} else {
|
||||||
// keep the old client and reject the new one
|
// keep the old client and reject the new one
|
||||||
f.connPool.Push(i, i.linUsage)
|
f.connPool.Push(i, i.linUsage)
|
||||||
|
|
@ -130,7 +140,7 @@ func (f *freeClientPool) connect(address string, disconnectFn func()) bool {
|
||||||
}
|
}
|
||||||
f.disconnPool.Remove(e.index)
|
f.disconnPool.Remove(e.index)
|
||||||
e.connected = true
|
e.connected = true
|
||||||
e.disconnectFn = disconnectFn
|
e.id = id
|
||||||
f.connPool.Push(e, e.linUsage)
|
f.connPool.Push(e, e.linUsage)
|
||||||
if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit {
|
if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit {
|
||||||
f.disconnPool.Pop()
|
f.disconnPool.Pop()
|
||||||
|
|
@ -139,6 +149,13 @@ func (f *freeClientPool) connect(address string, disconnectFn func()) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unregisterPeer implements clientPool
|
||||||
|
func (f *freeClientPool) unregisterPeer(p *peer) {
|
||||||
|
if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
f.disconnect(addr.IP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// disconnect should be called when a connection is terminated. If the disconnection
|
// disconnect should be called when a connection is terminated. If the disconnection
|
||||||
// was initiated by the pool itself using disconnectFn then calling disconnect is
|
// was initiated by the pool itself using disconnectFn then calling disconnect is
|
||||||
// not necessary but permitted.
|
// not necessary but permitted.
|
||||||
|
|
@ -163,6 +180,34 @@ func (f *freeClientPool) disconnect(address string) {
|
||||||
log.Debug("Client disconnected", "address", address)
|
log.Debug("Client disconnected", "address", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setConnLimit sets the maximum number of free client slots and also drops
|
||||||
|
// some peers if necessary
|
||||||
|
func (f *freeClientPool) setLimits(count int, totalCap uint64) {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
f.connectedLimit = int(totalCap / f.freeClientCap)
|
||||||
|
if count < f.connectedLimit {
|
||||||
|
f.connectedLimit = count
|
||||||
|
}
|
||||||
|
now := mclock.Now()
|
||||||
|
for f.connPool.Size() > f.connectedLimit {
|
||||||
|
i := f.connPool.PopItem().(*freeClientPoolEntry)
|
||||||
|
f.dropClient(i, now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropClient disconnects a client and also moves it from the connected to the
|
||||||
|
// disconnected pool
|
||||||
|
func (f *freeClientPool) dropClient(i *freeClientPoolEntry, now mclock.AbsTime) {
|
||||||
|
f.connPool.Remove(i.index)
|
||||||
|
f.calcLogUsage(i, now)
|
||||||
|
i.connected = false
|
||||||
|
f.disconnPool.Push(i, -i.logUsage)
|
||||||
|
log.Debug("Client kicked out", "address", i.address)
|
||||||
|
f.removePeer(i.id)
|
||||||
|
}
|
||||||
|
|
||||||
// logOffset calculates the time-dependent offset for the logarithmic
|
// logOffset calculates the time-dependent offset for the logarithmic
|
||||||
// representation of recent usage
|
// representation of recent usage
|
||||||
func (f *freeClientPool) logOffset(now mclock.AbsTime) int64 {
|
func (f *freeClientPool) logOffset(now mclock.AbsTime) int64 {
|
||||||
|
|
@ -245,7 +290,7 @@ func (f *freeClientPool) saveToDb() {
|
||||||
// even though they are close to each other at any time they may wrap around int64
|
// even though they are close to each other at any time they may wrap around int64
|
||||||
// limits over time. Comparison should be performed accordingly.
|
// limits over time. Comparison should be performed accordingly.
|
||||||
type freeClientPoolEntry struct {
|
type freeClientPoolEntry struct {
|
||||||
address string
|
address, id string
|
||||||
connected bool
|
connected bool
|
||||||
disconnectFn func()
|
disconnectFn func()
|
||||||
linUsage, logUsage int64
|
linUsage, logUsage int64
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,12 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,30 +45,36 @@ func testFreeClientPool(t *testing.T, connLimit, clientCount int) {
|
||||||
var (
|
var (
|
||||||
clock mclock.Simulated
|
clock mclock.Simulated
|
||||||
db = ethdb.NewMemDatabase()
|
db = ethdb.NewMemDatabase()
|
||||||
pool = newFreeClientPool(db, connLimit, 10000, &clock)
|
|
||||||
connected = make([]bool, clientCount)
|
connected = make([]bool, clientCount)
|
||||||
connTicks = make([]int, clientCount)
|
connTicks = make([]int, clientCount)
|
||||||
disconnCh = make(chan int, clientCount)
|
disconnCh = make(chan int, clientCount)
|
||||||
)
|
peerAddress = func(i int) string {
|
||||||
peerId := func(i int) string {
|
return fmt.Sprintf("addr #%d", i)
|
||||||
return fmt.Sprintf("test peer #%d", i)
|
}
|
||||||
|
peerId = func(i int) string {
|
||||||
|
return fmt.Sprintf("id #%d", i)
|
||||||
|
}
|
||||||
|
disconnFn = func(id string) {
|
||||||
|
i, err := strconv.Atoi(id[4:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
disconnFn := func(i int) func() {
|
|
||||||
return func() {
|
|
||||||
disconnCh <- i
|
disconnCh <- i
|
||||||
}
|
}
|
||||||
}
|
pool = newFreeClientPool(db, 1, 10000, &clock, disconnFn)
|
||||||
|
)
|
||||||
|
pool.setLimits(connLimit, uint64(connLimit))
|
||||||
|
|
||||||
// pool should accept new peers up to its connected limit
|
// pool should accept new peers up to its connected limit
|
||||||
for i := 0; i < connLimit; i++ {
|
for i := 0; i < connLimit; i++ {
|
||||||
if pool.connect(peerId(i), disconnFn(i)) {
|
if pool.connect(peerAddress(i), peerId(i)) {
|
||||||
connected[i] = true
|
connected[i] = true
|
||||||
} else {
|
} else {
|
||||||
t.Fatalf("Test peer #%d rejected", i)
|
t.Fatalf("Test peer #%d rejected", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// since all accepted peers are new and should not be kicked out, the next one should be rejected
|
// since all accepted peers are new and should not be kicked out, the next one should be rejected
|
||||||
if pool.connect(peerId(connLimit), disconnFn(connLimit)) {
|
if pool.connect(peerAddress(connLimit), peerId(connLimit)) {
|
||||||
connected[connLimit] = true
|
connected[connLimit] = true
|
||||||
t.Fatalf("Peer accepted over connected limit")
|
t.Fatalf("Peer accepted over connected limit")
|
||||||
}
|
}
|
||||||
|
|
@ -80,11 +85,11 @@ func testFreeClientPool(t *testing.T, connLimit, clientCount int) {
|
||||||
|
|
||||||
i := rand.Intn(clientCount)
|
i := rand.Intn(clientCount)
|
||||||
if connected[i] {
|
if connected[i] {
|
||||||
pool.disconnect(peerId(i))
|
pool.disconnect(peerAddress(i))
|
||||||
connected[i] = false
|
connected[i] = false
|
||||||
connTicks[i] += tickCounter
|
connTicks[i] += tickCounter
|
||||||
} else {
|
} else {
|
||||||
if pool.connect(peerId(i), disconnFn(i)) {
|
if pool.connect(peerAddress(i), peerId(i)) {
|
||||||
connected[i] = true
|
connected[i] = true
|
||||||
connTicks[i] -= tickCounter
|
connTicks[i] -= tickCounter
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +98,7 @@ func testFreeClientPool(t *testing.T, connLimit, clientCount int) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case i := <-disconnCh:
|
case i := <-disconnCh:
|
||||||
pool.disconnect(peerId(i))
|
pool.disconnect(peerAddress(i))
|
||||||
if connected[i] {
|
if connected[i] {
|
||||||
connTicks[i] += tickCounter
|
connTicks[i] += tickCounter
|
||||||
connected[i] = false
|
connected[i] = false
|
||||||
|
|
@ -119,20 +124,21 @@ func testFreeClientPool(t *testing.T, connLimit, clientCount int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// a previously unknown peer should be accepted now
|
// a previously unknown peer should be accepted now
|
||||||
if !pool.connect("newPeer", func() {}) {
|
if !pool.connect("newAddr", "newId") {
|
||||||
t.Fatalf("Previously unknown peer rejected")
|
t.Fatalf("Previously unknown peer rejected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// close and restart pool
|
// close and restart pool
|
||||||
pool.stop()
|
pool.stop()
|
||||||
pool = newFreeClientPool(db, connLimit, 10000, &clock)
|
pool = newFreeClientPool(db, 1, 10000, &clock, disconnFn)
|
||||||
|
pool.setLimits(connLimit, uint64(connLimit))
|
||||||
|
|
||||||
// try connecting all known peers (connLimit should be filled up)
|
// try connecting all known peers (connLimit should be filled up)
|
||||||
for i := 0; i < clientCount; i++ {
|
for i := 0; i < clientCount; i++ {
|
||||||
pool.connect(peerId(i), func() {})
|
pool.connect(peerAddress(i), peerId(i))
|
||||||
}
|
}
|
||||||
// expect pool to remember known nodes and kick out one of them to accept a new one
|
// expect pool to remember known nodes and kick out one of them to accept a new one
|
||||||
if !pool.connect("newPeer2", func() {}) {
|
if !pool.connect("newAddr2", "newId2") {
|
||||||
t.Errorf("Previously unknown peer rejected after restarting pool")
|
t.Errorf("Previously unknown peer rejected after restarting pool")
|
||||||
}
|
}
|
||||||
pool.stop()
|
pool.stop()
|
||||||
|
|
|
||||||
329
les/handler.go
329
les/handler.go
|
|
@ -14,7 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -22,12 +21,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -101,10 +98,10 @@ type ProtocolManager struct {
|
||||||
odr *LesOdr
|
odr *LesOdr
|
||||||
server *LesServer
|
server *LesServer
|
||||||
serverPool *serverPool
|
serverPool *serverPool
|
||||||
clientPool *freeClientPool
|
|
||||||
lesTopic discv5.Topic
|
lesTopic discv5.Topic
|
||||||
reqDist *requestDistributor
|
reqDist *requestDistributor
|
||||||
retriever *retrieveManager
|
retriever *retrieveManager
|
||||||
|
servingQueue *servingQueue
|
||||||
|
|
||||||
downloader *downloader.Downloader
|
downloader *downloader.Downloader
|
||||||
fetcher *lightFetcher
|
fetcher *lightFetcher
|
||||||
|
|
@ -165,6 +162,8 @@ func NewProtocolManager(
|
||||||
if odr != nil {
|
if odr != nil {
|
||||||
manager.retriever = odr.retriever
|
manager.retriever = odr.retriever
|
||||||
manager.reqDist = odr.retriever.dist
|
manager.reqDist = odr.retriever.dist
|
||||||
|
} else {
|
||||||
|
manager.servingQueue = newServingQueue(int64(time.Millisecond * 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ulcConfig != nil {
|
if ulcConfig != nil {
|
||||||
|
|
@ -181,7 +180,6 @@ func NewProtocolManager(
|
||||||
manager.peers.notify((*downloaderPeerNotify)(manager))
|
manager.peers.notify((*downloaderPeerNotify)(manager))
|
||||||
manager.fetcher = newLightFetcher(manager)
|
manager.fetcher = newLightFetcher(manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager, nil
|
return manager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,11 +190,9 @@ func (pm *ProtocolManager) removePeer(id string) {
|
||||||
|
|
||||||
func (pm *ProtocolManager) Start(maxPeers int) {
|
func (pm *ProtocolManager) Start(maxPeers int) {
|
||||||
pm.maxPeers = maxPeers
|
pm.maxPeers = maxPeers
|
||||||
|
|
||||||
if pm.lightSync {
|
if pm.lightSync {
|
||||||
go pm.syncer()
|
go pm.syncer()
|
||||||
} else {
|
} else {
|
||||||
pm.clientPool = newFreeClientPool(pm.chainDb, maxPeers, 10000, mclock.System{})
|
|
||||||
go func() {
|
go func() {
|
||||||
for range pm.newPeerCh {
|
for range pm.newPeerCh {
|
||||||
}
|
}
|
||||||
|
|
@ -214,8 +210,9 @@ func (pm *ProtocolManager) Stop() {
|
||||||
pm.noMorePeers <- struct{}{}
|
pm.noMorePeers <- struct{}{}
|
||||||
|
|
||||||
close(pm.quitSync) // quits syncer, fetcher
|
close(pm.quitSync) // quits syncer, fetcher
|
||||||
if pm.clientPool != nil {
|
|
||||||
pm.clientPool.stop()
|
if pm.servingQueue != nil {
|
||||||
|
pm.servingQueue.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect existing sessions.
|
// Disconnect existing sessions.
|
||||||
|
|
@ -286,17 +283,8 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if p.fcClient != nil {
|
||||||
if !pm.lightSync && !p.Peer.Info().Network.Trusted {
|
defer p.fcClient.Disconnect()
|
||||||
addr, ok := p.RemoteAddr().(*net.TCPAddr)
|
|
||||||
// test peer address is not a tcp address, don't use client pool if can not typecast
|
|
||||||
if ok {
|
|
||||||
id := addr.IP.String()
|
|
||||||
if !pm.clientPool.connect(id, func() { go pm.removePeer(p.id) }) {
|
|
||||||
return p2p.DiscTooManyPeers
|
|
||||||
}
|
|
||||||
defer pm.clientPool.disconnect(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
|
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
|
||||||
|
|
@ -309,9 +297,6 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if pm.server != nil && pm.server.fcManager != nil && p.fcClient != nil {
|
|
||||||
p.fcClient.Remove(pm.server.fcManager)
|
|
||||||
}
|
|
||||||
pm.removePeer(p.id)
|
pm.removePeer(p.id)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -329,31 +314,18 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop := make(chan struct{})
|
|
||||||
defer close(stop)
|
|
||||||
go func() {
|
|
||||||
// new block announce loop
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case announce := <-p.announceChn:
|
|
||||||
p.SendAnnounce(announce)
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// main loop. handle incoming messages.
|
// main loop. handle incoming messages.
|
||||||
for {
|
for {
|
||||||
if err := pm.handleMsg(p); err != nil {
|
if err := pm.handleMsg(p); err != nil {
|
||||||
p.Log().Debug("Light Ethereum message handling failed", "err", err)
|
p.Log().Debug("Light Ethereum message handling failed", "err", err)
|
||||||
|
if p.fcServer != nil {
|
||||||
|
p.fcServer.DumpLogs()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, SendTxV2Msg, GetTxStatusMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetHelperTrieProofsMsg}
|
|
||||||
|
|
||||||
// handleMsg is invoked whenever an inbound message is received from a remote
|
// handleMsg is invoked whenever an inbound message is received from a remote
|
||||||
// peer. The remote connection is torn down upon returning any error.
|
// peer. The remote connection is torn down upon returning any error.
|
||||||
func (pm *ProtocolManager) handleMsg(p *peer) error {
|
func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
|
|
@ -364,22 +336,31 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size)
|
p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size)
|
||||||
|
|
||||||
costs := p.fcCosts[msg.Code]
|
p.responseCount++
|
||||||
reject := func(reqCnt, maxCnt uint64) bool {
|
responseCount := p.responseCount
|
||||||
|
var (
|
||||||
|
maxCost uint64
|
||||||
|
task *servingTask
|
||||||
|
)
|
||||||
|
|
||||||
|
accept := func(reqID, reqCnt, maxCnt uint64) bool {
|
||||||
|
if reqCnt == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if p.fcClient == nil || reqCnt > maxCnt {
|
if p.fcClient == nil || reqCnt > maxCnt {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
bufValue, _ := p.fcClient.AcceptRequest()
|
maxCost = p.fcCosts.getCost(msg.Code, reqCnt)
|
||||||
cost := costs.baseCost + reqCnt*costs.reqCost
|
|
||||||
if cost > pm.server.defParams.BufLimit {
|
if accepted, bufShort, servingPriority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost); !accepted {
|
||||||
cost = pm.server.defParams.BufLimit
|
if bufShort > 0 {
|
||||||
}
|
p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
|
||||||
if cost > bufValue {
|
|
||||||
recharge := time.Duration((cost - bufValue) * 1000000 / pm.server.defParams.MinRecharge)
|
|
||||||
p.Log().Error("Request came too early", "recharge", common.PrettyDuration(recharge))
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
} else {
|
||||||
|
task = pm.servingQueue.newTask(servingPriority)
|
||||||
|
}
|
||||||
|
return task.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Size > ProtocolMaxMsgSize {
|
if msg.Size > ProtocolMaxMsgSize {
|
||||||
|
|
@ -389,6 +370,31 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
|
|
||||||
var deliverMsg *Msg
|
var deliverMsg *Msg
|
||||||
|
|
||||||
|
sendResponse := func(reqID, amount uint64, reply *reply, servingTime uint64) {
|
||||||
|
p.responseLock.Lock()
|
||||||
|
defer p.responseLock.Unlock()
|
||||||
|
|
||||||
|
var replySize uint32
|
||||||
|
if reply != nil {
|
||||||
|
replySize = reply.size()
|
||||||
|
}
|
||||||
|
var realCost uint64
|
||||||
|
if pm.server.costTracker != nil {
|
||||||
|
realCost = pm.server.costTracker.realCost(servingTime, msg.Size, replySize)
|
||||||
|
pm.server.costTracker.updateStats(msg.Code, amount, servingTime, realCost)
|
||||||
|
} else {
|
||||||
|
realCost = maxCost
|
||||||
|
}
|
||||||
|
bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
|
||||||
|
if reply != nil {
|
||||||
|
p.queueSend(func() {
|
||||||
|
if err := reply.send(bv); err != nil {
|
||||||
|
p.errCh <- err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the message depending on its contents
|
// Handle the message depending on its contents
|
||||||
switch msg.Code {
|
switch msg.Code {
|
||||||
case StatusMsg:
|
case StatusMsg:
|
||||||
|
|
@ -399,16 +405,23 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
// Block header query, collect the requested headers and reply
|
// Block header query, collect the requested headers and reply
|
||||||
case AnnounceMsg:
|
case AnnounceMsg:
|
||||||
p.Log().Trace("Received announce message")
|
p.Log().Trace("Received announce message")
|
||||||
if p.announceType == announceTypeNone {
|
|
||||||
return errResp(ErrUnexpectedResponse, "")
|
|
||||||
}
|
|
||||||
var req announceData
|
var req announceData
|
||||||
if err := msg.Decode(&req); err != nil {
|
if err := msg.Decode(&req); err != nil {
|
||||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update, size := req.Update.decode()
|
||||||
|
if p.rejectUpdate(size) {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
p.updateFlowControl(update)
|
||||||
|
|
||||||
|
if req.Hash != (common.Hash{}) {
|
||||||
|
if p.announceType == announceTypeNone {
|
||||||
|
return errResp(ErrUnexpectedResponse, "")
|
||||||
|
}
|
||||||
if p.announceType == announceTypeSigned {
|
if p.announceType == announceTypeSigned {
|
||||||
if err := req.checkSignature(p.ID()); err != nil {
|
if err := req.checkSignature(p.ID(), update); err != nil {
|
||||||
p.Log().Trace("Invalid announcement signature", "err", err)
|
p.Log().Trace("Invalid announcement signature", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -419,6 +432,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if pm.fetcher != nil {
|
if pm.fetcher != nil {
|
||||||
pm.fetcher.announce(p, &req)
|
pm.fetcher.announce(p, &req)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case GetBlockHeadersMsg:
|
case GetBlockHeadersMsg:
|
||||||
p.Log().Trace("Received block header request")
|
p.Log().Trace("Received block header request")
|
||||||
|
|
@ -432,10 +446,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
query := req.Query
|
query := req.Query
|
||||||
if reject(query.Amount, MaxHeaderFetch) {
|
if !accept(req.ReqID, query.Amount, MaxHeaderFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
hashMode := query.Origin.Hash != (common.Hash{})
|
hashMode := query.Origin.Hash != (common.Hash{})
|
||||||
first := true
|
first := true
|
||||||
maxNonCanonical := uint64(100)
|
maxNonCanonical := uint64(100)
|
||||||
|
|
@ -447,11 +461,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
unknown bool
|
unknown bool
|
||||||
)
|
)
|
||||||
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit {
|
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit {
|
||||||
|
if !first && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Retrieve the next header satisfying the query
|
// Retrieve the next header satisfying the query
|
||||||
var origin *types.Header
|
var origin *types.Header
|
||||||
if hashMode {
|
if hashMode {
|
||||||
if first {
|
if first {
|
||||||
first = false
|
|
||||||
origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash)
|
origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash)
|
||||||
if origin != nil {
|
if origin != nil {
|
||||||
query.Origin.Number = origin.Number.Uint64()
|
query.Origin.Number = origin.Number.Uint64()
|
||||||
|
|
@ -514,11 +530,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
// Number based traversal towards the leaf block
|
// Number based traversal towards the leaf block
|
||||||
query.Origin.Number += query.Skip + 1
|
query.Origin.Number += query.Skip + 1
|
||||||
}
|
}
|
||||||
|
first = false
|
||||||
}
|
}
|
||||||
|
sendResponse(req.ReqID, query.Amount, p.ReplyBlockHeaders(req.ReqID, headers), task.done())
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + query.Amount*costs.reqCost)
|
}()
|
||||||
pm.server.fcCostStats.update(msg.Code, query.Amount, rcost)
|
|
||||||
return p.SendBlockHeaders(req.ReqID, bv, headers)
|
|
||||||
|
|
||||||
case BlockHeadersMsg:
|
case BlockHeadersMsg:
|
||||||
if pm.downloader == nil {
|
if pm.downloader == nil {
|
||||||
|
|
@ -534,7 +549,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
if pm.fetcher != nil && pm.fetcher.requestedID(resp.ReqID) {
|
if pm.fetcher != nil && pm.fetcher.requestedID(resp.ReqID) {
|
||||||
pm.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
|
pm.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -560,10 +575,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
bodies []rlp.RawValue
|
bodies []rlp.RawValue
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Hashes)
|
reqCnt := len(req.Hashes)
|
||||||
if reject(uint64(reqCnt), MaxBodyFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxBodyFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
for _, hash := range req.Hashes {
|
go func() {
|
||||||
|
for i, hash := range req.Hashes {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if bytes >= softResponseLimit {
|
if bytes >= softResponseLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -575,9 +594,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyBlockBodiesRLP(req.ReqID, bodies), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendBlockBodiesRLP(req.ReqID, bv, bodies)
|
|
||||||
|
|
||||||
case BlockBodiesMsg:
|
case BlockBodiesMsg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
|
|
@ -593,7 +611,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgBlockBodies,
|
MsgType: MsgBlockBodies,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -616,10 +634,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
data [][]byte
|
data [][]byte
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Reqs)
|
reqCnt := len(req.Reqs)
|
||||||
if reject(uint64(reqCnt), MaxCodeFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxCodeFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
for _, req := range req.Reqs {
|
go func() {
|
||||||
|
for i, req := range req.Reqs {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Retrieve the requested state entry, stopping if enough was found
|
// Retrieve the requested state entry, stopping if enough was found
|
||||||
if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil {
|
if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil {
|
||||||
if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil {
|
if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil {
|
||||||
|
|
@ -640,9 +662,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyCode(req.ReqID, data), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendCode(req.ReqID, bv, data)
|
|
||||||
|
|
||||||
case CodeMsg:
|
case CodeMsg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
|
|
@ -658,7 +679,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgCode,
|
MsgType: MsgCode,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -681,10 +702,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
receipts []rlp.RawValue
|
receipts []rlp.RawValue
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Hashes)
|
reqCnt := len(req.Hashes)
|
||||||
if reject(uint64(reqCnt), MaxReceiptFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxReceiptFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
for _, hash := range req.Hashes {
|
go func() {
|
||||||
|
for i, hash := range req.Hashes {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if bytes >= softResponseLimit {
|
if bytes >= softResponseLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -706,9 +731,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
bytes += len(encoded)
|
bytes += len(encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyReceiptsRLP(req.ReqID, receipts), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendReceiptsRLP(req.ReqID, bv, receipts)
|
|
||||||
|
|
||||||
case ReceiptsMsg:
|
case ReceiptsMsg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
|
|
@ -724,7 +748,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgReceipts,
|
MsgType: MsgReceipts,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -747,10 +771,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
proofs proofsData
|
proofs proofsData
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Reqs)
|
reqCnt := len(req.Reqs)
|
||||||
if reject(uint64(reqCnt), MaxProofsFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
for _, req := range req.Reqs {
|
go func() {
|
||||||
|
for i, req := range req.Reqs {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Retrieve the requested state entry, stopping if enough was found
|
// Retrieve the requested state entry, stopping if enough was found
|
||||||
if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil {
|
if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil {
|
||||||
if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil {
|
if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil {
|
||||||
|
|
@ -780,9 +808,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyProofs(req.ReqID, proofs), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendProofs(req.ReqID, bv, proofs)
|
|
||||||
|
|
||||||
case GetProofsV2Msg:
|
case GetProofsV2Msg:
|
||||||
p.Log().Trace("Received les/2 proofs request")
|
p.Log().Trace("Received les/2 proofs request")
|
||||||
|
|
@ -801,13 +828,17 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
root common.Hash
|
root common.Hash
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Reqs)
|
reqCnt := len(req.Reqs)
|
||||||
if reject(uint64(reqCnt), MaxProofsFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
|
||||||
nodes := light.NewNodeSet()
|
nodes := light.NewNodeSet()
|
||||||
|
|
||||||
for _, req := range req.Reqs {
|
for i, req := range req.Reqs {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Look up the state belonging to the request
|
// Look up the state belonging to the request
|
||||||
if statedb == nil || req.BHash != lastBHash {
|
if statedb == nil || req.BHash != lastBHash {
|
||||||
statedb, root, lastBHash = nil, common.Hash{}, req.BHash
|
statedb, root, lastBHash = nil, common.Hash{}, req.BHash
|
||||||
|
|
@ -842,9 +873,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyProofsV2(req.ReqID, nodes.NodeList()), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendProofsV2(req.ReqID, bv, nodes.NodeList())
|
|
||||||
|
|
||||||
case ProofsV1Msg:
|
case ProofsV1Msg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
|
|
@ -860,7 +890,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgProofsV1,
|
MsgType: MsgProofsV1,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -881,7 +911,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgProofsV2,
|
MsgType: MsgProofsV2,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -904,11 +934,15 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
proofs []ChtResp
|
proofs []ChtResp
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Reqs)
|
reqCnt := len(req.Reqs)
|
||||||
if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
trieDb := trie.NewDatabase(ethdb.NewTable(pm.chainDb, light.ChtTablePrefix))
|
trieDb := trie.NewDatabase(ethdb.NewTable(pm.chainDb, light.ChtTablePrefix))
|
||||||
for _, req := range req.Reqs {
|
for i, req := range req.Reqs {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil {
|
if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil {
|
||||||
sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, req.ChtNum*pm.iConfig.ChtSize-1)
|
sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, req.ChtNum*pm.iConfig.ChtSize-1)
|
||||||
if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) {
|
if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) {
|
||||||
|
|
@ -929,9 +963,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyHeaderProofs(req.ReqID, proofs), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendHeaderProofs(req.ReqID, bv, proofs)
|
|
||||||
|
|
||||||
case GetHelperTrieProofsMsg:
|
case GetHelperTrieProofsMsg:
|
||||||
p.Log().Trace("Received helper trie proof request")
|
p.Log().Trace("Received helper trie proof request")
|
||||||
|
|
@ -949,9 +982,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
auxData [][]byte
|
auxData [][]byte
|
||||||
)
|
)
|
||||||
reqCnt := len(req.Reqs)
|
reqCnt := len(req.Reqs)
|
||||||
if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lastIdx uint64
|
lastIdx uint64
|
||||||
|
|
@ -960,7 +994,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
auxTrie *trie.Trie
|
auxTrie *trie.Trie
|
||||||
)
|
)
|
||||||
nodes := light.NewNodeSet()
|
nodes := light.NewNodeSet()
|
||||||
for _, req := range req.Reqs {
|
for i, req := range req.Reqs {
|
||||||
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if auxTrie == nil || req.Type != lastType || req.TrieIdx != lastIdx {
|
if auxTrie == nil || req.Type != lastType || req.TrieIdx != lastIdx {
|
||||||
auxTrie, lastType, lastIdx = nil, req.Type, req.TrieIdx
|
auxTrie, lastType, lastIdx = nil, req.Type, req.TrieIdx
|
||||||
|
|
||||||
|
|
@ -990,9 +1027,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}), task.done())
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
}()
|
||||||
return p.SendHelperTrieProofs(req.ReqID, bv, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData})
|
|
||||||
|
|
||||||
case HeaderProofsMsg:
|
case HeaderProofsMsg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
|
|
@ -1007,7 +1043,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&resp); err != nil {
|
if err := msg.Decode(&resp); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgHeaderProofs,
|
MsgType: MsgHeaderProofs,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -1028,7 +1064,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
deliverMsg = &Msg{
|
deliverMsg = &Msg{
|
||||||
MsgType: MsgHelperTrieProofs,
|
MsgType: MsgHelperTrieProofs,
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
|
|
@ -1045,13 +1081,18 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
reqCnt := len(txs)
|
reqCnt := len(txs)
|
||||||
if reject(uint64(reqCnt), MaxTxSend) {
|
if !accept(0, uint64(reqCnt), MaxTxSend) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
pm.txpool.AddRemotes(txs)
|
go func() {
|
||||||
|
for i, tx := range txs {
|
||||||
_, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
if i != 0 && !task.waitOrStop() {
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
return
|
||||||
|
}
|
||||||
|
pm.txpool.AddRemotes([]*types.Transaction{tx})
|
||||||
|
}
|
||||||
|
sendResponse(0, uint64(reqCnt), nil, task.done())
|
||||||
|
}()
|
||||||
|
|
||||||
case SendTxV2Msg:
|
case SendTxV2Msg:
|
||||||
if pm.txpool == nil {
|
if pm.txpool == nil {
|
||||||
|
|
@ -1066,29 +1107,27 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
reqCnt := len(req.Txs)
|
reqCnt := len(req.Txs)
|
||||||
if reject(uint64(reqCnt), MaxTxSend) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxTxSend) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
hashes := make([]common.Hash, len(req.Txs))
|
stats := make([]txStatus, len(req.Txs))
|
||||||
for i, tx := range req.Txs {
|
for i, tx := range req.Txs {
|
||||||
hashes[i] = tx.Hash()
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
stats := pm.txStatus(hashes)
|
hash := tx.Hash()
|
||||||
for i, stat := range stats {
|
stats[i] = pm.txStatus(hash)
|
||||||
if stat.Status == core.TxStatusUnknown {
|
if stats[i].Status == core.TxStatusUnknown {
|
||||||
if errs := pm.txpool.AddRemotes([]*types.Transaction{req.Txs[i]}); errs[0] != nil {
|
if errs := pm.txpool.AddRemotes([]*types.Transaction{tx}); errs[0] != nil {
|
||||||
stats[i].Error = errs[0].Error()
|
stats[i].Error = errs[0].Error()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stats[i] = pm.txStatus([]common.Hash{hashes[i]})[0]
|
stats[i] = pm.txStatus(hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), task.done())
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
}()
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
|
||||||
|
|
||||||
return p.SendTxStatus(req.ReqID, bv, stats)
|
|
||||||
|
|
||||||
case GetTxStatusMsg:
|
case GetTxStatusMsg:
|
||||||
if pm.txpool == nil {
|
if pm.txpool == nil {
|
||||||
|
|
@ -1103,13 +1142,19 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
reqCnt := len(req.Hashes)
|
reqCnt := len(req.Hashes)
|
||||||
if reject(uint64(reqCnt), MaxTxStatus) {
|
if !accept(req.ReqID, uint64(reqCnt), MaxTxStatus) {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
|
go func() {
|
||||||
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
|
stats := make([]txStatus, len(req.Hashes))
|
||||||
|
for i, hash := range req.Hashes {
|
||||||
return p.SendTxStatus(req.ReqID, bv, pm.txStatus(req.Hashes))
|
if i != 0 && !task.waitOrStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stats[i] = pm.txStatus(hash)
|
||||||
|
}
|
||||||
|
sendResponse(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), task.done())
|
||||||
|
}()
|
||||||
|
|
||||||
case TxStatusMsg:
|
case TxStatusMsg:
|
||||||
if pm.odr == nil {
|
if pm.odr == nil {
|
||||||
|
|
@ -1125,7 +1170,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.fcServer.GotReply(resp.ReqID, resp.BV)
|
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
p.Log().Trace("Received unknown message", "code", msg.Code)
|
p.Log().Trace("Received unknown message", "code", msg.Code)
|
||||||
|
|
@ -1185,21 +1230,17 @@ func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus {
|
func (pm *ProtocolManager) txStatus(hash common.Hash) txStatus {
|
||||||
stats := make([]txStatus, len(hashes))
|
var stat txStatus
|
||||||
for i, stat := range pm.txpool.Status(hashes) {
|
stat.Status = pm.txpool.Status([]common.Hash{hash})[0]
|
||||||
// Save the status we've got from the transaction pool
|
|
||||||
stats[i].Status = stat
|
|
||||||
|
|
||||||
// If the transaction is unknown to the pool, try looking it up locally
|
// If the transaction is unknown to the pool, try looking it up locally
|
||||||
if stat == core.TxStatusUnknown {
|
if stat.Status == core.TxStatusUnknown {
|
||||||
if tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(pm.chainDb, hashes[i]); tx != nil {
|
if tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(pm.chainDb, hash); tx != nil {
|
||||||
stats[i].Status = core.TxStatusIncluded
|
stat.Status = core.TxStatusIncluded
|
||||||
stats[i].Lookup = &rawdb.LegacyTxLookupEntry{BlockHash: blockHash, BlockIndex: blockNumber, Index: txIndex}
|
stat.Lookup = &rawdb.LegacyTxLookupEntry{BlockHash: blockHash, BlockIndex: blockNumber, Index: txIndex}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return stat
|
||||||
return stats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isULCEnabled returns true if we can use ULC
|
// isULCEnabled returns true if we can use ULC
|
||||||
|
|
@ -1235,7 +1276,7 @@ func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, s
|
||||||
request: func(dp distPeer) func() {
|
request: func(dp distPeer) func() {
|
||||||
peer := dp.(*peer)
|
peer := dp.(*peer)
|
||||||
cost := peer.GetRequestCost(GetBlockHeadersMsg, amount)
|
cost := peer.GetRequestCost(GetBlockHeadersMsg, amount)
|
||||||
peer.fcServer.QueueRequest(reqID, cost)
|
peer.fcServer.QueuedRequest(reqID, cost)
|
||||||
return func() { peer.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse) }
|
return func() { peer.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse) }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -1259,7 +1300,7 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip
|
||||||
request: func(dp distPeer) func() {
|
request: func(dp distPeer) func() {
|
||||||
peer := dp.(*peer)
|
peer := dp.(*peer)
|
||||||
cost := peer.GetRequestCost(GetBlockHeadersMsg, amount)
|
cost := peer.GetRequestCost(GetBlockHeadersMsg, amount)
|
||||||
peer.fcServer.QueueRequest(reqID, cost)
|
peer.fcServer.QueuedRequest(reqID, cost)
|
||||||
return func() { peer.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse) }
|
return func() { peer.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse) }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -133,16 +134,6 @@ func testIndexers(db ethdb.Database, odr light.OdrBackend, iConfig *light.Indexe
|
||||||
return chtIndexer, bloomIndexer, bloomTrieIndexer
|
return chtIndexer, bloomIndexer, bloomTrieIndexer
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRCL() RequestCostList {
|
|
||||||
cl := make(RequestCostList, len(reqList))
|
|
||||||
for i, code := range reqList {
|
|
||||||
cl[i].MsgCode = code
|
|
||||||
cl[i].BaseCost = 0
|
|
||||||
cl[i].ReqCost = 0
|
|
||||||
}
|
|
||||||
return cl
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTestProtocolManager creates a new protocol manager for testing purposes,
|
// newTestProtocolManager creates a new protocol manager for testing purposes,
|
||||||
// with the given number of blocks already known, potential notification
|
// with the given number of blocks already known, potential notification
|
||||||
// channels for different events and relative chain indexers array.
|
// channels for different events and relative chain indexers array.
|
||||||
|
|
@ -183,14 +174,14 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
||||||
if !lightSync {
|
if !lightSync {
|
||||||
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm}}
|
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm}}
|
||||||
pm.server = srv
|
pm.server = srv
|
||||||
|
pm.servingQueue.setThreads(4)
|
||||||
|
|
||||||
srv.defParams = &flowcontrol.ServerParams{
|
srv.defParams = flowcontrol.ServerParams{
|
||||||
BufLimit: testBufLimit,
|
BufLimit: testBufLimit,
|
||||||
MinRecharge: 1,
|
MinRecharge: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.fcManager = flowcontrol.NewClientManager(50, 10, 1000000000)
|
srv.fcManager = flowcontrol.NewClientManager(nil, &mclock.System{})
|
||||||
srv.fcCostStats = newCostStats(nil)
|
|
||||||
}
|
}
|
||||||
pm.Start(1000)
|
pm.Start(1000)
|
||||||
return pm, nil
|
return pm, nil
|
||||||
|
|
@ -304,7 +295,7 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
|
||||||
expList = expList.add("txRelay", nil)
|
expList = expList.add("txRelay", nil)
|
||||||
expList = expList.add("flowControl/BL", testBufLimit)
|
expList = expList.add("flowControl/BL", testBufLimit)
|
||||||
expList = expList.add("flowControl/MRR", uint64(1))
|
expList = expList.add("flowControl/MRR", uint64(1))
|
||||||
expList = expList.add("flowControl/MRC", testRCL())
|
expList = expList.add("flowControl/MRC", testCostList())
|
||||||
|
|
||||||
if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil {
|
if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil {
|
||||||
t.Fatalf("status recv: %v", err)
|
t.Fatalf("status recv: %v", err)
|
||||||
|
|
@ -313,7 +304,7 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
|
||||||
t.Fatalf("status send: %v", err)
|
t.Fatalf("status send: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.fcServerParams = &flowcontrol.ServerParams{
|
p.fcParams = flowcontrol.ServerParams{
|
||||||
BufLimit: testBufLimit,
|
BufLimit: testBufLimit,
|
||||||
MinRecharge: 1,
|
MinRecharge: 1,
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +366,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers fun
|
||||||
db, ldb := ethdb.NewMemDatabase(), ethdb.NewMemDatabase()
|
db, ldb := ethdb.NewMemDatabase(), ethdb.NewMemDatabase()
|
||||||
peers, lPeers := newPeerSet(), newPeerSet()
|
peers, lPeers := newPeerSet(), newPeerSet()
|
||||||
|
|
||||||
dist := newRequestDistributor(lPeers, make(chan struct{}))
|
dist := newRequestDistributor(lPeers, make(chan struct{}), &mclock.System{})
|
||||||
rm := newRetrieveManager(lPeers, dist, nil)
|
rm := newRetrieveManager(lPeers, dist, nil)
|
||||||
odr := NewLesOdr(ldb, light.TestClientIndexerConfig, rm)
|
odr := NewLesOdr(ldb, light.TestClientIndexerConfig, rm)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro
|
||||||
request: func(dp distPeer) func() {
|
request: func(dp distPeer) func() {
|
||||||
p := dp.(*peer)
|
p := dp.(*peer)
|
||||||
cost := lreq.GetCost(p)
|
cost := lreq.GetCost(p)
|
||||||
p.fcServer.QueueRequest(reqID, cost)
|
p.fcServer.QueuedRequest(reqID, cost)
|
||||||
return func() { lreq.Request(reqID, p) }
|
return func() { lreq.Request(reqID, p) }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
236
les/peer.go
236
les/peer.go
|
|
@ -14,7 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -25,6 +24,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
|
|
@ -42,6 +42,17 @@ var (
|
||||||
|
|
||||||
const maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
|
const maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
|
||||||
|
|
||||||
|
// capacity limitation for parameter updates
|
||||||
|
const (
|
||||||
|
allowedUpdateBytes = 100000 // initial/maximum allowed update size
|
||||||
|
allowedUpdateRate = time.Millisecond * 10 // time constant for recharging one byte of allowance
|
||||||
|
)
|
||||||
|
|
||||||
|
// if the total encoded size of a sent transaction batch is over txSizeCostLimit
|
||||||
|
// per transaction then the request cost is calculated as proportional to the
|
||||||
|
// encoded size instead of the transaction count
|
||||||
|
const txSizeCostLimit = 0x10000
|
||||||
|
|
||||||
const (
|
const (
|
||||||
announceTypeNone = iota
|
announceTypeNone = iota
|
||||||
announceTypeSimple
|
announceTypeSimple
|
||||||
|
|
@ -63,16 +74,23 @@ type peer struct {
|
||||||
headInfo *announceData
|
headInfo *announceData
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
announceChn chan announceData
|
|
||||||
sendQueue *execQueue
|
sendQueue *execQueue
|
||||||
|
|
||||||
|
errCh chan error
|
||||||
|
// responseLock ensures that responses are queued in the same order as
|
||||||
|
// RequestProcessed is called
|
||||||
|
responseLock sync.Mutex
|
||||||
|
responseCount uint64
|
||||||
|
|
||||||
poolEntry *poolEntry
|
poolEntry *poolEntry
|
||||||
hasBlock func(common.Hash, uint64, bool) bool
|
hasBlock func(common.Hash, uint64, bool) bool
|
||||||
responseErrors int
|
responseErrors int
|
||||||
|
updateCounter uint64
|
||||||
|
updateTime mclock.AbsTime
|
||||||
|
|
||||||
fcClient *flowcontrol.ClientNode // nil if the peer is server only
|
fcClient *flowcontrol.ClientNode // nil if the peer is server only
|
||||||
fcServer *flowcontrol.ServerNode // nil if the peer is client only
|
fcServer *flowcontrol.ServerNode // nil if the peer is client only
|
||||||
fcServerParams *flowcontrol.ServerParams
|
fcParams flowcontrol.ServerParams
|
||||||
fcCosts requestCostTable
|
fcCosts requestCostTable
|
||||||
|
|
||||||
isTrusted bool
|
isTrusted bool
|
||||||
|
|
@ -87,12 +105,32 @@ func newPeer(version int, network uint64, isTrusted bool, p *p2p.Peer, rw p2p.Ms
|
||||||
rw: rw,
|
rw: rw,
|
||||||
version: version,
|
version: version,
|
||||||
network: network,
|
network: network,
|
||||||
id: fmt.Sprintf("%x", id[:8]),
|
id: fmt.Sprintf("%x", id),
|
||||||
announceChn: make(chan announceData, 20),
|
|
||||||
isTrusted: isTrusted,
|
isTrusted: isTrusted,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rejectUpdate returns true if a parameter update has to be rejected because
|
||||||
|
// the size and/or rate of updates exceed the capacity limitation
|
||||||
|
func (p *peer) rejectUpdate(size uint64) bool {
|
||||||
|
now := mclock.Now()
|
||||||
|
if p.updateCounter == 0 {
|
||||||
|
p.updateTime = now
|
||||||
|
} else {
|
||||||
|
dt := now - p.updateTime
|
||||||
|
r := uint64(dt / mclock.AbsTime(allowedUpdateRate))
|
||||||
|
if p.updateCounter > r {
|
||||||
|
p.updateCounter -= r
|
||||||
|
p.updateTime += mclock.AbsTime(allowedUpdateRate * time.Duration(r))
|
||||||
|
} else {
|
||||||
|
p.updateCounter = 0
|
||||||
|
p.updateTime = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.updateCounter += size
|
||||||
|
return p.updateCounter > allowedUpdateBytes
|
||||||
|
}
|
||||||
|
|
||||||
func (p *peer) canQueue() bool {
|
func (p *peer) canQueue() bool {
|
||||||
return p.sendQueue.canQueue()
|
return p.sendQueue.canQueue()
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +185,20 @@ func (p *peer) waitBefore(maxCost uint64) (time.Duration, float64) {
|
||||||
return p.fcServer.CanSend(maxCost)
|
return p.fcServer.CanSend(maxCost)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateCapacity updates the request serving capacity assigned to a given client
|
||||||
|
// and also sends an announcement about the updated flow control parameters
|
||||||
|
func (p *peer) updateCapacity(cap uint64) {
|
||||||
|
p.responseLock.Lock()
|
||||||
|
defer p.responseLock.Unlock()
|
||||||
|
|
||||||
|
p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio}
|
||||||
|
p.fcClient.UpdateParams(p.fcParams)
|
||||||
|
var kvList keyValueList
|
||||||
|
kvList = kvList.add("flowControl/MRR", cap)
|
||||||
|
kvList = kvList.add("flowControl/BL", cap*bufLimitRatio)
|
||||||
|
p.queueSend(func() { p.SendAnnounce(announceData{Update: kvList}) })
|
||||||
|
}
|
||||||
|
|
||||||
func sendRequest(w p2p.MsgWriter, msgcode, reqID, cost uint64, data interface{}) error {
|
func sendRequest(w p2p.MsgWriter, msgcode, reqID, cost uint64, data interface{}) error {
|
||||||
type req struct {
|
type req struct {
|
||||||
ReqID uint64
|
ReqID uint64
|
||||||
|
|
@ -155,12 +207,27 @@ func sendRequest(w p2p.MsgWriter, msgcode, reqID, cost uint64, data interface{})
|
||||||
return p2p.Send(w, msgcode, req{reqID, data})
|
return p2p.Send(w, msgcode, req{reqID, data})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendResponse(w p2p.MsgWriter, msgcode, reqID, bv uint64, data interface{}) error {
|
// reply struct represents a reply with the actual data already RLP encoded and
|
||||||
|
// only the bv (buffer value) missing. This allows the serving mechanism to
|
||||||
|
// calculate the bv value which depends on the data size before sending the reply.
|
||||||
|
type reply struct {
|
||||||
|
w p2p.MsgWriter
|
||||||
|
msgcode, reqID uint64
|
||||||
|
data rlp.RawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// send sends the reply with the calculated buffer value
|
||||||
|
func (r *reply) send(bv uint64) error {
|
||||||
type resp struct {
|
type resp struct {
|
||||||
ReqID, BV uint64
|
ReqID, BV uint64
|
||||||
Data interface{}
|
Data rlp.RawValue
|
||||||
}
|
}
|
||||||
return p2p.Send(w, msgcode, resp{reqID, bv, data})
|
return p2p.Send(r.w, r.msgcode, resp{r.reqID, bv, r.data})
|
||||||
|
}
|
||||||
|
|
||||||
|
// size returns the RLP encoded size of the message data
|
||||||
|
func (r *reply) size() uint32 {
|
||||||
|
return uint32(len(r.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
|
func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
|
||||||
|
|
@ -168,8 +235,34 @@ func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
|
||||||
defer p.lock.RUnlock()
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount)
|
cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount)
|
||||||
if cost > p.fcServerParams.BufLimit {
|
if cost > p.fcParams.BufLimit {
|
||||||
cost = p.fcServerParams.BufLimit
|
cost = p.fcParams.BufLimit
|
||||||
|
}
|
||||||
|
return cost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peer) GetTxRelayCost(amount, size int) uint64 {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
var msgcode uint64
|
||||||
|
switch p.version {
|
||||||
|
case lpv1:
|
||||||
|
msgcode = SendTxMsg
|
||||||
|
case lpv2:
|
||||||
|
msgcode = SendTxV2Msg
|
||||||
|
default:
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount)
|
||||||
|
sizeCost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(size)/txSizeCostLimit
|
||||||
|
if sizeCost > cost {
|
||||||
|
cost = sizeCost
|
||||||
|
}
|
||||||
|
|
||||||
|
if cost > p.fcParams.BufLimit {
|
||||||
|
cost = p.fcParams.BufLimit
|
||||||
}
|
}
|
||||||
return cost
|
return cost
|
||||||
}
|
}
|
||||||
|
|
@ -188,52 +281,61 @@ func (p *peer) SendAnnounce(request announceData) error {
|
||||||
return p2p.Send(p.rw, AnnounceMsg, request)
|
return p2p.Send(p.rw, AnnounceMsg, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendBlockHeaders sends a batch of block headers to the remote peer.
|
// ReplyBlockHeaders creates a reply with a batch of block headers
|
||||||
func (p *peer) SendBlockHeaders(reqID, bv uint64, headers []*types.Header) error {
|
func (p *peer) ReplyBlockHeaders(reqID uint64, headers []*types.Header) *reply {
|
||||||
return sendResponse(p.rw, BlockHeadersMsg, reqID, bv, headers)
|
data, _ := rlp.EncodeToBytes(headers)
|
||||||
|
return &reply{p.rw, BlockHeadersMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
|
// ReplyBlockBodiesRLP creates a reply with a batch of block contents from
|
||||||
// an already RLP encoded format.
|
// an already RLP encoded format.
|
||||||
func (p *peer) SendBlockBodiesRLP(reqID, bv uint64, bodies []rlp.RawValue) error {
|
func (p *peer) ReplyBlockBodiesRLP(reqID uint64, bodies []rlp.RawValue) *reply {
|
||||||
return sendResponse(p.rw, BlockBodiesMsg, reqID, bv, bodies)
|
data, _ := rlp.EncodeToBytes(bodies)
|
||||||
|
return &reply{p.rw, BlockBodiesMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendCodeRLP sends a batch of arbitrary internal data, corresponding to the
|
// ReplyCode creates a reply with a batch of arbitrary internal data, corresponding to the
|
||||||
// hashes requested.
|
// hashes requested.
|
||||||
func (p *peer) SendCode(reqID, bv uint64, data [][]byte) error {
|
func (p *peer) ReplyCode(reqID uint64, codes [][]byte) *reply {
|
||||||
return sendResponse(p.rw, CodeMsg, reqID, bv, data)
|
data, _ := rlp.EncodeToBytes(codes)
|
||||||
|
return &reply{p.rw, CodeMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
|
// ReplyReceiptsRLP creates a reply with a batch of transaction receipts, corresponding to the
|
||||||
// ones requested from an already RLP encoded format.
|
// ones requested from an already RLP encoded format.
|
||||||
func (p *peer) SendReceiptsRLP(reqID, bv uint64, receipts []rlp.RawValue) error {
|
func (p *peer) ReplyReceiptsRLP(reqID uint64, receipts []rlp.RawValue) *reply {
|
||||||
return sendResponse(p.rw, ReceiptsMsg, reqID, bv, receipts)
|
data, _ := rlp.EncodeToBytes(receipts)
|
||||||
|
return &reply{p.rw, ReceiptsMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendProofs sends a batch of legacy LES/1 merkle proofs, corresponding to the ones requested.
|
// ReplyProofs creates a reply with a batch of legacy LES/1 merkle proofs, corresponding to the ones requested.
|
||||||
func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error {
|
func (p *peer) ReplyProofs(reqID uint64, proofs proofsData) *reply {
|
||||||
return sendResponse(p.rw, ProofsV1Msg, reqID, bv, proofs)
|
data, _ := rlp.EncodeToBytes(proofs)
|
||||||
|
return &reply{p.rw, ProofsV1Msg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendProofsV2 sends a batch of merkle proofs, corresponding to the ones requested.
|
// ReplyProofsV2 creates a reply with a batch of merkle proofs, corresponding to the ones requested.
|
||||||
func (p *peer) SendProofsV2(reqID, bv uint64, proofs light.NodeList) error {
|
func (p *peer) ReplyProofsV2(reqID uint64, proofs light.NodeList) *reply {
|
||||||
return sendResponse(p.rw, ProofsV2Msg, reqID, bv, proofs)
|
data, _ := rlp.EncodeToBytes(proofs)
|
||||||
|
return &reply{p.rw, ProofsV2Msg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendHeaderProofs sends a batch of legacy LES/1 header proofs, corresponding to the ones requested.
|
// ReplyHeaderProofs creates a reply with a batch of legacy LES/1 header proofs, corresponding to the ones requested.
|
||||||
func (p *peer) SendHeaderProofs(reqID, bv uint64, proofs []ChtResp) error {
|
func (p *peer) ReplyHeaderProofs(reqID uint64, proofs []ChtResp) *reply {
|
||||||
return sendResponse(p.rw, HeaderProofsMsg, reqID, bv, proofs)
|
data, _ := rlp.EncodeToBytes(proofs)
|
||||||
|
return &reply{p.rw, HeaderProofsMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendHelperTrieProofs sends a batch of HelperTrie proofs, corresponding to the ones requested.
|
// ReplyHelperTrieProofs creates a reply with a batch of HelperTrie proofs, corresponding to the ones requested.
|
||||||
func (p *peer) SendHelperTrieProofs(reqID, bv uint64, resp HelperTrieResps) error {
|
func (p *peer) ReplyHelperTrieProofs(reqID uint64, resp HelperTrieResps) *reply {
|
||||||
return sendResponse(p.rw, HelperTrieProofsMsg, reqID, bv, resp)
|
data, _ := rlp.EncodeToBytes(resp)
|
||||||
|
return &reply{p.rw, HelperTrieProofsMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTxStatus sends a batch of transaction status records, corresponding to the ones requested.
|
// ReplyTxStatus creates a reply with a batch of transaction status records, corresponding to the ones requested.
|
||||||
func (p *peer) SendTxStatus(reqID, bv uint64, stats []txStatus) error {
|
func (p *peer) ReplyTxStatus(reqID uint64, stats []txStatus) *reply {
|
||||||
return sendResponse(p.rw, TxStatusMsg, reqID, bv, stats)
|
data, _ := rlp.EncodeToBytes(stats)
|
||||||
|
return &reply{p.rw, TxStatusMsg, reqID, data}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
|
// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
|
||||||
|
|
@ -311,9 +413,9 @@ func (p *peer) RequestTxStatus(reqID, cost uint64, txHashes []common.Hash) error
|
||||||
return sendRequest(p.rw, GetTxStatusMsg, reqID, cost, txHashes)
|
return sendRequest(p.rw, GetTxStatusMsg, reqID, cost, txHashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTxStatus sends a batch of transactions to be added to the remote transaction pool.
|
// SendTxStatus creates a reply with a batch of transactions to be added to the remote transaction pool.
|
||||||
func (p *peer) SendTxs(reqID, cost uint64, txs types.Transactions) error {
|
func (p *peer) SendTxs(reqID, cost uint64, txs rlp.RawValue) error {
|
||||||
p.Log().Debug("Fetching batch of transactions", "count", len(txs))
|
p.Log().Debug("Sending batch of transactions", "size", len(txs))
|
||||||
switch p.version {
|
switch p.version {
|
||||||
case lpv1:
|
case lpv1:
|
||||||
return p2p.Send(p.rw, SendTxMsg, txs) // old message format does not include reqID
|
return p2p.Send(p.rw, SendTxMsg, txs) // old message format does not include reqID
|
||||||
|
|
@ -344,12 +446,14 @@ func (l keyValueList) add(key string, val interface{}) keyValueList {
|
||||||
return append(l, entry)
|
return append(l, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l keyValueList) decode() keyValueMap {
|
func (l keyValueList) decode() (keyValueMap, uint64) {
|
||||||
m := make(keyValueMap)
|
m := make(keyValueMap)
|
||||||
|
var size uint64
|
||||||
for _, entry := range l {
|
for _, entry := range l {
|
||||||
m[entry.Key] = entry.Value
|
m[entry.Key] = entry.Value
|
||||||
|
size += uint64(len(entry.Key)) + uint64(len(entry.Value)) + 8
|
||||||
}
|
}
|
||||||
return m
|
return m, size
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m keyValueMap) get(key string, val interface{}) error {
|
func (m keyValueMap) get(key string, val interface{}) error {
|
||||||
|
|
@ -414,9 +518,15 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
}
|
}
|
||||||
send = send.add("flowControl/BL", server.defParams.BufLimit)
|
send = send.add("flowControl/BL", server.defParams.BufLimit)
|
||||||
send = send.add("flowControl/MRR", server.defParams.MinRecharge)
|
send = send.add("flowControl/MRR", server.defParams.MinRecharge)
|
||||||
list := server.fcCostStats.getCurrentList()
|
var costList RequestCostList
|
||||||
send = send.add("flowControl/MRC", list)
|
if server.costTracker != nil {
|
||||||
p.fcCosts = list.decode()
|
costList = server.costTracker.makeCostList()
|
||||||
|
} else {
|
||||||
|
costList = testCostList()
|
||||||
|
}
|
||||||
|
send = send.add("flowControl/MRC", costList)
|
||||||
|
p.fcCosts = costList.decode()
|
||||||
|
p.fcParams = server.defParams
|
||||||
} else {
|
} else {
|
||||||
//on client node
|
//on client node
|
||||||
p.announceType = announceTypeSimple
|
p.announceType = announceTypeSimple
|
||||||
|
|
@ -430,8 +540,10 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
recv, size := recvList.decode()
|
||||||
recv := recvList.decode()
|
if p.rejectUpdate(size) {
|
||||||
|
return errResp(ErrRequestRejected, "")
|
||||||
|
}
|
||||||
|
|
||||||
var rGenesis, rHash common.Hash
|
var rGenesis, rHash common.Hash
|
||||||
var rVersion, rNetwork, rNum uint64
|
var rVersion, rNetwork, rNum uint64
|
||||||
|
|
@ -492,7 +604,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
return errResp(ErrUselessPeer, "peer cannot serve requests")
|
return errResp(ErrUselessPeer, "peer cannot serve requests")
|
||||||
}
|
}
|
||||||
|
|
||||||
params := &flowcontrol.ServerParams{}
|
var params flowcontrol.ServerParams
|
||||||
if err := recv.get("flowControl/BL", ¶ms.BufLimit); err != nil {
|
if err := recv.get("flowControl/BL", ¶ms.BufLimit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -503,14 +615,38 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
||||||
if err := recv.get("flowControl/MRC", &MRC); err != nil {
|
if err := recv.get("flowControl/MRC", &MRC); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.fcServerParams = params
|
p.fcParams = params
|
||||||
p.fcServer = flowcontrol.NewServerNode(params)
|
p.fcServer = flowcontrol.NewServerNode(params, &mclock.System{})
|
||||||
p.fcCosts = MRC.decode()
|
p.fcCosts = MRC.decode()
|
||||||
}
|
}
|
||||||
p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
|
p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateFlowControl updates the flow control parameters belonging to the server
|
||||||
|
// node if the announced key/value set contains relevant fields
|
||||||
|
func (p *peer) updateFlowControl(update keyValueMap) {
|
||||||
|
if p.fcServer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params := p.fcParams
|
||||||
|
updateParams := false
|
||||||
|
if update.get("flowControl/BL", ¶ms.BufLimit) == nil {
|
||||||
|
updateParams = true
|
||||||
|
}
|
||||||
|
if update.get("flowControl/MRR", ¶ms.MinRecharge) == nil {
|
||||||
|
updateParams = true
|
||||||
|
}
|
||||||
|
if updateParams {
|
||||||
|
p.fcParams = params
|
||||||
|
p.fcServer.UpdateParams(params)
|
||||||
|
}
|
||||||
|
var MRC RequestCostList
|
||||||
|
if update.get("flowControl/MRC", &MRC) == nil {
|
||||||
|
p.fcCosts = MRC.decode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer.
|
// String implements fmt.Stringer.
|
||||||
func (p *peer) String() string {
|
func (p *peer) String() string {
|
||||||
return fmt.Sprintf("Peer %s [%s]", p.id,
|
return fmt.Sprintf("Peer %s [%s]", p.id,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,8 +25,7 @@ var (
|
||||||
|
|
||||||
//ulc connects to trusted peer and send announceType=announceTypeSigned
|
//ulc connects to trusted peer and send announceType=announceTypeSigned
|
||||||
func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) {
|
func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) {
|
||||||
|
id := newNodeID(t).ID()
|
||||||
var id enode.ID = newNodeID(t).ID()
|
|
||||||
|
|
||||||
//peer to connect(on ulc side)
|
//peer to connect(on ulc side)
|
||||||
p := peer{
|
p := peer{
|
||||||
|
|
@ -36,7 +35,7 @@ func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testi
|
||||||
rw: &rwStub{
|
rw: &rwStub{
|
||||||
WriteHook: func(recvList keyValueList) {
|
WriteHook: func(recvList keyValueList) {
|
||||||
//checking that ulc sends to peer allowedRequests=onlyAnnounceRequests and announceType = announceTypeSigned
|
//checking that ulc sends to peer allowedRequests=onlyAnnounceRequests and announceType = announceTypeSigned
|
||||||
recv := recvList.decode()
|
recv, _ := recvList.decode()
|
||||||
var reqType uint64
|
var reqType uint64
|
||||||
|
|
||||||
err := recv.get("announceType", &reqType)
|
err := recv.get("announceType", &reqType)
|
||||||
|
|
@ -74,14 +73,14 @@ func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testi
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testing.T) {
|
func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testing.T) {
|
||||||
var id enode.ID = newNodeID(t).ID()
|
id := newNodeID(t).ID()
|
||||||
p := peer{
|
p := peer{
|
||||||
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
|
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
|
||||||
version: protocol_version,
|
version: protocol_version,
|
||||||
rw: &rwStub{
|
rw: &rwStub{
|
||||||
WriteHook: func(recvList keyValueList) {
|
WriteHook: func(recvList keyValueList) {
|
||||||
//checking that ulc sends to peer allowedRequests=noRequests and announceType != announceTypeSigned
|
//checking that ulc sends to peer allowedRequests=noRequests and announceType != announceTypeSigned
|
||||||
recv := recvList.decode()
|
recv, _ := recvList.decode()
|
||||||
var reqType uint64
|
var reqType uint64
|
||||||
|
|
||||||
err := recv.get("announceType", &reqType)
|
err := recv.get("announceType", &reqType)
|
||||||
|
|
@ -118,7 +117,7 @@ func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testi
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
|
func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
|
||||||
var id enode.ID = newNodeID(t).ID()
|
id := newNodeID(t).ID()
|
||||||
|
|
||||||
s := generateLesServer()
|
s := generateLesServer()
|
||||||
|
|
||||||
|
|
@ -147,7 +146,7 @@ func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
|
func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
|
||||||
var id enode.ID = newNodeID(t).ID()
|
id := newNodeID(t).ID()
|
||||||
|
|
||||||
s := generateLesServer()
|
s := generateLesServer()
|
||||||
s.onlyAnnounce = true
|
s.onlyAnnounce = true
|
||||||
|
|
@ -181,7 +180,7 @@ func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
|
func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
|
||||||
var id enode.ID = newNodeID(t).ID()
|
id := newNodeID(t).ID()
|
||||||
|
|
||||||
p := peer{
|
p := peer{
|
||||||
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
|
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
|
||||||
|
|
@ -212,7 +211,7 @@ func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
|
func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
|
||||||
var id enode.ID = newNodeID(t).ID()
|
id := newNodeID(t).ID()
|
||||||
|
|
||||||
p := peer{
|
p := peer{
|
||||||
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
|
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
|
||||||
|
|
@ -239,17 +238,11 @@ func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
|
||||||
|
|
||||||
func generateLesServer() *LesServer {
|
func generateLesServer() *LesServer {
|
||||||
s := &LesServer{
|
s := &LesServer{
|
||||||
defParams: &flowcontrol.ServerParams{
|
defParams: flowcontrol.ServerParams{
|
||||||
BufLimit: uint64(300000000),
|
BufLimit: uint64(300000000),
|
||||||
MinRecharge: uint64(50000),
|
MinRecharge: uint64(50000),
|
||||||
},
|
},
|
||||||
fcManager: flowcontrol.NewClientManager(1, 2, 3),
|
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
|
||||||
fcCostStats: &requestCostStats{
|
|
||||||
stats: make(map[uint64]*linReg, len(reqList)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, code := range reqList {
|
|
||||||
s.fcCostStats.stats[code] = &linReg{cnt: 100}
|
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -81,6 +80,25 @@ const (
|
||||||
TxStatusMsg = 0x15
|
TxStatusMsg = 0x15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type requestInfo struct {
|
||||||
|
name string
|
||||||
|
maxCount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
var requests = map[uint64]requestInfo{
|
||||||
|
GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch},
|
||||||
|
GetBlockBodiesMsg: {"GetBlockBodies", MaxBodyFetch},
|
||||||
|
GetReceiptsMsg: {"GetReceipts", MaxReceiptFetch},
|
||||||
|
GetProofsV1Msg: {"GetProofsV1", MaxProofsFetch},
|
||||||
|
GetCodeMsg: {"GetCode", MaxCodeFetch},
|
||||||
|
SendTxMsg: {"SendTx", MaxTxSend},
|
||||||
|
GetHeaderProofsMsg: {"GetHeaderProofs", MaxHelperTrieProofsFetch},
|
||||||
|
GetProofsV2Msg: {"GetProofsV2", MaxProofsFetch},
|
||||||
|
GetHelperTrieProofsMsg: {"GetHelperTrieProofs", MaxHelperTrieProofsFetch},
|
||||||
|
SendTxV2Msg: {"SendTxV2", MaxTxSend},
|
||||||
|
GetTxStatusMsg: {"GetTxStatus", MaxTxStatus},
|
||||||
|
}
|
||||||
|
|
||||||
type errCode int
|
type errCode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -146,9 +164,9 @@ func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkSignature verifies if the block announcement has a valid signature by the given pubKey
|
// checkSignature verifies if the block announcement has a valid signature by the given pubKey
|
||||||
func (a *announceData) checkSignature(id enode.ID) error {
|
func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error {
|
||||||
var sig []byte
|
var sig []byte
|
||||||
if err := a.Update.decode().get("sign", &sig); err != nil {
|
if err := update.get("sign", &sig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
301
les/server.go
301
les/server.go
|
|
@ -14,40 +14,46 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/binary"
|
|
||||||
"math"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
|
||||||
|
|
||||||
type LesServer struct {
|
type LesServer struct {
|
||||||
lesCommons
|
lesCommons
|
||||||
|
|
||||||
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
||||||
fcCostStats *requestCostStats
|
costTracker *costTracker
|
||||||
defParams *flowcontrol.ServerParams
|
defParams flowcontrol.ServerParams
|
||||||
lesTopics []discv5.Topic
|
lesTopics []discv5.Topic
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
quitSync chan struct{}
|
quitSync chan struct{}
|
||||||
onlyAnnounce bool
|
onlyAnnounce bool
|
||||||
|
|
||||||
|
thcNormal, thcBlockProcessing int // serving thread count for normal operation and block processing mode
|
||||||
|
|
||||||
|
maxPeers int
|
||||||
|
freeClientCap uint64
|
||||||
|
freeClientPool *freeClientPool
|
||||||
|
priorityClientPool *priorityClientPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
|
|
@ -87,12 +93,20 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
|
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
|
||||||
protocolManager: pm,
|
protocolManager: pm,
|
||||||
},
|
},
|
||||||
|
costTracker: newCostTracker(eth.ChainDb(), config),
|
||||||
quitSync: quitSync,
|
quitSync: quitSync,
|
||||||
lesTopics: lesTopics,
|
lesTopics: lesTopics,
|
||||||
onlyAnnounce: config.OnlyAnnounce,
|
onlyAnnounce: config.OnlyAnnounce,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.New()
|
logger := log.New()
|
||||||
|
pm.server = srv
|
||||||
|
srv.thcNormal = config.LightServ * 4 / 100
|
||||||
|
if srv.thcNormal < 4 {
|
||||||
|
srv.thcNormal = 4
|
||||||
|
}
|
||||||
|
srv.thcBlockProcessing = config.LightServ/100 + 1
|
||||||
|
srv.fcManager = flowcontrol.NewClientManager(nil, &mclock.System{})
|
||||||
|
|
||||||
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
|
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
|
||||||
chtV2SectionCount := chtV1SectionCount / (params.CHTFrequencyClient / params.CHTFrequencyServer)
|
chtV2SectionCount := chtV1SectionCount / (params.CHTFrequencyClient / params.CHTFrequencyServer)
|
||||||
|
|
@ -114,23 +128,92 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.chtIndexer.Start(eth.BlockChain())
|
srv.chtIndexer.Start(eth.BlockChain())
|
||||||
pm.server = srv
|
|
||||||
|
|
||||||
srv.defParams = &flowcontrol.ServerParams{
|
|
||||||
BufLimit: 300000000,
|
|
||||||
MinRecharge: 50000,
|
|
||||||
}
|
|
||||||
srv.fcManager = flowcontrol.NewClientManager(uint64(config.LightServ), 10, 1000000000)
|
|
||||||
srv.fcCostStats = newCostStats(eth.ChainDb())
|
|
||||||
return srv, nil
|
return srv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LesServer) APIs() []rpc.API {
|
||||||
|
return []rpc.API{
|
||||||
|
{
|
||||||
|
Namespace: "les",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateLightServerAPI(s),
|
||||||
|
Public: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startEventLoop starts an event handler loop that updates the recharge curve of
|
||||||
|
// the client manager and adjusts the client pool's size according to the total
|
||||||
|
// capacity updates coming from the client manager
|
||||||
|
func (s *LesServer) startEventLoop() {
|
||||||
|
s.protocolManager.wg.Add(1)
|
||||||
|
|
||||||
|
var processing bool
|
||||||
|
blockProcFeed := make(chan bool, 100)
|
||||||
|
s.protocolManager.blockchain.(*core.BlockChain).SubscribeBlockProcessingEvent(blockProcFeed)
|
||||||
|
totalRechargeCh := make(chan uint64, 100)
|
||||||
|
totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh)
|
||||||
|
totalCapacityCh := make(chan uint64, 100)
|
||||||
|
updateRecharge := func() {
|
||||||
|
if processing {
|
||||||
|
s.protocolManager.servingQueue.setThreads(s.thcBlockProcessing)
|
||||||
|
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
|
||||||
|
} else {
|
||||||
|
s.protocolManager.servingQueue.setThreads(s.thcNormal)
|
||||||
|
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 10, totalRecharge}, {totalRecharge, totalRecharge}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateRecharge()
|
||||||
|
totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
|
||||||
|
s.priorityClientPool.setLimits(s.maxPeers, totalCapacity)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case processing = <-blockProcFeed:
|
||||||
|
updateRecharge()
|
||||||
|
case totalRecharge = <-totalRechargeCh:
|
||||||
|
updateRecharge()
|
||||||
|
case totalCapacity = <-totalCapacityCh:
|
||||||
|
s.priorityClientPool.setLimits(s.maxPeers, totalCapacity)
|
||||||
|
case <-s.protocolManager.quitSync:
|
||||||
|
s.protocolManager.wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LesServer) Protocols() []p2p.Protocol {
|
func (s *LesServer) Protocols() []p2p.Protocol {
|
||||||
return s.makeProtocols(ServerProtocolVersions)
|
return s.makeProtocols(ServerProtocolVersions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the LES server
|
// Start starts the LES server
|
||||||
func (s *LesServer) Start(srvr *p2p.Server) {
|
func (s *LesServer) Start(srvr *p2p.Server) {
|
||||||
|
s.maxPeers = s.config.LightPeers
|
||||||
|
totalRecharge := s.costTracker.totalRecharge()
|
||||||
|
if s.maxPeers > 0 {
|
||||||
|
s.freeClientCap = minCapacity //totalRecharge / uint64(s.maxPeers)
|
||||||
|
if s.freeClientCap < minCapacity {
|
||||||
|
s.freeClientCap = minCapacity
|
||||||
|
}
|
||||||
|
if s.freeClientCap > 0 {
|
||||||
|
s.defParams = flowcontrol.ServerParams{
|
||||||
|
BufLimit: s.freeClientCap * bufLimitRatio,
|
||||||
|
MinRecharge: s.freeClientCap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freePeers := int(totalRecharge / s.freeClientCap)
|
||||||
|
if freePeers < s.maxPeers {
|
||||||
|
log.Warn("Light peer count limited", "specified", s.maxPeers, "allowed", freePeers)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.freeClientPool = newFreeClientPool(s.chainDb, s.freeClientCap, 10000, mclock.System{}, func(id string) { go s.protocolManager.removePeer(id) })
|
||||||
|
s.priorityClientPool = newPriorityClientPool(s.freeClientCap, s.protocolManager.peers, s.freeClientPool)
|
||||||
|
|
||||||
|
s.protocolManager.peers.notify(s.priorityClientPool)
|
||||||
|
s.startEventLoop()
|
||||||
s.protocolManager.Start(s.config.LightPeers)
|
s.protocolManager.Start(s.config.LightPeers)
|
||||||
if srvr.DiscV5 != nil {
|
if srvr.DiscV5 != nil {
|
||||||
for _, topic := range s.lesTopics {
|
for _, topic := range s.lesTopics {
|
||||||
|
|
@ -156,185 +239,14 @@ func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
|
||||||
func (s *LesServer) Stop() {
|
func (s *LesServer) Stop() {
|
||||||
s.chtIndexer.Close()
|
s.chtIndexer.Close()
|
||||||
// bloom trie indexer is closed by parent bloombits indexer
|
// bloom trie indexer is closed by parent bloombits indexer
|
||||||
s.fcCostStats.store()
|
|
||||||
s.fcManager.Stop()
|
|
||||||
go func() {
|
go func() {
|
||||||
<-s.protocolManager.noMorePeers
|
<-s.protocolManager.noMorePeers
|
||||||
}()
|
}()
|
||||||
|
s.freeClientPool.stop()
|
||||||
|
s.costTracker.stop()
|
||||||
s.protocolManager.Stop()
|
s.protocolManager.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestCosts struct {
|
|
||||||
baseCost, reqCost uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestCostTable map[uint64]*requestCosts
|
|
||||||
|
|
||||||
type RequestCostList []struct {
|
|
||||||
MsgCode, BaseCost, ReqCost uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list RequestCostList) decode() requestCostTable {
|
|
||||||
table := make(requestCostTable)
|
|
||||||
for _, e := range list {
|
|
||||||
table[e.MsgCode] = &requestCosts{
|
|
||||||
baseCost: e.BaseCost,
|
|
||||||
reqCost: e.ReqCost,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return table
|
|
||||||
}
|
|
||||||
|
|
||||||
type linReg struct {
|
|
||||||
sumX, sumY, sumXX, sumXY float64
|
|
||||||
cnt uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
const linRegMaxCnt = 100000
|
|
||||||
|
|
||||||
func (l *linReg) add(x, y float64) {
|
|
||||||
if l.cnt >= linRegMaxCnt {
|
|
||||||
sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt
|
|
||||||
l.sumX -= l.sumX * sub
|
|
||||||
l.sumY -= l.sumY * sub
|
|
||||||
l.sumXX -= l.sumXX * sub
|
|
||||||
l.sumXY -= l.sumXY * sub
|
|
||||||
l.cnt = linRegMaxCnt - 1
|
|
||||||
}
|
|
||||||
l.cnt++
|
|
||||||
l.sumX += x
|
|
||||||
l.sumY += y
|
|
||||||
l.sumXX += x * x
|
|
||||||
l.sumXY += x * y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linReg) calc() (b, m float64) {
|
|
||||||
if l.cnt == 0 {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
cnt := float64(l.cnt)
|
|
||||||
d := cnt*l.sumXX - l.sumX*l.sumX
|
|
||||||
if d < 0.001 {
|
|
||||||
return l.sumY / cnt, 0
|
|
||||||
}
|
|
||||||
m = (cnt*l.sumXY - l.sumX*l.sumY) / d
|
|
||||||
b = (l.sumY / cnt) - (m * l.sumX / cnt)
|
|
||||||
return b, m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linReg) toBytes() []byte {
|
|
||||||
var arr [40]byte
|
|
||||||
binary.BigEndian.PutUint64(arr[0:8], math.Float64bits(l.sumX))
|
|
||||||
binary.BigEndian.PutUint64(arr[8:16], math.Float64bits(l.sumY))
|
|
||||||
binary.BigEndian.PutUint64(arr[16:24], math.Float64bits(l.sumXX))
|
|
||||||
binary.BigEndian.PutUint64(arr[24:32], math.Float64bits(l.sumXY))
|
|
||||||
binary.BigEndian.PutUint64(arr[32:40], l.cnt)
|
|
||||||
return arr[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func linRegFromBytes(data []byte) *linReg {
|
|
||||||
if len(data) != 40 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
l := &linReg{}
|
|
||||||
l.sumX = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
|
|
||||||
l.sumY = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
|
|
||||||
l.sumXX = math.Float64frombits(binary.BigEndian.Uint64(data[16:24]))
|
|
||||||
l.sumXY = math.Float64frombits(binary.BigEndian.Uint64(data[24:32]))
|
|
||||||
l.cnt = binary.BigEndian.Uint64(data[32:40])
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestCostStats struct {
|
|
||||||
lock sync.RWMutex
|
|
||||||
db ethdb.Database
|
|
||||||
stats map[uint64]*linReg
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestCostStatsRlp []struct {
|
|
||||||
MsgCode uint64
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var rcStatsKey = []byte("_requestCostStats")
|
|
||||||
|
|
||||||
func newCostStats(db ethdb.Database) *requestCostStats {
|
|
||||||
stats := make(map[uint64]*linReg)
|
|
||||||
for _, code := range reqList {
|
|
||||||
stats[code] = &linReg{cnt: 100}
|
|
||||||
}
|
|
||||||
|
|
||||||
if db != nil {
|
|
||||||
data, err := db.Get(rcStatsKey)
|
|
||||||
var statsRlp requestCostStatsRlp
|
|
||||||
if err == nil {
|
|
||||||
err = rlp.DecodeBytes(data, &statsRlp)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
for _, r := range statsRlp {
|
|
||||||
if stats[r.MsgCode] != nil {
|
|
||||||
if l := linRegFromBytes(r.Data); l != nil {
|
|
||||||
stats[r.MsgCode] = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &requestCostStats{
|
|
||||||
db: db,
|
|
||||||
stats: stats,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *requestCostStats) store() {
|
|
||||||
s.lock.Lock()
|
|
||||||
defer s.lock.Unlock()
|
|
||||||
|
|
||||||
statsRlp := make(requestCostStatsRlp, len(reqList))
|
|
||||||
for i, code := range reqList {
|
|
||||||
statsRlp[i].MsgCode = code
|
|
||||||
statsRlp[i].Data = s.stats[code].toBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
if data, err := rlp.EncodeToBytes(statsRlp); err == nil {
|
|
||||||
s.db.Put(rcStatsKey, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *requestCostStats) getCurrentList() RequestCostList {
|
|
||||||
s.lock.Lock()
|
|
||||||
defer s.lock.Unlock()
|
|
||||||
|
|
||||||
list := make(RequestCostList, len(reqList))
|
|
||||||
for idx, code := range reqList {
|
|
||||||
b, m := s.stats[code].calc()
|
|
||||||
if m < 0 {
|
|
||||||
b += m
|
|
||||||
m = 0
|
|
||||||
}
|
|
||||||
if b < 0 {
|
|
||||||
b = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
list[idx].MsgCode = code
|
|
||||||
list[idx].BaseCost = uint64(b * 2)
|
|
||||||
list[idx].ReqCost = uint64(m * 2)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) {
|
|
||||||
s.lock.Lock()
|
|
||||||
defer s.lock.Unlock()
|
|
||||||
|
|
||||||
c, ok := s.stats[msgCode]
|
|
||||||
if !ok || reqCnt == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.add(float64(reqCnt), float64(cost))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *ProtocolManager) blockLoop() {
|
func (pm *ProtocolManager) blockLoop() {
|
||||||
pm.wg.Add(1)
|
pm.wg.Add(1)
|
||||||
headCh := make(chan core.ChainHeadEvent, 10)
|
headCh := make(chan core.ChainHeadEvent, 10)
|
||||||
|
|
@ -371,12 +283,7 @@ func (pm *ProtocolManager) blockLoop() {
|
||||||
switch p.announceType {
|
switch p.announceType {
|
||||||
|
|
||||||
case announceTypeSimple:
|
case announceTypeSimple:
|
||||||
select {
|
p.queueSend(func() { p.SendAnnounce(announce) })
|
||||||
case p.announceChn <- announce:
|
|
||||||
default:
|
|
||||||
pm.removePeer(p.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
case announceTypeSigned:
|
case announceTypeSigned:
|
||||||
if !signed {
|
if !signed {
|
||||||
signedAnnounce = announce
|
signedAnnounce = announce
|
||||||
|
|
@ -384,11 +291,7 @@ func (pm *ProtocolManager) blockLoop() {
|
||||||
signed = true
|
signed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
p.queueSend(func() { p.SendAnnounce(signedAnnounce) })
|
||||||
case p.announceChn <- signedAnnounce:
|
|
||||||
default:
|
|
||||||
pm.removePeer(p.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package les implements the Light Ethereum Subprotocol.
|
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
261
les/servingqueue.go
Normal file
261
les/servingqueue.go
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
|
)
|
||||||
|
|
||||||
|
// servingQueue allows running tasks in a limited number of threads and puts the
|
||||||
|
// waiting tasks in a priority queue
|
||||||
|
type servingQueue struct {
|
||||||
|
tokenCh chan runToken
|
||||||
|
queueAddCh, queueBestCh chan *servingTask
|
||||||
|
stopThreadCh, quit chan struct{}
|
||||||
|
setThreadsCh chan int
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
|
threadCount int // number of currently running threads
|
||||||
|
queue *prque.Prque // priority queue for waiting or suspended tasks
|
||||||
|
best *servingTask // the highest priority task (not included in the queue)
|
||||||
|
suspendBias int64 // priority bias against suspending an already running task
|
||||||
|
}
|
||||||
|
|
||||||
|
// servingTask represents a request serving task. Tasks can be implemented to
|
||||||
|
// run in multiple steps, allowing the serving queue to suspend execution between
|
||||||
|
// steps if higher priority tasks are entered. The creator of the task should
|
||||||
|
// set the following fields:
|
||||||
|
//
|
||||||
|
// - priority: greater value means higher priority; values can wrap around the int64 range
|
||||||
|
// - run: execute a single step; return true if finished
|
||||||
|
// - after: executed after run finishes or returns an error, receives the total serving time
|
||||||
|
type servingTask struct {
|
||||||
|
sq *servingQueue
|
||||||
|
servingTime uint64
|
||||||
|
priority int64
|
||||||
|
biasAdded bool
|
||||||
|
token runToken
|
||||||
|
tokenCh chan runToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// runToken received by servingTask.start allows the task to run. Closing the
|
||||||
|
// channel by servingTask.stop signals the thread controller to allow a new task
|
||||||
|
// to start running.
|
||||||
|
type runToken chan struct{}
|
||||||
|
|
||||||
|
// start blocks until the task can start and returns true if it is allowed to run.
|
||||||
|
// Returning false means that the task should be cancelled.
|
||||||
|
func (t *servingTask) start() bool {
|
||||||
|
select {
|
||||||
|
case t.token = <-t.sq.tokenCh:
|
||||||
|
default:
|
||||||
|
t.tokenCh = make(chan runToken, 1)
|
||||||
|
select {
|
||||||
|
case t.sq.queueAddCh <- t:
|
||||||
|
case <-t.sq.quit:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case t.token = <-t.tokenCh:
|
||||||
|
case <-t.sq.quit:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.token == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t.servingTime -= uint64(mclock.Now())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// done signals the thread controller about the task being finished and returns
|
||||||
|
// the total serving time of the task in nanoseconds.
|
||||||
|
func (t *servingTask) done() uint64 {
|
||||||
|
t.servingTime += uint64(mclock.Now())
|
||||||
|
close(t.token)
|
||||||
|
return t.servingTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitOrStop can be called during the execution of the task. It blocks if there
|
||||||
|
// is a higher priority task waiting (a bias is applied in favor of the currently
|
||||||
|
// running task). Returning true means that the execution can be resumed. False
|
||||||
|
// means the task should be cancelled.
|
||||||
|
func (t *servingTask) waitOrStop() bool {
|
||||||
|
t.done()
|
||||||
|
if !t.biasAdded {
|
||||||
|
t.priority += t.sq.suspendBias
|
||||||
|
t.biasAdded = true
|
||||||
|
}
|
||||||
|
return t.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newServingQueue returns a new servingQueue
|
||||||
|
func newServingQueue(suspendBias int64) *servingQueue {
|
||||||
|
sq := &servingQueue{
|
||||||
|
queue: prque.New(nil),
|
||||||
|
suspendBias: suspendBias,
|
||||||
|
tokenCh: make(chan runToken),
|
||||||
|
queueAddCh: make(chan *servingTask, 100),
|
||||||
|
queueBestCh: make(chan *servingTask),
|
||||||
|
stopThreadCh: make(chan struct{}),
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
setThreadsCh: make(chan int, 10),
|
||||||
|
}
|
||||||
|
sq.wg.Add(2)
|
||||||
|
go sq.queueLoop()
|
||||||
|
go sq.threadCountLoop()
|
||||||
|
return sq
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTask creates a new task with the given priority
|
||||||
|
func (sq *servingQueue) newTask(priority int64) *servingTask {
|
||||||
|
return &servingTask{
|
||||||
|
sq: sq,
|
||||||
|
priority: priority,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// threadController is started in multiple goroutines and controls the execution
|
||||||
|
// of tasks. The number of active thread controllers equals the allowed number of
|
||||||
|
// concurrently running threads. It tries to fetch the highest priority queued
|
||||||
|
// task first. If there are no queued tasks waiting then it can directly catch
|
||||||
|
// run tokens from the token channel and allow the corresponding tasks to run
|
||||||
|
// without entering the priority queue.
|
||||||
|
func (sq *servingQueue) threadController() {
|
||||||
|
for {
|
||||||
|
token := make(runToken)
|
||||||
|
select {
|
||||||
|
case best := <-sq.queueBestCh:
|
||||||
|
best.tokenCh <- token
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case best := <-sq.queueBestCh:
|
||||||
|
best.tokenCh <- token
|
||||||
|
case sq.tokenCh <- token:
|
||||||
|
case <-sq.stopThreadCh:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
case <-sq.quit:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<-token
|
||||||
|
select {
|
||||||
|
case <-sq.stopThreadCh:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
case <-sq.quit:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTask inserts a task into the priority queue
|
||||||
|
func (sq *servingQueue) addTask(task *servingTask) {
|
||||||
|
if sq.best == nil {
|
||||||
|
sq.best = task
|
||||||
|
} else if task.priority > sq.best.priority {
|
||||||
|
sq.queue.Push(sq.best, sq.best.priority)
|
||||||
|
sq.best = task
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
sq.queue.Push(task, task.priority)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// queueLoop is an event loop running in a goroutine. It receives tasks from queueAddCh
|
||||||
|
// and always tries to send the highest priority task to queueBestCh. Successfully sent
|
||||||
|
// tasks are removed from the queue.
|
||||||
|
func (sq *servingQueue) queueLoop() {
|
||||||
|
for {
|
||||||
|
if sq.best != nil {
|
||||||
|
select {
|
||||||
|
case task := <-sq.queueAddCh:
|
||||||
|
sq.addTask(task)
|
||||||
|
case sq.queueBestCh <- sq.best:
|
||||||
|
if sq.queue.Size() == 0 {
|
||||||
|
sq.best = nil
|
||||||
|
} else {
|
||||||
|
sq.best, _ = sq.queue.PopItem().(*servingTask)
|
||||||
|
}
|
||||||
|
case <-sq.quit:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case task := <-sq.queueAddCh:
|
||||||
|
sq.addTask(task)
|
||||||
|
case <-sq.quit:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// threadCountLoop is an event loop running in a goroutine. It adjusts the number
|
||||||
|
// of active thread controller goroutines.
|
||||||
|
func (sq *servingQueue) threadCountLoop() {
|
||||||
|
var threadCountTarget int
|
||||||
|
for {
|
||||||
|
for threadCountTarget > sq.threadCount {
|
||||||
|
sq.wg.Add(1)
|
||||||
|
go sq.threadController()
|
||||||
|
sq.threadCount++
|
||||||
|
}
|
||||||
|
if threadCountTarget < sq.threadCount {
|
||||||
|
select {
|
||||||
|
case threadCountTarget = <-sq.setThreadsCh:
|
||||||
|
case sq.stopThreadCh <- struct{}{}:
|
||||||
|
sq.threadCount--
|
||||||
|
case <-sq.quit:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case threadCountTarget = <-sq.setThreadsCh:
|
||||||
|
case <-sq.quit:
|
||||||
|
sq.wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setThreads sets the allowed processing thread count, suspending tasks as soon as
|
||||||
|
// possible if necessary.
|
||||||
|
func (sq *servingQueue) setThreads(threadCount int) {
|
||||||
|
select {
|
||||||
|
case sq.setThreadsCh <- threadCount:
|
||||||
|
case <-sq.quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops task processing as soon as possible and shuts down the serving queue.
|
||||||
|
func (sq *servingQueue) stop() {
|
||||||
|
close(sq.quit)
|
||||||
|
sq.wg.Wait()
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ltrInfo struct {
|
type ltrInfo struct {
|
||||||
|
|
@ -113,21 +114,22 @@ func (self *LesTxRelay) send(txs types.Transactions, count int) {
|
||||||
for p, list := range sendTo {
|
for p, list := range sendTo {
|
||||||
pp := p
|
pp := p
|
||||||
ll := list
|
ll := list
|
||||||
|
enc, _ := rlp.EncodeToBytes(ll)
|
||||||
|
|
||||||
reqID := genReqID()
|
reqID := genReqID()
|
||||||
rq := &distReq{
|
rq := &distReq{
|
||||||
getCost: func(dp distPeer) uint64 {
|
getCost: func(dp distPeer) uint64 {
|
||||||
peer := dp.(*peer)
|
peer := dp.(*peer)
|
||||||
return peer.GetRequestCost(SendTxMsg, len(ll))
|
return peer.GetTxRelayCost(len(ll), len(enc))
|
||||||
},
|
},
|
||||||
canSend: func(dp distPeer) bool {
|
canSend: func(dp distPeer) bool {
|
||||||
return !dp.(*peer).isOnlyAnnounce && dp.(*peer) == pp
|
return !dp.(*peer).isOnlyAnnounce && dp.(*peer) == pp
|
||||||
},
|
},
|
||||||
request: func(dp distPeer) func() {
|
request: func(dp distPeer) func() {
|
||||||
peer := dp.(*peer)
|
peer := dp.(*peer)
|
||||||
cost := peer.GetRequestCost(SendTxMsg, len(ll))
|
cost := peer.GetTxRelayCost(len(ll), len(enc))
|
||||||
peer.fcServer.QueueRequest(reqID, cost)
|
peer.fcServer.QueuedRequest(reqID, cost)
|
||||||
return func() { peer.SendTxs(reqID, cost, ll) }
|
return func() { peer.SendTxs(reqID, cost, enc) }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.reqDist.queue(rq)
|
self.reqDist.queue(rq)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
|
@ -217,7 +218,7 @@ func newFullPeerPair(t *testing.T, index int, numberOfblocks int, chainGen func(
|
||||||
// newLightPeer creates node with light sync mode
|
// newLightPeer creates node with light sync mode
|
||||||
func newLightPeer(t *testing.T, ulcConfig *eth.ULCConfig) pairPeer {
|
func newLightPeer(t *testing.T, ulcConfig *eth.ULCConfig) pairPeer {
|
||||||
peers := newPeerSet()
|
peers := newPeerSet()
|
||||||
dist := newRequestDistributor(peers, make(chan struct{}))
|
dist := newRequestDistributor(peers, make(chan struct{}), &mclock.System{})
|
||||||
rm := newRetrieveManager(peers, dist, nil)
|
rm := newRetrieveManager(peers, dist, nil)
|
||||||
ldb := ethdb.NewMemDatabase()
|
ldb := ethdb.NewMemDatabase()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package light implements on-demand retrieval capable state and chain objects
|
||||||
|
// for the Ethereum Light Client.
|
||||||
package light
|
package light
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package light
|
package light
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,11 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
|
||||||
Stack: node.DefaultConfig,
|
Stack: node.DefaultConfig,
|
||||||
Node: config,
|
Node: config,
|
||||||
}
|
}
|
||||||
|
if config.DataDir != "" {
|
||||||
|
conf.Stack.DataDir = config.DataDir
|
||||||
|
} else {
|
||||||
conf.Stack.DataDir = filepath.Join(dir, "data")
|
conf.Stack.DataDir = filepath.Join(dir, "data")
|
||||||
|
}
|
||||||
conf.Stack.WSHost = "127.0.0.1"
|
conf.Stack.WSHost = "127.0.0.1"
|
||||||
conf.Stack.WSPort = 0
|
conf.Stack.WSPort = 0
|
||||||
conf.Stack.WSOrigins = []string{"*"}
|
conf.Stack.WSOrigins = []string{"*"}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ type NodeConfig struct {
|
||||||
// Name is a human friendly name for the node like "node01"
|
// Name is a human friendly name for the node like "node01"
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
// Use an existing database instead of a temporary one if non-empty
|
||||||
|
DataDir string
|
||||||
|
|
||||||
// Services are the names of the services which should be run when
|
// Services are the names of the services which should be run when
|
||||||
// starting the node (for SimNodes it should be the names of services
|
// starting the node (for SimNodes it should be the names of services
|
||||||
// contained in SimAdapter.services, for other nodes it should be
|
// contained in SimAdapter.services, for other nodes it should be
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -48,6 +49,10 @@ var (
|
||||||
errUintOverflow = errors.New("rlp: uint overflow")
|
errUintOverflow = errors.New("rlp: uint overflow")
|
||||||
errNoPointer = errors.New("rlp: interface given to Decode must be a pointer")
|
errNoPointer = errors.New("rlp: interface given to Decode must be a pointer")
|
||||||
errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil")
|
errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil")
|
||||||
|
|
||||||
|
streamPool = sync.Pool{
|
||||||
|
New: func() interface{} { return new(Stream) },
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Decoder is implemented by types that require custom RLP
|
// Decoder is implemented by types that require custom RLP
|
||||||
|
|
@ -126,17 +131,24 @@ type Decoder interface {
|
||||||
//
|
//
|
||||||
// NewStream(r, limit).Decode(val)
|
// NewStream(r, limit).Decode(val)
|
||||||
func Decode(r io.Reader, val interface{}) error {
|
func Decode(r io.Reader, val interface{}) error {
|
||||||
// TODO: this could use a Stream from a pool.
|
stream := streamPool.Get().(*Stream)
|
||||||
return NewStream(r, 0).Decode(val)
|
defer streamPool.Put(stream)
|
||||||
|
|
||||||
|
stream.Reset(r, 0)
|
||||||
|
return stream.Decode(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBytes parses RLP data from b into val.
|
// DecodeBytes parses RLP data from b into val.
|
||||||
// Please see the documentation of Decode for the decoding rules.
|
// Please see the documentation of Decode for the decoding rules.
|
||||||
// The input must contain exactly one value and no trailing data.
|
// The input must contain exactly one value and no trailing data.
|
||||||
func DecodeBytes(b []byte, val interface{}) error {
|
func DecodeBytes(b []byte, val interface{}) error {
|
||||||
// TODO: this could use a Stream from a pool.
|
|
||||||
r := bytes.NewReader(b)
|
r := bytes.NewReader(b)
|
||||||
if err := NewStream(r, uint64(len(b))).Decode(val); err != nil {
|
|
||||||
|
stream := streamPool.Get().(*Stream)
|
||||||
|
defer streamPool.Put(stream)
|
||||||
|
|
||||||
|
stream.Reset(r, uint64(len(b)))
|
||||||
|
if err := stream.Decode(val); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if r.Len() > 0 {
|
if r.Len() > 0 {
|
||||||
|
|
@ -853,6 +865,7 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) {
|
||||||
if s.uintbuf == nil {
|
if s.uintbuf == nil {
|
||||||
s.uintbuf = make([]byte, 8)
|
s.uintbuf = make([]byte, 8)
|
||||||
}
|
}
|
||||||
|
s.byteval = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kind returns the kind and size of the next value in the
|
// Kind returns the kind and size of the next value in the
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const EMPTY_CREDENTIALS = ""
|
const EmptyCredentials = ""
|
||||||
|
|
||||||
type AccessEntry struct {
|
type AccessEntry struct {
|
||||||
Type AccessType
|
Type AccessType
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,7 @@ func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Add
|
||||||
apiDeleteFail.Inc(1)
|
apiDeleteFail.Inc(1)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
key, err := a.ResolveURI(ctx, uri, EmptyCredentials)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -643,7 +643,7 @@ func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []
|
||||||
apiAddFileFail.Inc(1)
|
apiAddFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
mkey, err := a.ResolveURI(ctx, uri, EmptyCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiAddFileFail.Inc(1)
|
apiAddFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
|
@ -760,7 +760,7 @@ func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname s
|
||||||
apiRmFileFail.Inc(1)
|
apiRmFileFail.Inc(1)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
mkey, err := a.ResolveURI(ctx, uri, EmptyCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiRmFileFail.Inc(1)
|
apiRmFileFail.Inc(1)
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -827,7 +827,7 @@ func (a *API) AppendFile(ctx context.Context, mhash, path, fname string, existin
|
||||||
apiAppendFileFail.Inc(1)
|
apiAppendFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
mkey, err := a.ResolveURI(ctx, uri, EmptyCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiAppendFileFail.Inc(1)
|
apiAppendFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,109 @@
|
||||||
package chunk
|
package chunk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultSize = 4096
|
DefaultSize = 4096
|
||||||
|
MaxPO = 16
|
||||||
|
AddressLength = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrChunkNotFound = errors.New("chunk not found")
|
||||||
|
ErrChunkInvalid = errors.New("invalid chunk")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Chunk interface {
|
||||||
|
Address() Address
|
||||||
|
Data() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunk struct {
|
||||||
|
addr Address
|
||||||
|
sdata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChunk(addr Address, data []byte) *chunk {
|
||||||
|
return &chunk{
|
||||||
|
addr: addr,
|
||||||
|
sdata: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chunk) Address() Address {
|
||||||
|
return c.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chunk) Data() []byte {
|
||||||
|
return c.sdata
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *chunk) String() string {
|
||||||
|
return fmt.Sprintf("Address: %v Chunksize: %v", self.addr.Log(), len(self.sdata))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Address []byte
|
||||||
|
|
||||||
|
var ZeroAddr = Address(common.Hash{}.Bytes())
|
||||||
|
|
||||||
|
func (a Address) Hex() string {
|
||||||
|
return fmt.Sprintf("%064x", []byte(a[:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Address) Log() string {
|
||||||
|
if len(a[:]) < 8 {
|
||||||
|
return fmt.Sprintf("%x", []byte(a[:]))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%016x", []byte(a[:8]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Address) String() string {
|
||||||
|
return fmt.Sprintf("%064x", []byte(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Address) MarshalJSON() (out []byte, err error) {
|
||||||
|
return []byte(`"` + a.String() + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Address) UnmarshalJSON(value []byte) error {
|
||||||
|
s := string(value)
|
||||||
|
*a = make([]byte, 32)
|
||||||
|
h := common.Hex2Bytes(s[1 : len(s)-1])
|
||||||
|
copy(*a, h)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proximity returns the proximity order of the MSB distance between x and y
|
||||||
|
//
|
||||||
|
// The distance metric MSB(x, y) of two equal length byte sequences x an y is the
|
||||||
|
// value of the binary integer cast of the x^y, ie., x and y bitwise xor-ed.
|
||||||
|
// the binary cast is big endian: most significant bit first (=MSB).
|
||||||
|
//
|
||||||
|
// Proximity(x, y) is a discrete logarithmic scaling of the MSB distance.
|
||||||
|
// It is defined as the reverse rank of the integer part of the base 2
|
||||||
|
// logarithm of the distance.
|
||||||
|
// It is calculated by counting the number of common leading zeros in the (MSB)
|
||||||
|
// binary representation of the x^y.
|
||||||
|
//
|
||||||
|
// (0 farthest, 255 closest, 256 self)
|
||||||
|
func Proximity(one, other []byte) (ret int) {
|
||||||
|
b := (MaxPO-1)/8 + 1
|
||||||
|
if b > len(one) {
|
||||||
|
b = len(one)
|
||||||
|
}
|
||||||
|
m := 8
|
||||||
|
for i := 0; i < b; i++ {
|
||||||
|
oxo := one[i] ^ other[i]
|
||||||
|
for j := 0; j < m; j++ {
|
||||||
|
if (oxo>>uint8(7-j))&0x01 != 0 {
|
||||||
|
return i*8 + j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MaxPO
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package storage
|
package chunk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -24,10 +24,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Swarmfs_Version = "0.1"
|
SwarmFSVersion = "0.1"
|
||||||
mountTimeout = time.Second * 5
|
mountTimeout = time.Second * 5
|
||||||
unmountTimeout = time.Second * 10
|
unmountTimeout = time.Second * 10
|
||||||
maxFuseMounts = 5
|
maxFUSEMounts = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func (swarmfs *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) {
|
||||||
|
|
||||||
noOfActiveMounts := len(swarmfs.activeMounts)
|
noOfActiveMounts := len(swarmfs.activeMounts)
|
||||||
log.Debug("swarmfs mount", "# active mounts", noOfActiveMounts)
|
log.Debug("swarmfs mount", "# active mounts", noOfActiveMounts)
|
||||||
if noOfActiveMounts >= maxFuseMounts {
|
if noOfActiveMounts >= maxFUSEMounts {
|
||||||
return nil, errMaxMountCount
|
return nil, errMaxMountCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,11 +222,11 @@ func (s *Simulation) UploadSnapshot(snapshotFile string, opts ...AddNodeOption)
|
||||||
//the snapshot probably has the property EnableMsgEvents not set
|
//the snapshot probably has the property EnableMsgEvents not set
|
||||||
//just in case, set it to true!
|
//just in case, set it to true!
|
||||||
//(we need this to wait for messages before uploading)
|
//(we need this to wait for messages before uploading)
|
||||||
for _, n := range snap.Nodes {
|
for i := range snap.Nodes {
|
||||||
n.Node.Config.EnableMsgEvents = true
|
snap.Nodes[i].Node.Config.EnableMsgEvents = true
|
||||||
n.Node.Config.Services = s.serviceNames
|
snap.Nodes[i].Node.Config.Services = s.serviceNames
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(n.Node.Config)
|
o(snap.Nodes[i].Node.Config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,20 +101,20 @@ func NewPeer(peer *protocols.Peer, streamer *Registry) *Peer {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
var len_maxi int
|
var lenMaxi int
|
||||||
var cap_maxi int
|
var capMaxi int
|
||||||
for k := range pq.Queues {
|
for k := range pq.Queues {
|
||||||
if len_maxi < len(pq.Queues[k]) {
|
if lenMaxi < len(pq.Queues[k]) {
|
||||||
len_maxi = len(pq.Queues[k])
|
lenMaxi = len(pq.Queues[k])
|
||||||
}
|
}
|
||||||
|
|
||||||
if cap_maxi < cap(pq.Queues[k]) {
|
if capMaxi < cap(pq.Queues[k]) {
|
||||||
cap_maxi = cap(pq.Queues[k])
|
capMaxi = cap(pq.Queues[k])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.GetOrRegisterGauge(fmt.Sprintf("pq_len_%s", p.ID().TerminalString()), nil).Update(int64(len_maxi))
|
metrics.GetOrRegisterGauge(fmt.Sprintf("pq_len_%s", p.ID().TerminalString()), nil).Update(int64(lenMaxi))
|
||||||
metrics.GetOrRegisterGauge(fmt.Sprintf("pq_cap_%s", p.ID().TerminalString()), nil).Update(int64(cap_maxi))
|
metrics.GetOrRegisterGauge(fmt.Sprintf("pq_cap_%s", p.ID().TerminalString()), nil).Update(int64(capMaxi))
|
||||||
case <-p.quit:
|
case <-p.quit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ const (
|
||||||
// subscriptionFunc is used to determine what to do in order to perform subscriptions
|
// subscriptionFunc is used to determine what to do in order to perform subscriptions
|
||||||
// usually we would start to really subscribe to nodes, but for tests other functionality may be needed
|
// usually we would start to really subscribe to nodes, but for tests other functionality may be needed
|
||||||
// (see TestRequestPeerSubscriptions in streamer_test.go)
|
// (see TestRequestPeerSubscriptions in streamer_test.go)
|
||||||
var subscriptionFunc func(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool = doRequestSubscription
|
var subscriptionFunc = doRequestSubscription
|
||||||
|
|
||||||
// Registry registry for outgoing and incoming streamer constructors
|
// Registry registry for outgoing and incoming streamer constructors
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -286,18 +285,3 @@ func newServices() adapters.Services {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copied from swarm/network/protocol_test_go
|
|
||||||
type testStore struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
values map[string][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testStore) Load(key string) ([]byte, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testStore) Save(key string, v []byte) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ func NewHandshakeParams() *HandshakeParams {
|
||||||
type HandshakeController struct {
|
type HandshakeController struct {
|
||||||
pss *Pss
|
pss *Pss
|
||||||
keyC map[string]chan []string // adds a channel to report when a handshake succeeds
|
keyC map[string]chan []string // adds a channel to report when a handshake succeeds
|
||||||
|
keyCMu sync.Mutex // protects keyC map
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
symKeyRequestTimeout time.Duration
|
symKeyRequestTimeout time.Duration
|
||||||
symKeyExpiryTimeout time.Duration
|
symKeyExpiryTimeout time.Duration
|
||||||
|
|
@ -165,9 +166,9 @@ func (ctl *HandshakeController) validKeys(pubkeyid string, topic *Topic, in bool
|
||||||
|
|
||||||
for _, key := range *keystore {
|
for _, key := range *keystore {
|
||||||
if key.limit <= key.count {
|
if key.limit <= key.count {
|
||||||
ctl.releaseKey(*key.symKeyID, topic)
|
ctl.releaseKeyNoLock(*key.symKeyID, topic)
|
||||||
} else if !key.expiredAt.IsZero() && key.expiredAt.Before(now) {
|
} else if !key.expiredAt.IsZero() && key.expiredAt.Before(now) {
|
||||||
ctl.releaseKey(*key.symKeyID, topic)
|
ctl.releaseKeyNoLock(*key.symKeyID, topic)
|
||||||
} else {
|
} else {
|
||||||
validkeys = append(validkeys, key.symKeyID)
|
validkeys = append(validkeys, key.symKeyID)
|
||||||
}
|
}
|
||||||
|
|
@ -205,15 +206,23 @@ func (ctl *HandshakeController) updateKeys(pubkeyid string, topic *Topic, in boo
|
||||||
limit: limit,
|
limit: limit,
|
||||||
}
|
}
|
||||||
*keystore = append(*keystore, storekey)
|
*keystore = append(*keystore, storekey)
|
||||||
|
ctl.pss.mx.Lock()
|
||||||
ctl.pss.symKeyPool[*storekey.symKeyID][*topic].protected = true
|
ctl.pss.symKeyPool[*storekey.symKeyID][*topic].protected = true
|
||||||
|
ctl.pss.mx.Unlock()
|
||||||
}
|
}
|
||||||
for i := 0; i < len(*keystore); i++ {
|
for i := 0; i < len(*keystore); i++ {
|
||||||
ctl.symKeyIndex[*(*keystore)[i].symKeyID] = &((*keystore)[i])
|
ctl.symKeyIndex[*(*keystore)[i].symKeyID] = &((*keystore)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expire a symmetric key, making it elegible for garbage collection
|
|
||||||
func (ctl *HandshakeController) releaseKey(symkeyid string, topic *Topic) bool {
|
func (ctl *HandshakeController) releaseKey(symkeyid string, topic *Topic) bool {
|
||||||
|
ctl.lock.Lock()
|
||||||
|
defer ctl.lock.Unlock()
|
||||||
|
return ctl.releaseKeyNoLock(symkeyid, topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire a symmetric key, making it eligible for garbage collection
|
||||||
|
func (ctl *HandshakeController) releaseKeyNoLock(symkeyid string, topic *Topic) bool {
|
||||||
if ctl.symKeyIndex[symkeyid] == nil {
|
if ctl.symKeyIndex[symkeyid] == nil {
|
||||||
log.Debug("no symkey", "symkeyid", symkeyid)
|
log.Debug("no symkey", "symkeyid", symkeyid)
|
||||||
return false
|
return false
|
||||||
|
|
@ -276,21 +285,18 @@ func (ctl *HandshakeController) clean() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *HandshakeController) getSymKey(symkeyid string) *handshakeKey {
|
||||||
|
ctl.lock.Lock()
|
||||||
|
defer ctl.lock.Unlock()
|
||||||
|
return ctl.symKeyIndex[symkeyid]
|
||||||
|
}
|
||||||
|
|
||||||
// Passed as a PssMsg handler for the topic handshake is activated on
|
// Passed as a PssMsg handler for the topic handshake is activated on
|
||||||
// Handles incoming key exchange messages and
|
// Handles incoming key exchange messages and
|
||||||
// ccunts message usage by symmetric key (expiry limit control)
|
// counts message usage by symmetric key (expiry limit control)
|
||||||
// Only returns error if key handler fails
|
// Only returns error if key handler fails
|
||||||
func (ctl *HandshakeController) handler(msg []byte, p *p2p.Peer, asymmetric bool, symkeyid string) error {
|
func (ctl *HandshakeController) handler(msg []byte, p *p2p.Peer, asymmetric bool, symkeyid string) error {
|
||||||
if !asymmetric {
|
if asymmetric {
|
||||||
if ctl.symKeyIndex[symkeyid] != nil {
|
|
||||||
if ctl.symKeyIndex[symkeyid].count >= ctl.symKeyIndex[symkeyid].limit {
|
|
||||||
return fmt.Errorf("discarding message using expired key: %s", symkeyid)
|
|
||||||
}
|
|
||||||
ctl.symKeyIndex[symkeyid].count++
|
|
||||||
log.Trace("increment symkey recv use", "symsymkeyid", symkeyid, "count", ctl.symKeyIndex[symkeyid].count, "limit", ctl.symKeyIndex[symkeyid].limit, "receiver", common.ToHex(crypto.FromECDSAPub(ctl.pss.PublicKey())))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
keymsg := &handshakeMsg{}
|
keymsg := &handshakeMsg{}
|
||||||
err := rlp.DecodeBytes(msg, keymsg)
|
err := rlp.DecodeBytes(msg, keymsg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -302,6 +308,28 @@ func (ctl *HandshakeController) handler(msg []byte, p *p2p.Peer, asymmetric bool
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return ctl.registerSymKeyUse(symkeyid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HandshakeController) registerSymKeyUse(symkeyid string) error {
|
||||||
|
ctl.lock.Lock()
|
||||||
|
defer ctl.lock.Unlock()
|
||||||
|
|
||||||
|
symKey, ok := ctl.symKeyIndex[symkeyid]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if symKey.count >= symKey.limit {
|
||||||
|
return fmt.Errorf("symetric key expired (id: %s)", symkeyid)
|
||||||
|
}
|
||||||
|
symKey.count++
|
||||||
|
|
||||||
|
receiver := common.ToHex(crypto.FromECDSAPub(ctl.pss.PublicKey()))
|
||||||
|
log.Trace("increment symkey recv use", "symsymkeyid", symkeyid, "count", symKey.count, "limit", symKey.limit, "receiver", receiver)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Handle incoming key exchange message
|
// Handle incoming key exchange message
|
||||||
// Add keys received from peer to store
|
// Add keys received from peer to store
|
||||||
|
|
@ -417,6 +445,8 @@ func (ctl *HandshakeController) sendKey(pubkeyid string, topic *Topic, keycount
|
||||||
|
|
||||||
// Enables callback for keys received from a key exchange request
|
// Enables callback for keys received from a key exchange request
|
||||||
func (ctl *HandshakeController) alertHandshake(pubkeyid string, symkeys []string) chan []string {
|
func (ctl *HandshakeController) alertHandshake(pubkeyid string, symkeys []string) chan []string {
|
||||||
|
ctl.keyCMu.Lock()
|
||||||
|
defer ctl.keyCMu.Unlock()
|
||||||
if len(symkeys) > 0 {
|
if len(symkeys) > 0 {
|
||||||
if _, ok := ctl.keyC[pubkeyid]; ok {
|
if _, ok := ctl.keyC[pubkeyid]; ok {
|
||||||
ctl.keyC[pubkeyid] <- symkeys
|
ctl.keyC[pubkeyid] <- symkeys
|
||||||
|
|
@ -519,7 +549,7 @@ func (api *HandshakeAPI) GetHandshakeKeys(pubkeyid string, topic Topic, in bool,
|
||||||
// Returns the amount of messages the specified symmetric key
|
// Returns the amount of messages the specified symmetric key
|
||||||
// is still valid for under the handshake scheme
|
// is still valid for under the handshake scheme
|
||||||
func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error) {
|
func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error) {
|
||||||
storekey := api.ctrl.symKeyIndex[symkeyid]
|
storekey := api.ctrl.getSymKey(symkeyid)
|
||||||
if storekey == nil {
|
if storekey == nil {
|
||||||
return 0, fmt.Errorf("invalid symkey id %s", symkeyid)
|
return 0, fmt.Errorf("invalid symkey id %s", symkeyid)
|
||||||
}
|
}
|
||||||
|
|
@ -529,7 +559,7 @@ func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error
|
||||||
// Returns the byte representation of the public key in ascii hex
|
// Returns the byte representation of the public key in ascii hex
|
||||||
// associated with the given symmetric key
|
// associated with the given symmetric key
|
||||||
func (api *HandshakeAPI) GetHandshakePublicKey(symkeyid string) (string, error) {
|
func (api *HandshakeAPI) GetHandshakePublicKey(symkeyid string) (string, error) {
|
||||||
storekey := api.ctrl.symKeyIndex[symkeyid]
|
storekey := api.ctrl.getSymKey(symkeyid)
|
||||||
if storekey == nil {
|
if storekey == nil {
|
||||||
return "", fmt.Errorf("invalid symkey id %s", symkeyid)
|
return "", fmt.Errorf("invalid symkey id %s", symkeyid)
|
||||||
}
|
}
|
||||||
|
|
@ -555,12 +585,8 @@ func (api *HandshakeAPI) ReleaseHandshakeKey(pubkeyid string, topic Topic, symke
|
||||||
// for message expiry control
|
// for message expiry control
|
||||||
func (api *HandshakeAPI) SendSym(symkeyid string, topic Topic, msg hexutil.Bytes) (err error) {
|
func (api *HandshakeAPI) SendSym(symkeyid string, topic Topic, msg hexutil.Bytes) (err error) {
|
||||||
err = api.ctrl.pss.SendSym(symkeyid, topic, msg[:])
|
err = api.ctrl.pss.SendSym(symkeyid, topic, msg[:])
|
||||||
if api.ctrl.symKeyIndex[symkeyid] != nil {
|
if otherErr := api.ctrl.registerSymKeyUse(symkeyid); otherErr != nil {
|
||||||
if api.ctrl.symKeyIndex[symkeyid].count >= api.ctrl.symKeyIndex[symkeyid].limit {
|
return otherErr
|
||||||
return errors.New("attempted send with expired key")
|
|
||||||
}
|
|
||||||
api.ctrl.symKeyIndex[symkeyid].count++
|
|
||||||
log.Trace("increment symkey send use", "symkeyid", symkeyid, "count", api.ctrl.symKeyIndex[symkeyid].count, "limit", api.ctrl.symKeyIndex[symkeyid].limit, "receiver", common.ToHex(crypto.FromECDSAPub(api.ctrl.pss.PublicKey())))
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// +build foo
|
|
||||||
|
|
||||||
package pss
|
package pss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -30,7 +28,6 @@ import (
|
||||||
// asymmetrical key exchange between two directly connected peers
|
// asymmetrical key exchange between two directly connected peers
|
||||||
// full address, partial address (8 bytes) and empty address
|
// full address, partial address (8 bytes) and empty address
|
||||||
func TestHandshake(t *testing.T) {
|
func TestHandshake(t *testing.T) {
|
||||||
t.Skip("handshakes are not adapted to current pss core code")
|
|
||||||
t.Run("32", testHandshake)
|
t.Run("32", testHandshake)
|
||||||
t.Run("8", testHandshake)
|
t.Run("8", testHandshake)
|
||||||
t.Run("0", testHandshake)
|
t.Run("0", testHandshake)
|
||||||
|
|
@ -47,7 +44,7 @@ func testHandshake(t *testing.T) {
|
||||||
|
|
||||||
// set up two nodes directly connected
|
// set up two nodes directly connected
|
||||||
// (we are not testing pss routing here)
|
// (we are not testing pss routing here)
|
||||||
clients, err := setupNetwork(2)
|
clients, err := setupNetwork(2, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,8 @@ func (ks *Pss) processAsym(envelope *whisper.Envelope) (*whisper.ReceivedMessage
|
||||||
// - it is not marked as protected
|
// - it is not marked as protected
|
||||||
// - it is not in the incoming decryption cache
|
// - it is not in the incoming decryption cache
|
||||||
func (ks *Pss) cleanKeys() (count int) {
|
func (ks *Pss) cleanKeys() (count int) {
|
||||||
|
ks.mx.Lock()
|
||||||
|
defer ks.mx.Unlock()
|
||||||
for keyid, peertopics := range ks.symKeyPool {
|
for keyid, peertopics := range ks.symKeyPool {
|
||||||
var expiredtopics []Topic
|
var expiredtopics []Topic
|
||||||
for topic, psp := range peertopics {
|
for topic, psp := range peertopics {
|
||||||
|
|
@ -229,10 +231,8 @@ func (ks *Pss) cleanKeys() (count int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, topic := range expiredtopics {
|
for _, topic := range expiredtopics {
|
||||||
ks.mx.Lock()
|
|
||||||
delete(ks.symKeyPool[keyid], topic)
|
delete(ks.symKeyPool[keyid], topic)
|
||||||
log.Trace("symkey cleanup deletion", "symkeyid", keyid, "topic", topic, "val", ks.symKeyPool[keyid])
|
log.Trace("symkey cleanup deletion", "symkeyid", keyid, "topic", topic, "val", ks.symKeyPool[keyid])
|
||||||
ks.mx.Unlock()
|
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -676,7 +676,7 @@ func (p *Pss) send(to []byte, topic Topic, msg []byte, asymmetric bool, key []by
|
||||||
|
|
||||||
// sendFunc is a helper function that tries to send a message and returns true on success.
|
// sendFunc is a helper function that tries to send a message and returns true on success.
|
||||||
// It is set here for usage in production, and optionally overridden in tests.
|
// It is set here for usage in production, and optionally overridden in tests.
|
||||||
var sendFunc func(p *Pss, sp *network.Peer, msg *PssMsg) bool = sendMsg
|
var sendFunc = sendMsg
|
||||||
|
|
||||||
// tries to send a message, returns true if successful
|
// tries to send a message, returns true if successful
|
||||||
func sendMsg(p *Pss, sp *network.Peer, msg *PssMsg) bool {
|
func sendMsg(p *Pss, sp *network.Peer, msg *PssMsg) bool {
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
|
||||||
"github.com/ethereum/go-ethereum/metrics/influxdb"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
|
@ -1369,8 +1367,6 @@ func TestNetwork(t *testing.T) {
|
||||||
// nodes/msgs/addrbytes/adaptertype
|
// nodes/msgs/addrbytes/adaptertype
|
||||||
// if adaptertype is exec uses execadapter, simadapter otherwise
|
// if adaptertype is exec uses execadapter, simadapter otherwise
|
||||||
func TestNetwork2000(t *testing.T) {
|
func TestNetwork2000(t *testing.T) {
|
||||||
//enableMetrics()
|
|
||||||
|
|
||||||
if !*longrunning {
|
if !*longrunning {
|
||||||
t.Skip("run with --longrunning flag to run extensive network tests")
|
t.Skip("run with --longrunning flag to run extensive network tests")
|
||||||
}
|
}
|
||||||
|
|
@ -1381,8 +1377,6 @@ func TestNetwork2000(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetwork5000(t *testing.T) {
|
func TestNetwork5000(t *testing.T) {
|
||||||
//enableMetrics()
|
|
||||||
|
|
||||||
if !*longrunning {
|
if !*longrunning {
|
||||||
t.Skip("run with --longrunning flag to run extensive network tests")
|
t.Skip("run with --longrunning flag to run extensive network tests")
|
||||||
}
|
}
|
||||||
|
|
@ -1393,8 +1387,6 @@ func TestNetwork5000(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetwork10000(t *testing.T) {
|
func TestNetwork10000(t *testing.T) {
|
||||||
//enableMetrics()
|
|
||||||
|
|
||||||
if !*longrunning {
|
if !*longrunning {
|
||||||
t.Skip("run with --longrunning flag to run extensive network tests")
|
t.Skip("run with --longrunning flag to run extensive network tests")
|
||||||
}
|
}
|
||||||
|
|
@ -2098,11 +2090,3 @@ func (apitest *APITest) SetSymKeys(pubkeyid string, recvsymkey []byte, sendsymke
|
||||||
func (apitest *APITest) Clean() (int, error) {
|
func (apitest *APITest) Clean() (int, error) {
|
||||||
return apitest.Pss.cleanKeys(), nil
|
return apitest.Pss.cleanKeys(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableMetrics is starting InfluxDB reporter so that we collect stats when running tests locally
|
|
||||||
func enableMetrics() {
|
|
||||||
metrics.Enabled = true
|
|
||||||
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 1*time.Second, "http://localhost:8086", "metrics", "admin", "admin", "swarm.", map[string]string{
|
|
||||||
"host": "test",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
ch "github.com/ethereum/go-ethereum/swarm/chunk"
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
|
|
@ -127,7 +127,7 @@ type TreeChunker struct {
|
||||||
func TreeJoin(ctx context.Context, addr Address, getter Getter, depth int) *LazyChunkReader {
|
func TreeJoin(ctx context.Context, addr Address, getter Getter, depth int) *LazyChunkReader {
|
||||||
jp := &JoinerParams{
|
jp := &JoinerParams{
|
||||||
ChunkerParams: ChunkerParams{
|
ChunkerParams: ChunkerParams{
|
||||||
chunkSize: ch.DefaultSize,
|
chunkSize: chunk.DefaultSize,
|
||||||
hashSize: int64(len(addr)),
|
hashSize: int64(len(addr)),
|
||||||
},
|
},
|
||||||
addr: addr,
|
addr: addr,
|
||||||
|
|
@ -147,7 +147,7 @@ func TreeSplit(ctx context.Context, data io.Reader, size int64, putter Putter) (
|
||||||
tsp := &TreeSplitterParams{
|
tsp := &TreeSplitterParams{
|
||||||
SplitterParams: SplitterParams{
|
SplitterParams: SplitterParams{
|
||||||
ChunkerParams: ChunkerParams{
|
ChunkerParams: ChunkerParams{
|
||||||
chunkSize: ch.DefaultSize,
|
chunkSize: chunk.DefaultSize,
|
||||||
hashSize: putter.RefSize(),
|
hashSize: putter.RefSize(),
|
||||||
},
|
},
|
||||||
reader: data,
|
reader: data,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
ch "github.com/ethereum/go-ethereum/swarm/chunk"
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ func mput(store ChunkStore, n int, f func(i int64) Chunk) (hs []Chunk, err error
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
for i := int64(0); i < int64(n); i++ {
|
for i := int64(0); i < int64(n); i++ {
|
||||||
chunk := f(ch.DefaultSize)
|
chunk := f(chunk.DefaultSize)
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case errc <- store.Put(ctx, chunk):
|
case errc <- store.Put(ctx, chunk):
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@
|
||||||
|
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import "github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrInit = iota
|
ErrInit = iota
|
||||||
|
|
@ -31,7 +29,8 @@ const (
|
||||||
ErrNotSynced
|
ErrNotSynced
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Errors are the same as the ones in chunk package for backward compatibility.
|
||||||
var (
|
var (
|
||||||
ErrChunkNotFound = errors.New("chunk not found")
|
ErrChunkNotFound = chunk.ErrChunkNotFound
|
||||||
ErrChunkInvalid = errors.New("invalid chunk")
|
ErrChunkInvalid = chunk.ErrChunkNotFound
|
||||||
)
|
)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
ch "github.com/ethereum/go-ethereum/swarm/chunk"
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/encryption"
|
"github.com/ethereum/go-ethereum/swarm/storage/encryption"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
@ -156,7 +156,7 @@ func (h *hasherStore) createHash(chunkData ChunkData) Address {
|
||||||
return hasher.Sum(nil)
|
return hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hasherStore) createChunk(chunkData ChunkData) *chunk {
|
func (h *hasherStore) createChunk(chunkData ChunkData) Chunk {
|
||||||
hash := h.createHash(chunkData)
|
hash := h.createHash(chunkData)
|
||||||
chunk := NewChunk(hash, chunkData)
|
chunk := NewChunk(hash, chunkData)
|
||||||
return chunk
|
return chunk
|
||||||
|
|
@ -189,9 +189,9 @@ func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryp
|
||||||
|
|
||||||
// removing extra bytes which were just added for padding
|
// removing extra bytes which were just added for padding
|
||||||
length := ChunkData(decryptedSpan).Size()
|
length := ChunkData(decryptedSpan).Size()
|
||||||
for length > ch.DefaultSize {
|
for length > chunk.DefaultSize {
|
||||||
length = length + (ch.DefaultSize - 1)
|
length = length + (chunk.DefaultSize - 1)
|
||||||
length = length / ch.DefaultSize
|
length = length / chunk.DefaultSize
|
||||||
length *= uint64(h.refSize)
|
length *= uint64(h.refSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,14 +232,14 @@ func (h *hasherStore) decrypt(chunkData ChunkData, key encryption.Key) ([]byte,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hasherStore) newSpanEncryption(key encryption.Key) encryption.Encryption {
|
func (h *hasherStore) newSpanEncryption(key encryption.Key) encryption.Encryption {
|
||||||
return encryption.New(key, 0, uint32(ch.DefaultSize/h.refSize), sha3.NewLegacyKeccak256)
|
return encryption.New(key, 0, uint32(chunk.DefaultSize/h.refSize), sha3.NewLegacyKeccak256)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hasherStore) newDataEncryption(key encryption.Key) encryption.Encryption {
|
func (h *hasherStore) newDataEncryption(key encryption.Key) encryption.Encryption {
|
||||||
return encryption.New(key, int(ch.DefaultSize), 0, sha3.NewLegacyKeccak256)
|
return encryption.New(key, int(chunk.DefaultSize), 0, sha3.NewLegacyKeccak256)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hasherStore) storeChunk(ctx context.Context, chunk *chunk) {
|
func (h *hasherStore) storeChunk(ctx context.Context, chunk Chunk) {
|
||||||
atomic.AddUint64(&h.nrChunks, 1)
|
atomic.AddUint64(&h.nrChunks, 1)
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ func decodeIndex(data []byte, index *dpaDBIndex) error {
|
||||||
return dec.Decode(index)
|
return dec.Decode(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeData(addr Address, data []byte) (*chunk, error) {
|
func decodeData(addr Address, data []byte) (Chunk, error) {
|
||||||
return NewChunk(addr, data[32:]), nil
|
return NewChunk(addr, data[32:]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,7 +502,7 @@ func (s *LDBStore) Import(in io.Reader) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup iterates over the database and deletes chunks if they pass the `f` condition
|
// Cleanup iterates over the database and deletes chunks if they pass the `f` condition
|
||||||
func (s *LDBStore) Cleanup(f func(*chunk) bool) {
|
func (s *LDBStore) Cleanup(f func(Chunk) bool) {
|
||||||
var errorsFound, removed, total int
|
var errorsFound, removed, total int
|
||||||
|
|
||||||
it := s.db.NewIterator()
|
it := s.db.NewIterator()
|
||||||
|
|
@ -551,12 +551,14 @@ func (s *LDBStore) Cleanup(f func(*chunk) bool) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := int64(binary.LittleEndian.Uint64(c.sdata[:8]))
|
sdata := c.Data()
|
||||||
log.Trace("chunk", "key", fmt.Sprintf("%x", key), "ck", fmt.Sprintf("%x", ck), "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po, "len data", len(data), "len sdata", len(c.sdata), "size", cs)
|
|
||||||
|
cs := int64(binary.LittleEndian.Uint64(sdata[:8]))
|
||||||
|
log.Trace("chunk", "key", fmt.Sprintf("%x", key), "ck", fmt.Sprintf("%x", ck), "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po, "len data", len(data), "len sdata", len(sdata), "size", cs)
|
||||||
|
|
||||||
// if chunk is to be removed
|
// if chunk is to be removed
|
||||||
if f(c) {
|
if f(c) {
|
||||||
log.Warn("chunk for cleanup", "key", fmt.Sprintf("%x", key), "ck", fmt.Sprintf("%x", ck), "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po, "len data", len(data), "len sdata", len(c.sdata), "size", cs)
|
log.Warn("chunk for cleanup", "key", fmt.Sprintf("%x", key), "ck", fmt.Sprintf("%x", ck), "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po, "len data", len(data), "len sdata", len(sdata), "size", cs)
|
||||||
s.deleteNow(&index, getIndexKey(key[1:]), po)
|
s.deleteNow(&index, getIndexKey(key[1:]), po)
|
||||||
removed++
|
removed++
|
||||||
errorsFound++
|
errorsFound++
|
||||||
|
|
@ -980,7 +982,7 @@ func (s *LDBStore) Has(_ context.Context, addr Address) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: To conform with other private methods of this object indices should not be updated
|
// TODO: To conform with other private methods of this object indices should not be updated
|
||||||
func (s *LDBStore) get(addr Address) (chunk *chunk, err error) {
|
func (s *LDBStore) get(addr Address) (chunk Chunk, err error) {
|
||||||
if s.closed {
|
if s.closed {
|
||||||
return nil, ErrDBClosed
|
return nil, ErrDBClosed
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
ch "github.com/ethereum/go-ethereum/swarm/chunk"
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/mem"
|
"github.com/ethereum/go-ethereum/swarm/storage/mock/mem"
|
||||||
ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
|
ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
|
||||||
|
|
@ -103,7 +103,7 @@ func TestMarkAccessed(t *testing.T) {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := GenerateRandomChunk(ch.DefaultSize)
|
h := GenerateRandomChunk(chunk.DefaultSize)
|
||||||
|
|
||||||
db.Put(context.Background(), h)
|
db.Put(context.Background(), h)
|
||||||
|
|
||||||
|
|
@ -189,11 +189,11 @@ func TestMockDbStoreNotFound(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIterator(t *testing.T, mock bool) {
|
func testIterator(t *testing.T, mock bool) {
|
||||||
var chunkcount int = 32
|
|
||||||
var i int
|
var i int
|
||||||
var poc uint
|
var poc uint
|
||||||
|
chunkcount := 32
|
||||||
chunkkeys := NewAddressCollection(chunkcount)
|
chunkkeys := NewAddressCollection(chunkcount)
|
||||||
chunkkeys_results := NewAddressCollection(chunkcount)
|
chunkkeysResults := NewAddressCollection(chunkcount)
|
||||||
|
|
||||||
db, cleanup, err := newTestDbStore(mock, false)
|
db, cleanup, err := newTestDbStore(mock, false)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
@ -201,7 +201,7 @@ func testIterator(t *testing.T, mock bool) {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks := GenerateRandomChunks(ch.DefaultSize, chunkcount)
|
chunks := GenerateRandomChunks(chunk.DefaultSize, chunkcount)
|
||||||
|
|
||||||
for i = 0; i < len(chunks); i++ {
|
for i = 0; i < len(chunks); i++ {
|
||||||
chunkkeys[i] = chunks[i].Address()
|
chunkkeys[i] = chunks[i].Address()
|
||||||
|
|
@ -218,7 +218,7 @@ func testIterator(t *testing.T, mock bool) {
|
||||||
for poc = 0; poc <= 255; poc++ {
|
for poc = 0; poc <= 255; poc++ {
|
||||||
err := db.SyncIterator(0, uint64(chunkkeys.Len()), uint8(poc), func(k Address, n uint64) bool {
|
err := db.SyncIterator(0, uint64(chunkkeys.Len()), uint8(poc), func(k Address, n uint64) bool {
|
||||||
log.Trace(fmt.Sprintf("Got key %v number %d poc %d", k, n, uint8(poc)))
|
log.Trace(fmt.Sprintf("Got key %v number %d poc %d", k, n, uint8(poc)))
|
||||||
chunkkeys_results[n] = k
|
chunkkeysResults[n] = k
|
||||||
i++
|
i++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
@ -228,8 +228,8 @@ func testIterator(t *testing.T, mock bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i = 0; i < chunkcount; i++ {
|
for i = 0; i < chunkcount; i++ {
|
||||||
if !bytes.Equal(chunkkeys[i], chunkkeys_results[i]) {
|
if !bytes.Equal(chunkkeys[i], chunkkeysResults[i]) {
|
||||||
t.Fatalf("Chunk put #%d key '%v' does not match iterator's key '%v'", i, chunkkeys[i], chunkkeys_results[i])
|
t.Fatalf("Chunk put #%d key '%v' does not match iterator's key '%v'", i, chunkkeys[i], chunkkeysResults[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -468,7 +468,7 @@ func testLDBStoreRemoveThenCollectGarbage(t *testing.T) {
|
||||||
// put capacity count number of chunks
|
// put capacity count number of chunks
|
||||||
chunks := make([]Chunk, n)
|
chunks := make([]Chunk, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
c := GenerateRandomChunk(ch.DefaultSize)
|
c := GenerateRandomChunk(chunk.DefaultSize)
|
||||||
chunks[i] = c
|
chunks[i] = c
|
||||||
log.Trace("generate random chunk", "idx", i, "chunk", c)
|
log.Trace("generate random chunk", "idx", i, "chunk", c)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ func (ls *LocalStore) Migrate() error {
|
||||||
func (ls *LocalStore) migrateFromNoneToPurity() {
|
func (ls *LocalStore) migrateFromNoneToPurity() {
|
||||||
// delete chunks that are not valid, i.e. chunks that do not pass
|
// delete chunks that are not valid, i.e. chunks that do not pass
|
||||||
// any of the ls.Validators
|
// any of the ls.Validators
|
||||||
ls.DbStore.Cleanup(func(c *chunk) bool {
|
ls.DbStore.Cleanup(func(c Chunk) bool {
|
||||||
return !ls.isValid(c)
|
return !ls.isValid(c)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,8 @@ var (
|
||||||
// run. GC run iterates on gcIndex and removes older items
|
// run. GC run iterates on gcIndex and removes older items
|
||||||
// form retrieval and other indexes.
|
// form retrieval and other indexes.
|
||||||
func (db *DB) collectGarbageWorker() {
|
func (db *DB) collectGarbageWorker() {
|
||||||
|
defer close(db.collectGarbageWorkerDone)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-db.collectGarbageTrigger:
|
case <-db.collectGarbageTrigger:
|
||||||
|
|
@ -132,7 +134,7 @@ func (db *DB) collectGarbageWorker() {
|
||||||
db.triggerGarbageCollection()
|
db.triggerGarbageCollection()
|
||||||
}
|
}
|
||||||
|
|
||||||
if testHookCollectGarbage != nil {
|
if collectedCount > 0 && testHookCollectGarbage != nil {
|
||||||
testHookCollectGarbage(collectedCount)
|
testHookCollectGarbage(collectedCount)
|
||||||
}
|
}
|
||||||
case <-db.close:
|
case <-db.close:
|
||||||
|
|
@ -243,6 +245,8 @@ func (db *DB) triggerGarbageCollection() {
|
||||||
// writeGCSizeDelay duration to avoid very frequent
|
// writeGCSizeDelay duration to avoid very frequent
|
||||||
// database operations.
|
// database operations.
|
||||||
func (db *DB) writeGCSizeWorker() {
|
func (db *DB) writeGCSizeWorker() {
|
||||||
|
defer close(db.writeGCSizeWorkerDone)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-db.writeGCSizeTrigger:
|
case <-db.writeGCSizeTrigger:
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestDB_collectGarbageWorker tests garbage collection runs
|
// TestDB_collectGarbageWorker tests garbage collection runs
|
||||||
|
|
@ -64,11 +64,11 @@ func testDB_collectGarbageWorker(t *testing.T) {
|
||||||
uploader := db.NewPutter(ModePutUpload)
|
uploader := db.NewPutter(ModePutUpload)
|
||||||
syncer := db.NewSetter(ModeSetSync)
|
syncer := db.NewSetter(ModeSetSync)
|
||||||
|
|
||||||
addrs := make([]storage.Address, 0)
|
addrs := make([]chunk.Address, 0)
|
||||||
|
|
||||||
// upload random chunks
|
// upload random chunks
|
||||||
for i := 0; i < chunkCount; i++ {
|
for i := 0; i < chunkCount; i++ {
|
||||||
chunk := generateRandomChunk()
|
chunk := generateTestRandomChunk()
|
||||||
|
|
||||||
err := uploader.Put(chunk)
|
err := uploader.Put(chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -106,8 +106,8 @@ func testDB_collectGarbageWorker(t *testing.T) {
|
||||||
// the first synced chunk should be removed
|
// the first synced chunk should be removed
|
||||||
t.Run("get the first synced chunk", func(t *testing.T) {
|
t.Run("get the first synced chunk", func(t *testing.T) {
|
||||||
_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
|
_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
|
||||||
if err != storage.ErrChunkNotFound {
|
if err != chunk.ErrChunkNotFound {
|
||||||
t.Errorf("got error %v, want %v", err, storage.ErrChunkNotFound)
|
t.Errorf("got error %v, want %v", err, chunk.ErrChunkNotFound)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -118,15 +118,6 @@ func testDB_collectGarbageWorker(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// cleanup: drain the last testHookCollectGarbageChan
|
|
||||||
// element before calling deferred functions not to block
|
|
||||||
// collectGarbageWorker loop, preventing the race in
|
|
||||||
// setting testHookCollectGarbage function
|
|
||||||
select {
|
|
||||||
case <-testHookCollectGarbageChan:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDB_collectGarbageWorker_withRequests is a helper test function
|
// TestDB_collectGarbageWorker_withRequests is a helper test function
|
||||||
|
|
@ -146,11 +137,11 @@ func TestDB_collectGarbageWorker_withRequests(t *testing.T) {
|
||||||
testHookCollectGarbageChan <- collectedCount
|
testHookCollectGarbageChan <- collectedCount
|
||||||
})()
|
})()
|
||||||
|
|
||||||
addrs := make([]storage.Address, 0)
|
addrs := make([]chunk.Address, 0)
|
||||||
|
|
||||||
// upload random chunks just up to the capacity
|
// upload random chunks just up to the capacity
|
||||||
for i := 0; i < int(db.capacity)-1; i++ {
|
for i := 0; i < int(db.capacity)-1; i++ {
|
||||||
chunk := generateRandomChunk()
|
chunk := generateTestRandomChunk()
|
||||||
|
|
||||||
err := uploader.Put(chunk)
|
err := uploader.Put(chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -165,6 +156,14 @@ func TestDB_collectGarbageWorker_withRequests(t *testing.T) {
|
||||||
addrs = append(addrs, chunk.Address())
|
addrs = append(addrs, chunk.Address())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set update gc test hook to signal when
|
||||||
|
// update gc goroutine is done by closing
|
||||||
|
// testHookUpdateGCChan channel
|
||||||
|
testHookUpdateGCChan := make(chan struct{})
|
||||||
|
resetTestHookUpdateGC := setTestHookUpdateGC(func() {
|
||||||
|
close(testHookUpdateGCChan)
|
||||||
|
})
|
||||||
|
|
||||||
// request the latest synced chunk
|
// request the latest synced chunk
|
||||||
// to prioritize it in the gc index
|
// to prioritize it in the gc index
|
||||||
// not to be collected
|
// not to be collected
|
||||||
|
|
@ -173,18 +172,29 @@ func TestDB_collectGarbageWorker_withRequests(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait for update gc goroutine to finish for garbage
|
||||||
|
// collector to be correctly triggered after the last upload
|
||||||
|
select {
|
||||||
|
case <-testHookUpdateGCChan:
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Fatal("updateGC was not called after getting chunk with ModeGetRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to wait for update gc hook anymore
|
||||||
|
resetTestHookUpdateGC()
|
||||||
|
|
||||||
// upload and sync another chunk to trigger
|
// upload and sync another chunk to trigger
|
||||||
// garbage collection
|
// garbage collection
|
||||||
chunk := generateRandomChunk()
|
ch := generateTestRandomChunk()
|
||||||
err = uploader.Put(chunk)
|
err = uploader.Put(ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = syncer.Set(chunk.Address())
|
err = syncer.Set(ch.Address())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
addrs = append(addrs, chunk.Address())
|
addrs = append(addrs, ch.Address())
|
||||||
|
|
||||||
// wait for garbage collection
|
// wait for garbage collection
|
||||||
|
|
||||||
|
|
@ -226,8 +236,8 @@ func TestDB_collectGarbageWorker_withRequests(t *testing.T) {
|
||||||
// the second synced chunk should be removed
|
// the second synced chunk should be removed
|
||||||
t.Run("get gc-ed chunk", func(t *testing.T) {
|
t.Run("get gc-ed chunk", func(t *testing.T) {
|
||||||
_, err := db.NewGetter(ModeGetRequest).Get(addrs[1])
|
_, err := db.NewGetter(ModeGetRequest).Get(addrs[1])
|
||||||
if err != storage.ErrChunkNotFound {
|
if err != chunk.ErrChunkNotFound {
|
||||||
t.Errorf("got error %v, want %v", err, storage.ErrChunkNotFound)
|
t.Errorf("got error %v, want %v", err, chunk.ErrChunkNotFound)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -263,7 +273,7 @@ func TestDB_gcSize(t *testing.T) {
|
||||||
count := 100
|
count := 100
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
chunk := generateRandomChunk()
|
chunk := generateTestRandomChunk()
|
||||||
|
|
||||||
err := uploader.Put(chunk)
|
err := uploader.Put(chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -290,6 +300,7 @@ func TestDB_gcSize(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
t.Run("gc index size", newIndexGCSizeTest(db))
|
t.Run("gc index size", newIndexGCSizeTest(db))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestDB_pullIndex validates the ordering of keys in pull index.
|
// TestDB_pullIndex validates the ordering of keys in pull index.
|
||||||
|
|
@ -43,7 +43,7 @@ func TestDB_pullIndex(t *testing.T) {
|
||||||
|
|
||||||
// upload random chunks
|
// upload random chunks
|
||||||
for i := 0; i < chunkCount; i++ {
|
for i := 0; i < chunkCount; i++ {
|
||||||
chunk := generateRandomChunk()
|
chunk := generateTestRandomChunk()
|
||||||
|
|
||||||
err := uploader.Put(chunk)
|
err := uploader.Put(chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -62,8 +62,8 @@ func TestDB_pullIndex(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
testItemsOrder(t, db.pullIndex, chunks, func(i, j int) (less bool) {
|
testItemsOrder(t, db.pullIndex, chunks, func(i, j int) (less bool) {
|
||||||
poi := storage.Proximity(db.baseKey, chunks[i].Address())
|
poi := chunk.Proximity(db.baseKey, chunks[i].Address())
|
||||||
poj := storage.Proximity(db.baseKey, chunks[j].Address())
|
poj := chunk.Proximity(db.baseKey, chunks[j].Address())
|
||||||
if poi < poj {
|
if poi < poj {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ func TestDB_gcIndex(t *testing.T) {
|
||||||
|
|
||||||
// upload random chunks
|
// upload random chunks
|
||||||
for i := 0; i < chunkCount; i++ {
|
for i := 0; i < chunkCount; i++ {
|
||||||
chunk := generateRandomChunk()
|
chunk := generateTestRandomChunk()
|
||||||
|
|
||||||
err := uploader.Put(chunk)
|
err := uploader.Put(chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue