mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
commit
531a29ce8f
101 changed files with 2779 additions and 3129 deletions
|
|
@ -34,7 +34,7 @@ The go-ethereum project comes with several wrappers/executables found in the `cm
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|:----------:|-------------|
|
|:----------:|-------------|
|
||||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default) archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
||||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||||
|
|
|
||||||
1
VERSION
1
VERSION
|
|
@ -1 +0,0 @@
|
||||||
1.8.13
|
|
||||||
|
|
@ -56,9 +56,9 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
|
||||||
|
|
||||||
var newLastMod time.Time
|
var newLastMod time.Time
|
||||||
for _, fi := range files {
|
for _, fi := range files {
|
||||||
// Skip any non-key files from the folder
|
|
||||||
path := filepath.Join(keyDir, fi.Name())
|
path := filepath.Join(keyDir, fi.Name())
|
||||||
if skipKeyFile(fi) {
|
// Skip any non-key files from the folder
|
||||||
|
if nonKeyFile(fi) {
|
||||||
log.Trace("Ignoring file on account scan", "path", path)
|
log.Trace("Ignoring file on account scan", "path", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +88,8 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
|
||||||
return creates, deletes, updates, nil
|
return creates, deletes, updates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipKeyFile ignores editor backups, hidden files and folders/symlinks.
|
// nonKeyFile ignores editor backups, hidden files and folders/symlinks.
|
||||||
func skipKeyFile(fi os.FileInfo) bool {
|
func nonKeyFile(fi os.FileInfo) bool {
|
||||||
// Skip editor backups and UNIX-style hidden files.
|
// Skip editor backups and UNIX-style hidden files.
|
||||||
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
171
build/ci.go
171
build/ci.go
|
|
@ -59,6 +59,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/build"
|
"github.com/ethereum/go-ethereum/internal/build"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
sv "github.com/ethereum/go-ethereum/swarm/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -77,46 +79,77 @@ var (
|
||||||
executablePath("geth"),
|
executablePath("geth"),
|
||||||
executablePath("puppeth"),
|
executablePath("puppeth"),
|
||||||
executablePath("rlpdump"),
|
executablePath("rlpdump"),
|
||||||
executablePath("swarm"),
|
|
||||||
executablePath("wnode"),
|
executablePath("wnode"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Files that end up in the swarm*.zip archive.
|
||||||
|
swarmArchiveFiles = []string{
|
||||||
|
"COPYING",
|
||||||
|
executablePath("swarm"),
|
||||||
|
}
|
||||||
|
|
||||||
// A debian package is created for all executables listed here.
|
// A debian package is created for all executables listed here.
|
||||||
debExecutables = []debExecutable{
|
debExecutables = []debExecutable{
|
||||||
{
|
{
|
||||||
Name: "abigen",
|
BinaryName: "abigen",
|
||||||
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bootnode",
|
BinaryName: "bootnode",
|
||||||
Description: "Ethereum bootnode.",
|
Description: "Ethereum bootnode.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "evm",
|
BinaryName: "evm",
|
||||||
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "geth",
|
BinaryName: "geth",
|
||||||
Description: "Ethereum CLI client.",
|
Description: "Ethereum CLI client.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "puppeth",
|
BinaryName: "puppeth",
|
||||||
Description: "Ethereum private network manager.",
|
Description: "Ethereum private network manager.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "rlpdump",
|
BinaryName: "rlpdump",
|
||||||
Description: "Developer utility tool that prints RLP structures.",
|
Description: "Developer utility tool that prints RLP structures.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "swarm",
|
BinaryName: "wnode",
|
||||||
Description: "Ethereum Swarm daemon and tools",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "wnode",
|
|
||||||
Description: "Ethereum Whisper diagnostic tool",
|
Description: "Ethereum Whisper diagnostic tool",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A debian package is created for all executables listed here.
|
||||||
|
debSwarmExecutables = []debExecutable{
|
||||||
|
{
|
||||||
|
BinaryName: "swarm",
|
||||||
|
PackageName: "ethereum-swarm",
|
||||||
|
Description: "Ethereum Swarm daemon and tools",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
debEthereum = debPackage{
|
||||||
|
Name: "ethereum",
|
||||||
|
Version: params.Version,
|
||||||
|
Executables: debExecutables,
|
||||||
|
}
|
||||||
|
|
||||||
|
debSwarm = debPackage{
|
||||||
|
Name: "ethereum-swarm",
|
||||||
|
Version: sv.Version,
|
||||||
|
Executables: debSwarmExecutables,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debian meta packages to build and push to Ubuntu PPA
|
||||||
|
debPackages = []debPackage{
|
||||||
|
debSwarm,
|
||||||
|
debEthereum,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packages to be cross-compiled by the xgo command
|
||||||
|
allCrossCompiledArchiveFiles = append(allToolsArchiveFiles, swarmArchiveFiles...)
|
||||||
|
|
||||||
// Distros for which packages are created.
|
// Distros for which packages are created.
|
||||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||||
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
||||||
|
|
@ -351,7 +384,6 @@ func doLint(cmdline []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release Packaging
|
// Release Packaging
|
||||||
|
|
||||||
func doArchive(cmdline []string) {
|
func doArchive(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||||
|
|
@ -372,9 +404,13 @@ func doArchive(cmdline []string) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
env = build.Env()
|
env = build.Env()
|
||||||
base = archiveBasename(*arch, env)
|
|
||||||
geth = "geth-" + base + ext
|
basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit))
|
||||||
alltools = "geth-alltools-" + base + ext
|
geth = "geth-" + basegeth + ext
|
||||||
|
alltools = "geth-alltools-" + basegeth + ext
|
||||||
|
|
||||||
|
baseswarm = archiveBasename(*arch, sv.ArchiveVersion(env.Commit))
|
||||||
|
swarm = "swarm-" + baseswarm + ext
|
||||||
)
|
)
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
||||||
|
|
@ -383,14 +419,17 @@ func doArchive(cmdline []string) {
|
||||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, archive := range []string{geth, alltools} {
|
if err := build.WriteArchive(swarm, swarmArchiveFiles); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, archive := range []string{geth, alltools, swarm} {
|
||||||
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveBasename(arch string, env build.Environment) string {
|
func archiveBasename(arch string, archiveVersion string) string {
|
||||||
platform := runtime.GOOS + "-" + arch
|
platform := runtime.GOOS + "-" + arch
|
||||||
if arch == "arm" {
|
if arch == "arm" {
|
||||||
platform += os.Getenv("GOARM")
|
platform += os.Getenv("GOARM")
|
||||||
|
|
@ -401,18 +440,7 @@ func archiveBasename(arch string, env build.Environment) string {
|
||||||
if arch == "ios" {
|
if arch == "ios" {
|
||||||
platform = "ios-all"
|
platform = "ios-all"
|
||||||
}
|
}
|
||||||
return platform + "-" + archiveVersion(env)
|
return platform + "-" + archiveVersion
|
||||||
}
|
|
||||||
|
|
||||||
func archiveVersion(env build.Environment) string {
|
|
||||||
version := build.VERSION()
|
|
||||||
if isUnstableBuild(env) {
|
|
||||||
version += "-unstable"
|
|
||||||
}
|
|
||||||
if env.Commit != "" {
|
|
||||||
version += "-" + env.Commit[:8]
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveUpload(archive string, blobstore string, signer string) error {
|
func archiveUpload(archive string, blobstore string, signer string) error {
|
||||||
|
|
@ -462,7 +490,6 @@ func maybeSkipArchive(env build.Environment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debian Packaging
|
// Debian Packaging
|
||||||
|
|
||||||
func doDebianSource(cmdline []string) {
|
func doDebianSource(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
||||||
|
|
@ -486,9 +513,10 @@ func doDebianSource(cmdline []string) {
|
||||||
build.MustRun(gpg)
|
build.MustRun(gpg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the packages.
|
// Create Debian packages and upload them
|
||||||
|
for _, pkg := range debPackages {
|
||||||
for _, distro := range debDistros {
|
for _, distro := range debDistros {
|
||||||
meta := newDebMetadata(distro, *signer, env, now)
|
meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
|
||||||
pkgdir := stageDebianSource(*workdir, meta)
|
pkgdir := stageDebianSource(*workdir, meta)
|
||||||
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
|
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
|
||||||
debuild.Dir = pkgdir
|
debuild.Dir = pkgdir
|
||||||
|
|
@ -504,6 +532,7 @@ func doDebianSource(cmdline []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makeWorkdir(wdflag string) string {
|
func makeWorkdir(wdflag string) string {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -525,9 +554,17 @@ func isUnstableBuild(env build.Environment) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type debPackage struct {
|
||||||
|
Name string // the name of the Debian package to produce, e.g. "ethereum", or "ethereum-swarm"
|
||||||
|
Version string // the clean version of the debPackage, e.g. 1.8.12 or 0.3.0, without any metadata
|
||||||
|
Executables []debExecutable // executables to be included in the package
|
||||||
|
}
|
||||||
|
|
||||||
type debMetadata struct {
|
type debMetadata struct {
|
||||||
Env build.Environment
|
Env build.Environment
|
||||||
|
|
||||||
|
PackageName string
|
||||||
|
|
||||||
// go-ethereum version being built. Note that this
|
// go-ethereum version being built. Note that this
|
||||||
// is not the debian package version. The package version
|
// is not the debian package version. The package version
|
||||||
// is constructed by VersionString.
|
// is constructed by VersionString.
|
||||||
|
|
@ -539,21 +576,33 @@ type debMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type debExecutable struct {
|
type debExecutable struct {
|
||||||
Name, Description string
|
PackageName string
|
||||||
|
BinaryName string
|
||||||
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebMetadata(distro, author string, env build.Environment, t time.Time) debMetadata {
|
// Package returns the name of the package if present, or
|
||||||
|
// fallbacks to BinaryName
|
||||||
|
func (d debExecutable) Package() string {
|
||||||
|
if d.PackageName != "" {
|
||||||
|
return d.PackageName
|
||||||
|
}
|
||||||
|
return d.BinaryName
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
|
||||||
if author == "" {
|
if author == "" {
|
||||||
// No signing key, use default author.
|
// No signing key, use default author.
|
||||||
author = "Ethereum Builds <fjl@ethereum.org>"
|
author = "Ethereum Builds <fjl@ethereum.org>"
|
||||||
}
|
}
|
||||||
return debMetadata{
|
return debMetadata{
|
||||||
|
PackageName: name,
|
||||||
Env: env,
|
Env: env,
|
||||||
Author: author,
|
Author: author,
|
||||||
Distro: distro,
|
Distro: distro,
|
||||||
Version: build.VERSION(),
|
Version: version,
|
||||||
Time: t.Format(time.RFC1123Z),
|
Time: t.Format(time.RFC1123Z),
|
||||||
Executables: debExecutables,
|
Executables: exes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -561,9 +610,9 @@ func newDebMetadata(distro, author string, env build.Environment, t time.Time) d
|
||||||
// on all executable packages.
|
// on all executable packages.
|
||||||
func (meta debMetadata) Name() string {
|
func (meta debMetadata) Name() string {
|
||||||
if isUnstableBuild(meta.Env) {
|
if isUnstableBuild(meta.Env) {
|
||||||
return "ethereum-unstable"
|
return meta.PackageName + "-unstable"
|
||||||
}
|
}
|
||||||
return "ethereum"
|
return meta.PackageName
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionString returns the debian version of the packages.
|
// VersionString returns the debian version of the packages.
|
||||||
|
|
@ -590,9 +639,20 @@ func (meta debMetadata) ExeList() string {
|
||||||
// ExeName returns the package name of an executable package.
|
// ExeName returns the package name of an executable package.
|
||||||
func (meta debMetadata) ExeName(exe debExecutable) string {
|
func (meta debMetadata) ExeName(exe debExecutable) string {
|
||||||
if isUnstableBuild(meta.Env) {
|
if isUnstableBuild(meta.Env) {
|
||||||
return exe.Name + "-unstable"
|
return exe.Package() + "-unstable"
|
||||||
}
|
}
|
||||||
return exe.Name
|
return exe.Package()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthereumSwarmPackageName returns the name of the swarm package based on
|
||||||
|
// environment, e.g. "ethereum-swarm-unstable", or "ethereum-swarm".
|
||||||
|
// This is needed so that we make sure that "ethereum" package,
|
||||||
|
// depends on and installs "ethereum-swarm"
|
||||||
|
func (meta debMetadata) EthereumSwarmPackageName() string {
|
||||||
|
if isUnstableBuild(meta.Env) {
|
||||||
|
return debSwarm.Name + "-unstable"
|
||||||
|
}
|
||||||
|
return debSwarm.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExeConflicts returns the content of the Conflicts field
|
// ExeConflicts returns the content of the Conflicts field
|
||||||
|
|
@ -607,7 +667,7 @@ func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||||
// be preferred and the conflicting files should be handled via
|
// be preferred and the conflicting files should be handled via
|
||||||
// alternates. We might do this eventually but using a conflict is
|
// alternates. We might do this eventually but using a conflict is
|
||||||
// easier now.
|
// easier now.
|
||||||
return "ethereum, " + exe.Name
|
return "ethereum, " + exe.Package()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -624,24 +684,23 @@ func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
|
||||||
|
|
||||||
// Put the debian build files in place.
|
// Put the debian build files in place.
|
||||||
debian := filepath.Join(pkgdir, "debian")
|
debian := filepath.Join(pkgdir, "debian")
|
||||||
build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
||||||
build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
||||||
build.Render("build/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
||||||
build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
||||||
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
||||||
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
||||||
for _, exe := range meta.Executables {
|
for _, exe := range meta.Executables {
|
||||||
install := filepath.Join(debian, meta.ExeName(exe)+".install")
|
install := filepath.Join(debian, meta.ExeName(exe)+".install")
|
||||||
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
|
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
|
||||||
build.Render("build/deb.install", install, 0644, exe)
|
build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe)
|
||||||
build.Render("build/deb.docs", docs, 0644, exe)
|
build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgdir
|
return pkgdir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows installer
|
// Windows installer
|
||||||
|
|
||||||
func doWindowsInstaller(cmdline []string) {
|
func doWindowsInstaller(cmdline []string) {
|
||||||
// Parse the flags and make skip installer generation on PRs
|
// Parse the flags and make skip installer generation on PRs
|
||||||
var (
|
var (
|
||||||
|
|
@ -691,11 +750,11 @@ func doWindowsInstaller(cmdline []string) {
|
||||||
// Build the installer. This assumes that all the needed files have been previously
|
// Build the installer. This assumes that all the needed files have been previously
|
||||||
// built (don't mix building and packaging to keep cross compilation complexity to a
|
// built (don't mix building and packaging to keep cross compilation complexity to a
|
||||||
// minimum).
|
// minimum).
|
||||||
version := strings.Split(build.VERSION(), ".")
|
version := strings.Split(params.Version, ".")
|
||||||
if env.Commit != "" {
|
if env.Commit != "" {
|
||||||
version[2] += "-" + env.Commit[:8]
|
version[2] += "-" + env.Commit[:8]
|
||||||
}
|
}
|
||||||
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, env) + ".exe")
|
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe")
|
||||||
build.MustRunCommand("makensis.exe",
|
build.MustRunCommand("makensis.exe",
|
||||||
"/DOUTPUTFILE="+installer,
|
"/DOUTPUTFILE="+installer,
|
||||||
"/DMAJORVERSION="+version[0],
|
"/DMAJORVERSION="+version[0],
|
||||||
|
|
@ -747,7 +806,7 @@ func doAndroidArchive(cmdline []string) {
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
|
|
||||||
// Sign and upload the archive to Azure
|
// Sign and upload the archive to Azure
|
||||||
archive := "geth-" + archiveBasename("android", env) + ".aar"
|
archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar"
|
||||||
os.Rename("geth.aar", archive)
|
os.Rename("geth.aar", archive)
|
||||||
|
|
||||||
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
||||||
|
|
@ -832,7 +891,7 @@ func newMavenMetadata(env build.Environment) mavenMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Render the version and package strings
|
// Render the version and package strings
|
||||||
version := build.VERSION()
|
version := params.Version
|
||||||
if isUnstableBuild(env) {
|
if isUnstableBuild(env) {
|
||||||
version += "-SNAPSHOT"
|
version += "-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|
@ -867,7 +926,7 @@ func doXCodeFramework(cmdline []string) {
|
||||||
build.MustRun(bind)
|
build.MustRun(bind)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
archive := "geth-" + archiveBasename("ios", env)
|
archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit))
|
||||||
if err := os.Mkdir(archive, os.ModePerm); err != nil {
|
if err := os.Mkdir(archive, os.ModePerm); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -923,7 +982,7 @@ func newPodMetadata(env build.Environment, archive string) podMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version := build.VERSION()
|
version := params.Version
|
||||||
if isUnstableBuild(env) {
|
if isUnstableBuild(env) {
|
||||||
version += "-unstable." + env.Buildnum
|
version += "-unstable." + env.Buildnum
|
||||||
}
|
}
|
||||||
|
|
@ -953,7 +1012,7 @@ func doXgo(cmdline []string) {
|
||||||
|
|
||||||
if *alltools {
|
if *alltools {
|
||||||
args = append(args, []string{"--dest", GOBIN}...)
|
args = append(args, []string{"--dest", GOBIN}...)
|
||||||
for _, res := range allToolsArchiveFiles {
|
for _, res := range allCrossCompiledArchiveFiles {
|
||||||
if strings.HasPrefix(res, GOBIN) {
|
if strings.HasPrefix(res, GOBIN) {
|
||||||
// Binary tool found, cross build it explicitly
|
// Binary tool found, cross build it explicitly
|
||||||
args = append(args, "./"+filepath.Join("cmd", filepath.Base(res)))
|
args = append(args, "./"+filepath.Join("cmd", filepath.Base(res)))
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
build/bin/{{.Name}} usr/bin
|
|
||||||
19
build/deb/ethereum-swarm/deb.control
Normal file
19
build/deb/ethereum-swarm/deb.control
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Source: {{.Name}}
|
||||||
|
Section: science
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: {{.Author}}
|
||||||
|
Build-Depends: debhelper (>= 8.0.0), golang-1.10
|
||||||
|
Standards-Version: 3.9.5
|
||||||
|
Homepage: https://ethereum.org
|
||||||
|
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||||
|
Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
|
|
||||||
|
{{range .Executables}}
|
||||||
|
Package: {{$.ExeName .}}
|
||||||
|
Conflicts: {{$.ExeConflicts .}}
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Built-Using: ${misc:Built-Using}
|
||||||
|
Description: {{.Description}}
|
||||||
|
{{.Description}}
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright 2016 The go-ethereum Authors
|
Copyright 2018 The go-ethereum Authors
|
||||||
|
|
||||||
go-ethereum is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
1
build/deb/ethereum-swarm/deb.install
Normal file
1
build/deb/ethereum-swarm/deb.install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build/bin/{{.BinaryName}} usr/bin
|
||||||
5
build/deb/ethereum/deb.changelog
Normal file
5
build/deb/ethereum/deb.changelog
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||||
|
|
||||||
|
* git build of {{.Env.Commit}}
|
||||||
|
|
||||||
|
-- {{.Author}} {{.Time}}
|
||||||
|
|
@ -10,9 +10,9 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
|
|
||||||
Package: {{.Name}}
|
Package: {{.Name}}
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${misc:Depends}, {{.ExeList}}
|
Depends: ${misc:Depends}, {{.EthereumSwarmPackageName}}, {{.ExeList}}
|
||||||
Description: Meta-package to install geth and other tools
|
Description: Meta-package to install geth, swarm, and other tools
|
||||||
Meta-package to install geth and other tools
|
Meta-package to install geth, swarm and other tools
|
||||||
|
|
||||||
{{range .Executables}}
|
{{range .Executables}}
|
||||||
Package: {{$.ExeName .}}
|
Package: {{$.ExeName .}}
|
||||||
14
build/deb/ethereum/deb.copyright
Normal file
14
build/deb/ethereum/deb.copyright
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Copyright 2018 The go-ethereum Authors
|
||||||
|
|
||||||
|
go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
1
build/deb/ethereum/deb.docs
Normal file
1
build/deb/ethereum/deb.docs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
AUTHORS
|
||||||
1
build/deb/ethereum/deb.install
Normal file
1
build/deb/ethereum/deb.install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build/bin/{{.BinaryName}} usr/bin
|
||||||
13
build/deb/ethereum/deb.rules
Normal file
13
build/deb/ethereum/deb.rules
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
build/env.sh /usr/lib/go-1.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}}
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
@ -415,7 +415,7 @@ func signer(c *cli.Context) error {
|
||||||
|
|
||||||
// start http server
|
// start http server
|
||||||
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
|
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
|
||||||
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts)
|
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not start RPC api: %v", err)
|
utils.Fatalf("Could not start RPC api: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
||||||
// Assemble the raw devp2p protocol stack
|
// Assemble the raw devp2p protocol stack
|
||||||
stack, err := node.New(&node.Config{
|
stack, err := node.New(&node.Config{
|
||||||
Name: "geth",
|
Name: "geth",
|
||||||
Version: params.Version,
|
Version: params.VersionWithMeta,
|
||||||
DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
|
DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
|
||||||
P2P: p2p.Config{
|
P2P: p2p.Config{
|
||||||
NAT: nat.Any(),
|
NAT: nat.Any(),
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func reportBug(ctx *cli.Context) error {
|
||||||
|
|
||||||
fmt.Fprintln(&buff, "#### System information")
|
fmt.Fprintln(&buff, "#### System information")
|
||||||
fmt.Fprintln(&buff)
|
fmt.Fprintln(&buff)
|
||||||
fmt.Fprintln(&buff, "Version:", params.Version)
|
fmt.Fprintln(&buff, "Version:", params.VersionWithMeta)
|
||||||
fmt.Fprintln(&buff, "Go Version:", runtime.Version())
|
fmt.Fprintln(&buff, "Go Version:", runtime.Version())
|
||||||
fmt.Fprintln(&buff, "OS:", runtime.GOOS)
|
fmt.Fprintln(&buff, "OS:", runtime.GOOS)
|
||||||
printOSDetails(&buff)
|
printOSDetails(&buff)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
|
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
|
||||||
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
|
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ func TestConsoleWelcome(t *testing.T) {
|
||||||
geth.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
geth.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||||
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
geth.SetTemplateFunc("gover", runtime.Version)
|
geth.SetTemplateFunc("gover", runtime.Version)
|
||||||
geth.SetTemplateFunc("gethver", func() string { return params.Version })
|
geth.SetTemplateFunc("gethver", func() string { return params.VersionWithMeta })
|
||||||
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||||
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
attach.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
attach.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||||
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
attach.SetTemplateFunc("gover", runtime.Version)
|
attach.SetTemplateFunc("gover", runtime.Version)
|
||||||
attach.SetTemplateFunc("gethver", func() string { return params.Version })
|
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithMeta })
|
||||||
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
||||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||||
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ func makedag(ctx *cli.Context) error {
|
||||||
|
|
||||||
func version(ctx *cli.Context) error {
|
func version(ctx *cli.Context) error {
|
||||||
fmt.Println(strings.Title(clientIdentifier))
|
fmt.Println(strings.Title(clientIdentifier))
|
||||||
fmt.Println("Version:", params.Version)
|
fmt.Println("Version:", params.VersionWithMeta)
|
||||||
if gitCommit != "" {
|
if gitCommit != "" {
|
||||||
fmt.Println("Git Commit:", gitCommit)
|
fmt.Println("Git Commit:", gitCommit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -678,9 +678,9 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
|
||||||
|
|
||||||
// Build and deploy the dashboard service
|
// Build and deploy the dashboard service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dashboardInfos is returned from a dashboard status check to allow reporting
|
// dashboardInfos is returned from a dashboard status check to allow reporting
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,9 @@ func deployEthstats(client *sshClient, network string, port int, secret string,
|
||||||
|
|
||||||
// Build and deploy the ethstats service
|
// Build and deploy the ethstats service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ethstatsInfos is returned from an ethstats status check to allow reporting
|
// ethstatsInfos is returned from an ethstats status check to allow reporting
|
||||||
|
|
@ -122,7 +122,7 @@ func (info *ethstatsInfos) Report() map[string]string {
|
||||||
"Website address": info.host,
|
"Website address": info.host,
|
||||||
"Website listener port": strconv.Itoa(info.port),
|
"Website listener port": strconv.Itoa(info.port),
|
||||||
"Login secret": info.secret,
|
"Login secret": info.secret,
|
||||||
"Banned addresses": fmt.Sprintf("%v", info.banned),
|
"Banned addresses": strings.Join(info.banned, "\n"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ ADD chain.json /chain.json
|
||||||
RUN \
|
RUN \
|
||||||
echo '(cd ../eth-net-intelligence-api && pm2 start /ethstats.json)' > explorer.sh && \
|
echo '(cd ../eth-net-intelligence-api && pm2 start /ethstats.json)' > explorer.sh && \
|
||||||
echo '(cd ../etherchain-light && npm start &)' >> explorer.sh && \
|
echo '(cd ../etherchain-light && npm start &)' >> explorer.sh && \
|
||||||
echo '/parity/parity --chain=/chain.json --port={{.NodePort}} --tracing=on --fat-db=on --pruning=archive' >> explorer.sh
|
echo 'exec /parity/parity --chain=/chain.json --port={{.NodePort}} --tracing=on --fat-db=on --pruning=archive' >> explorer.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "explorer.sh"]
|
ENTRYPOINT ["/bin/sh", "explorer.sh"]
|
||||||
`
|
`
|
||||||
|
|
@ -140,9 +140,9 @@ func deployExplorer(client *sshClient, network string, chainspec []byte, config
|
||||||
|
|
||||||
// Build and deploy the boot or seal node service
|
// Build and deploy the boot or seal node service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// explorerInfos is returned from a block explorer status check to allow reporting
|
// explorerInfos is returned from a block explorer status check to allow reporting
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,9 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
||||||
|
|
||||||
// Build and deploy the faucet service
|
// Build and deploy the faucet service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// faucetInfos is returned from a faucet status check to allow reporting various
|
// faucetInfos is returned from a faucet status check to allow reporting various
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,9 @@ func deployNginx(client *sshClient, network string, port int, nocache bool) ([]b
|
||||||
|
|
||||||
// Build and deploy the reverse-proxy service
|
// Build and deploy the reverse-proxy service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// nginxInfos is returned from an nginx reverse-proxy status check to allow
|
// nginxInfos is returned from an nginx reverse-proxy status check to allow
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ ADD genesis.json /genesis.json
|
||||||
RUN \
|
RUN \
|
||||||
echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
|
echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
|
||||||
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
||||||
echo $'geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine --minerthreads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> geth.sh
|
echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine --minerthreads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> geth.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
||||||
`
|
`
|
||||||
|
|
@ -139,9 +139,9 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n
|
||||||
|
|
||||||
// Build and deploy the boot or seal node service
|
// Build and deploy the boot or seal node service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeInfos is returned from a boot or seal node status check to allow reporting
|
// nodeInfos is returned from a boot or seal node status check to allow reporting
|
||||||
|
|
@ -221,7 +221,7 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||||
|
|
||||||
// Container available, retrieve its node ID and its genesis json
|
// Container available, retrieve its node ID and its genesis json
|
||||||
var out []byte
|
var out []byte
|
||||||
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.id attach", network, kind)); err != nil {
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.id --cache=16 attach", network, kind)); err != nil {
|
||||||
return nil, ErrServiceUnreachable
|
return nil, ErrServiceUnreachable
|
||||||
}
|
}
|
||||||
id := bytes.Trim(bytes.TrimSpace(out), "\"")
|
id := bytes.Trim(bytes.TrimSpace(out), "\"")
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ ADD genesis.json /genesis.json
|
||||||
RUN \
|
RUN \
|
||||||
echo 'node server.js &' > wallet.sh && \
|
echo 'node server.js &' > wallet.sh && \
|
||||||
echo 'geth --cache 512 init /genesis.json' >> wallet.sh && \
|
echo 'geth --cache 512 init /genesis.json' >> wallet.sh && \
|
||||||
echo $'geth --networkid {{.NetworkID}} --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcaddr=0.0.0.0 --rpccorsdomain "*" --rpcvhosts "*"' >> wallet.sh
|
echo $'exec geth --networkid {{.NetworkID}} --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcaddr=0.0.0.0 --rpccorsdomain "*" --rpcvhosts "*"' >> wallet.sh
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
sed -i 's/PuppethNetworkID/{{.NetworkID}}/g' dist/js/etherwallet-master.js && \
|
sed -i 's/PuppethNetworkID/{{.NetworkID}}/g' dist/js/etherwallet-master.js && \
|
||||||
|
|
@ -120,9 +120,9 @@ func deployWallet(client *sshClient, network string, bootnodes []string, config
|
||||||
|
|
||||||
// Build and deploy the boot or seal node service
|
// Build and deploy the boot or seal node service
|
||||||
if nocache {
|
if nocache {
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
|
||||||
}
|
}
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// walletInfos is returned from a web wallet status check to allow reporting
|
// walletInfos is returned from a web wallet status check to allow reporting
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ func (stats serverStats) render() {
|
||||||
|
|
||||||
table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
|
table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||||
table.SetColWidth(100)
|
table.SetColWidth(40)
|
||||||
|
|
||||||
// Find the longest lines for all columns for the hacked separator
|
// Find the longest lines for all columns for the hacked separator
|
||||||
separator := make([]string, 5)
|
separator := make([]string, 5)
|
||||||
|
|
@ -222,8 +222,10 @@ func (stats serverStats) render() {
|
||||||
if len(config) > len(separator[3]) {
|
if len(config) > len(separator[3]) {
|
||||||
separator[3] = strings.Repeat("-", len(config))
|
separator[3] = strings.Repeat("-", len(config))
|
||||||
}
|
}
|
||||||
if len(value) > len(separator[4]) {
|
for _, val := range strings.Split(value, "\n") {
|
||||||
separator[4] = strings.Repeat("-", len(value))
|
if len(val) > len(separator[4]) {
|
||||||
|
separator[4] = strings.Repeat("-", len(val))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -263,13 +265,17 @@ func (stats serverStats) render() {
|
||||||
sort.Strings(configs)
|
sort.Strings(configs)
|
||||||
|
|
||||||
for k, config := range configs {
|
for k, config := range configs {
|
||||||
|
for l, value := range strings.Split(stats[server].services[service][config], "\n") {
|
||||||
switch {
|
switch {
|
||||||
case j == 0 && k == 0:
|
case j == 0 && k == 0 && l == 0:
|
||||||
table.Append([]string{server, stats[server].address, service, config, stats[server].services[service][config]})
|
table.Append([]string{server, stats[server].address, service, config, value})
|
||||||
case k == 0:
|
case k == 0 && l == 0:
|
||||||
table.Append([]string{"", "", service, config, stats[server].services[service][config]})
|
table.Append([]string{"", "", service, config, value})
|
||||||
|
case l == 0:
|
||||||
|
table.Append([]string{"", "", "", config, value})
|
||||||
default:
|
default:
|
||||||
table.Append([]string{"", "", "", config, stats[server].services[service][config]})
|
table.Append([]string{"", "", "", "", value})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,6 @@ import (
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SWARM_VERSION = "0.3.1-unstable"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
//flag definition for the dumpconfig command
|
//flag definition for the dumpconfig command
|
||||||
DumpConfigCommand = cli.Command{
|
DumpConfigCommand = cli.Command{
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ func listMounts(cliContext *cli.Context) {
|
||||||
mf := []fuse.MountInfo{}
|
mf := []fuse.MountInfo{}
|
||||||
err = client.CallContext(ctx, &mf, "swarmfs_listmounts")
|
err = client.CallContext(ctx, &mf, "swarmfs_listmounts")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("encountered an error calling the RPC endpoint while unmounting: %v", err)
|
utils.Fatalf("encountered an error calling the RPC endpoint while listing mounts: %v", err)
|
||||||
}
|
}
|
||||||
if len(mf) == 0 {
|
if len(mf) == 0 {
|
||||||
fmt.Print("Could not found any swarmfs mounts. Please make sure you've specified the correct RPC endpoint\n")
|
fmt.Print("Could not found any swarmfs mounts. Please make sure you've specified the correct RPC endpoint\n")
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ type testFile struct {
|
||||||
|
|
||||||
// TestCLISwarmFs is a high-level test of swarmfs
|
// TestCLISwarmFs is a high-level test of swarmfs
|
||||||
func TestCLISwarmFs(t *testing.T) {
|
func TestCLISwarmFs(t *testing.T) {
|
||||||
|
// This test fails on travis as this executable exits with code 1
|
||||||
|
// and without any log messages in the log.
|
||||||
|
// /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse
|
||||||
|
t.Skip()
|
||||||
|
|
||||||
cluster := newTestCluster(t, 3)
|
cluster := newTestCluster(t, 3)
|
||||||
defer cluster.Shutdown()
|
defer cluster.Shutdown()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,11 @@ import (
|
||||||
"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/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
|
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
|
||||||
"github.com/ethereum/go-ethereum/swarm/tracing"
|
"github.com/ethereum/go-ethereum/swarm/tracing"
|
||||||
|
sv "github.com/ethereum/go-ethereum/swarm/version"
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
@ -216,7 +216,7 @@ var defaultNodeConfig = node.DefaultConfig
|
||||||
// This init function sets defaults so cmd/swarm can run alongside geth.
|
// This init function sets defaults so cmd/swarm can run alongside geth.
|
||||||
func init() {
|
func init() {
|
||||||
defaultNodeConfig.Name = clientIdentifier
|
defaultNodeConfig.Name = clientIdentifier
|
||||||
defaultNodeConfig.Version = params.VersionWithCommit(gitCommit)
|
defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit)
|
||||||
defaultNodeConfig.P2P.ListenAddr = ":30399"
|
defaultNodeConfig.P2P.ListenAddr = ":30399"
|
||||||
defaultNodeConfig.IPCPath = "bzzd.ipc"
|
defaultNodeConfig.IPCPath = "bzzd.ipc"
|
||||||
// Set flag defaults for --help display.
|
// Set flag defaults for --help display.
|
||||||
|
|
@ -516,7 +516,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func version(ctx *cli.Context) error {
|
func version(ctx *cli.Context) error {
|
||||||
fmt.Println("Version:", SWARM_VERSION)
|
fmt.Println(strings.Title(clientIdentifier))
|
||||||
|
fmt.Println("Version:", sv.VersionWithMeta)
|
||||||
if gitCommit != "" {
|
if gitCommit != "" {
|
||||||
fmt.Println("Git Commit:", gitCommit)
|
fmt.Println("Git Commit:", gitCommit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func NewApp(gitCommit, usage string) *cli.App {
|
||||||
app.Author = ""
|
app.Author = ""
|
||||||
//app.Authors = nil
|
//app.Authors = nil
|
||||||
app.Email = ""
|
app.Email = ""
|
||||||
app.Version = params.Version
|
app.Version = params.VersionWithMeta
|
||||||
if len(gitCommit) >= 8 {
|
if len(gitCommit) >= 8 {
|
||||||
app.Version += "-" + gitCommit[:8]
|
app.Version += "-" + gitCommit[:8]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -672,6 +672,11 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
|
||||||
return new(big.Int).Set(diffNoTurn)
|
return new(big.Int).Set(diffNoTurn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads.
|
||||||
|
func (c *Clique) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// APIs implements consensus.Engine, returning the user facing RPC API to allow
|
// APIs implements consensus.Engine, returning the user facing RPC API to allow
|
||||||
// controlling the signer voting.
|
// controlling the signer voting.
|
||||||
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
|
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,9 @@ type Engine interface {
|
||||||
|
|
||||||
// APIs returns the RPC APIs this consensus engine provides.
|
// APIs returns the RPC APIs this consensus engine provides.
|
||||||
APIs(chain ChainReader) []rpc.API
|
APIs(chain ChainReader) []rpc.API
|
||||||
|
|
||||||
|
// Close terminates any background threads maintained by the consensus engine.
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PoW is a consensus engine based on proof-of-work.
|
// PoW is a consensus engine based on proof-of-work.
|
||||||
|
|
|
||||||
|
|
@ -730,6 +730,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
|
||||||
go func(idx int) {
|
go func(idx int) {
|
||||||
defer pend.Done()
|
defer pend.Done()
|
||||||
ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal})
|
ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal})
|
||||||
|
defer ethash.Close()
|
||||||
if err := ethash.VerifySeal(nil, block.Header()); err != nil {
|
if err := ethash.VerifySeal(nil, block.Header()); err != nil {
|
||||||
t.Errorf("proc %d: block verification failed: %v", idx, err)
|
t.Errorf("proc %d: block verification failed: %v", idx, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
117
consensus/ethash/api.go
Normal file
117
consensus/ethash/api.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
// 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 ethash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errEthashStopped = errors.New("ethash stopped")
|
||||||
|
|
||||||
|
// API exposes ethash related methods for the RPC interface.
|
||||||
|
type API struct {
|
||||||
|
ethash *Ethash // Make sure the mode of ethash is normal.
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWork returns a work package for external miner.
|
||||||
|
//
|
||||||
|
// The work package consists of 3 strings:
|
||||||
|
// result[0] - 32 bytes hex encoded current block header pow-hash
|
||||||
|
// result[1] - 32 bytes hex encoded seed hash used for DAG
|
||||||
|
// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||||
|
func (api *API) GetWork() ([3]string, error) {
|
||||||
|
if api.ethash.config.PowMode != ModeNormal && api.ethash.config.PowMode != ModeTest {
|
||||||
|
return [3]string{}, errors.New("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
workCh = make(chan [3]string, 1)
|
||||||
|
errc = make(chan error, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case api.ethash.fetchWorkCh <- &sealWork{errc: errc, res: workCh}:
|
||||||
|
case <-api.ethash.exitCh:
|
||||||
|
return [3]string{}, errEthashStopped
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case work := <-workCh:
|
||||||
|
return work, nil
|
||||||
|
case err := <-errc:
|
||||||
|
return [3]string{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitWork can be used by external miner to submit their POW solution.
|
||||||
|
// It returns an indication if the work was accepted.
|
||||||
|
// Note either an invalid solution, a stale work a non-existent work will return false.
|
||||||
|
func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest common.Hash) bool {
|
||||||
|
if api.ethash.config.PowMode != ModeNormal && api.ethash.config.PowMode != ModeTest {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var errc = make(chan error, 1)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case api.ethash.submitWorkCh <- &mineResult{
|
||||||
|
nonce: nonce,
|
||||||
|
mixDigest: digest,
|
||||||
|
hash: hash,
|
||||||
|
errc: errc,
|
||||||
|
}:
|
||||||
|
case <-api.ethash.exitCh:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err := <-errc
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitHashrate can be used for remote miners to submit their hash rate.
|
||||||
|
// This enables the node to report the combined hash rate of all miners
|
||||||
|
// which submit work through this node.
|
||||||
|
//
|
||||||
|
// It accepts the miner hash rate and an identifier which must be unique
|
||||||
|
// between nodes.
|
||||||
|
func (api *API) SubmitHashRate(rate hexutil.Uint64, id common.Hash) bool {
|
||||||
|
if api.ethash.config.PowMode != ModeNormal && api.ethash.config.PowMode != ModeTest {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var done = make(chan struct{}, 1)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case api.ethash.submitRateCh <- &hashrate{done: done, rate: uint64(rate), id: id}:
|
||||||
|
case <-api.ethash.exitCh:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until hash rate submitted successfully.
|
||||||
|
<-done
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHashrate returns the current hashrate for local CPU miner and remote miner.
|
||||||
|
func (api *API) GetHashrate() uint64 {
|
||||||
|
return uint64(api.ethash.Hashrate())
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,9 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
mmap "github.com/edsrzf/mmap-go"
|
mmap "github.com/edsrzf/mmap-go"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
@ -389,6 +391,30 @@ type Config struct {
|
||||||
PowMode Mode
|
PowMode Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mineResult wraps the pow solution parameters for the specified block.
|
||||||
|
type mineResult struct {
|
||||||
|
nonce types.BlockNonce
|
||||||
|
mixDigest common.Hash
|
||||||
|
hash common.Hash
|
||||||
|
|
||||||
|
errc chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashrate wraps the hash rate submitted by the remote sealer.
|
||||||
|
type hashrate struct {
|
||||||
|
id common.Hash
|
||||||
|
ping time.Time
|
||||||
|
rate uint64
|
||||||
|
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sealWork wraps a seal work package for remote sealer.
|
||||||
|
type sealWork struct {
|
||||||
|
errc chan error
|
||||||
|
res chan [3]string
|
||||||
|
}
|
||||||
|
|
||||||
// Ethash is a consensus engine based on proof-of-work implementing the ethash
|
// Ethash is a consensus engine based on proof-of-work implementing the ethash
|
||||||
// algorithm.
|
// algorithm.
|
||||||
type Ethash struct {
|
type Ethash struct {
|
||||||
|
|
@ -403,15 +429,25 @@ type Ethash struct {
|
||||||
update chan struct{} // Notification channel to update mining parameters
|
update chan struct{} // Notification channel to update mining parameters
|
||||||
hashrate metrics.Meter // Meter tracking the average hashrate
|
hashrate metrics.Meter // Meter tracking the average hashrate
|
||||||
|
|
||||||
|
// Remote sealer related fields
|
||||||
|
workCh chan *types.Block // Notification channel to push new work to remote sealer
|
||||||
|
resultCh chan *types.Block // Channel used by mining threads to return result
|
||||||
|
fetchWorkCh chan *sealWork // Channel used for remote sealer to fetch mining work
|
||||||
|
submitWorkCh chan *mineResult // Channel used for remote sealer to submit their mining result
|
||||||
|
fetchRateCh chan chan uint64 // Channel used to gather submitted hash rate for local or remote sealer.
|
||||||
|
submitRateCh chan *hashrate // Channel used for remote sealer to submit their mining hashrate
|
||||||
|
|
||||||
// The fields below are hooks for testing
|
// The fields below are hooks for testing
|
||||||
shared *Ethash // Shared PoW verifier to avoid cache regeneration
|
shared *Ethash // Shared PoW verifier to avoid cache regeneration
|
||||||
fakeFail uint64 // Block number which fails PoW check even in fake mode
|
fakeFail uint64 // Block number which fails PoW check even in fake mode
|
||||||
fakeDelay time.Duration // Time delay to sleep for before returning from verify
|
fakeDelay time.Duration // Time delay to sleep for before returning from verify
|
||||||
|
|
||||||
lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
|
lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
|
||||||
|
closeOnce sync.Once // Ensures exit channel will not be closed twice.
|
||||||
|
exitCh chan chan error // Notification channel to exiting backend threads
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a full sized ethash PoW scheme.
|
// New creates a full sized ethash PoW scheme and starts a background thread for remote mining.
|
||||||
func New(config Config) *Ethash {
|
func New(config Config) *Ethash {
|
||||||
if config.CachesInMem <= 0 {
|
if config.CachesInMem <= 0 {
|
||||||
log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem)
|
log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem)
|
||||||
|
|
@ -423,19 +459,43 @@ func New(config Config) *Ethash {
|
||||||
if config.DatasetDir != "" && config.DatasetsOnDisk > 0 {
|
if config.DatasetDir != "" && config.DatasetsOnDisk > 0 {
|
||||||
log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk)
|
log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk)
|
||||||
}
|
}
|
||||||
return &Ethash{
|
ethash := &Ethash{
|
||||||
config: config,
|
config: config,
|
||||||
caches: newlru("cache", config.CachesInMem, newCache),
|
caches: newlru("cache", config.CachesInMem, newCache),
|
||||||
datasets: newlru("dataset", config.DatasetsInMem, newDataset),
|
datasets: newlru("dataset", config.DatasetsInMem, newDataset),
|
||||||
update: make(chan struct{}),
|
update: make(chan struct{}),
|
||||||
hashrate: metrics.NewMeter(),
|
hashrate: metrics.NewMeter(),
|
||||||
|
workCh: make(chan *types.Block),
|
||||||
|
resultCh: make(chan *types.Block),
|
||||||
|
fetchWorkCh: make(chan *sealWork),
|
||||||
|
submitWorkCh: make(chan *mineResult),
|
||||||
|
fetchRateCh: make(chan chan uint64),
|
||||||
|
submitRateCh: make(chan *hashrate),
|
||||||
|
exitCh: make(chan chan error),
|
||||||
}
|
}
|
||||||
|
go ethash.remote()
|
||||||
|
return ethash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTester creates a small sized ethash PoW scheme useful only for testing
|
// NewTester creates a small sized ethash PoW scheme useful only for testing
|
||||||
// purposes.
|
// purposes.
|
||||||
func NewTester() *Ethash {
|
func NewTester() *Ethash {
|
||||||
return New(Config{CachesInMem: 1, PowMode: ModeTest})
|
ethash := &Ethash{
|
||||||
|
config: Config{PowMode: ModeTest},
|
||||||
|
caches: newlru("cache", 1, newCache),
|
||||||
|
datasets: newlru("dataset", 1, newDataset),
|
||||||
|
update: make(chan struct{}),
|
||||||
|
hashrate: metrics.NewMeter(),
|
||||||
|
workCh: make(chan *types.Block),
|
||||||
|
resultCh: make(chan *types.Block),
|
||||||
|
fetchWorkCh: make(chan *sealWork),
|
||||||
|
submitWorkCh: make(chan *mineResult),
|
||||||
|
fetchRateCh: make(chan chan uint64),
|
||||||
|
submitRateCh: make(chan *hashrate),
|
||||||
|
exitCh: make(chan chan error),
|
||||||
|
}
|
||||||
|
go ethash.remote()
|
||||||
|
return ethash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
|
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
|
||||||
|
|
@ -489,6 +549,22 @@ func NewShared() *Ethash {
|
||||||
return &Ethash{shared: sharedEthash}
|
return &Ethash{shared: sharedEthash}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes the exit channel to notify all backend threads exiting.
|
||||||
|
func (ethash *Ethash) Close() error {
|
||||||
|
var err error
|
||||||
|
ethash.closeOnce.Do(func() {
|
||||||
|
// Short circuit if the exit channel is not allocated.
|
||||||
|
if ethash.exitCh == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errc := make(chan error)
|
||||||
|
ethash.exitCh <- errc
|
||||||
|
err = <-errc
|
||||||
|
close(ethash.exitCh)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// cache tries to retrieve a verification cache for the specified block number
|
// cache tries to retrieve a verification cache for the specified block number
|
||||||
// by first checking against a list of in-memory caches, then against caches
|
// by first checking against a list of in-memory caches, then against caches
|
||||||
// stored on disk, and finally generating one if none can be found.
|
// stored on disk, and finally generating one if none can be found.
|
||||||
|
|
@ -561,14 +637,44 @@ func (ethash *Ethash) SetThreads(threads int) {
|
||||||
|
|
||||||
// Hashrate implements PoW, returning the measured rate of the search invocations
|
// Hashrate implements PoW, returning the measured rate of the search invocations
|
||||||
// per second over the last minute.
|
// per second over the last minute.
|
||||||
|
// Note the returned hashrate includes local hashrate, but also includes the total
|
||||||
|
// hashrate of all remote miner.
|
||||||
func (ethash *Ethash) Hashrate() float64 {
|
func (ethash *Ethash) Hashrate() float64 {
|
||||||
|
// Short circuit if we are run the ethash in normal/test mode.
|
||||||
|
if ethash.config.PowMode != ModeNormal && ethash.config.PowMode != ModeTest {
|
||||||
|
return ethash.hashrate.Rate1()
|
||||||
|
}
|
||||||
|
var res = make(chan uint64, 1)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case ethash.fetchRateCh <- res:
|
||||||
|
case <-ethash.exitCh:
|
||||||
|
// Return local hashrate only if ethash is stopped.
|
||||||
return ethash.hashrate.Rate1()
|
return ethash.hashrate.Rate1()
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIs implements consensus.Engine, returning the user facing RPC APIs. Currently
|
// Gather total submitted hash rate of remote sealers.
|
||||||
// that is empty.
|
return ethash.hashrate.Rate1() + float64(<-res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIs implements consensus.Engine, returning the user facing RPC APIs.
|
||||||
func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API {
|
func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API {
|
||||||
return nil
|
// In order to ensure backward compatibility, we exposes ethash RPC APIs
|
||||||
|
// to both eth and ethash namespaces.
|
||||||
|
return []rpc.API{
|
||||||
|
{
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: &API{ethash},
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Namespace: "ethash",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: &API{ethash},
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeedHash is the seed to use for generating a verification cache and the mining
|
// SeedHash is the seed to use for generating a verification cache and the mining
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -32,6 +35,7 @@ func TestTestMode(t *testing.T) {
|
||||||
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||||
|
|
||||||
ethash := NewTester()
|
ethash := NewTester()
|
||||||
|
defer ethash.Close()
|
||||||
block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil)
|
block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to seal block: %v", err)
|
t.Fatalf("failed to seal block: %v", err)
|
||||||
|
|
@ -52,6 +56,7 @@ func TestCacheFileEvict(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpdir)
|
defer os.RemoveAll(tmpdir)
|
||||||
e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest})
|
e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest})
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
workers := 8
|
workers := 8
|
||||||
epochs := 100
|
epochs := 100
|
||||||
|
|
@ -77,3 +82,90 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) {
|
||||||
e.VerifySeal(nil, head)
|
e.VerifySeal(nil, head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoteSealer(t *testing.T) {
|
||||||
|
ethash := NewTester()
|
||||||
|
defer ethash.Close()
|
||||||
|
api := &API{ethash}
|
||||||
|
if _, err := api.GetWork(); err != errNoMiningWork {
|
||||||
|
t.Error("expect to return an error indicate there is no mining work")
|
||||||
|
}
|
||||||
|
|
||||||
|
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||||
|
block := types.NewBlockWithHeader(head)
|
||||||
|
|
||||||
|
// Push new work.
|
||||||
|
ethash.Seal(nil, block, nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
work [3]string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
|
||||||
|
t.Error("expect to return a mining work has same hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
||||||
|
t.Error("expect to return false when submit a fake solution")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push new block with same block number to replace the original one.
|
||||||
|
head = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
|
||||||
|
block = types.NewBlockWithHeader(head)
|
||||||
|
ethash.Seal(nil, block, nil)
|
||||||
|
|
||||||
|
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
|
||||||
|
t.Error("expect to return the latest pushed work")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push block with higher block number.
|
||||||
|
newHead := &types.Header{Number: big.NewInt(2), Difficulty: big.NewInt(100)}
|
||||||
|
newBlock := types.NewBlockWithHeader(newHead)
|
||||||
|
ethash.Seal(nil, newBlock, nil)
|
||||||
|
|
||||||
|
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
||||||
|
t.Error("expect to return false when submit a stale solution")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashRate(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ethash = NewTester()
|
||||||
|
api = &API{ethash}
|
||||||
|
hashrate = []hexutil.Uint64{100, 200, 300}
|
||||||
|
expect uint64
|
||||||
|
ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")}
|
||||||
|
)
|
||||||
|
|
||||||
|
defer ethash.Close()
|
||||||
|
|
||||||
|
if tot := ethash.Hashrate(); tot != 0 {
|
||||||
|
t.Error("expect the result should be zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(hashrate); i += 1 {
|
||||||
|
if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
|
||||||
|
t.Error("remote miner submit hashrate failed")
|
||||||
|
}
|
||||||
|
expect += uint64(hashrate[i])
|
||||||
|
}
|
||||||
|
if tot := ethash.Hashrate(); tot != float64(expect) {
|
||||||
|
t.Error("expect total hashrate should be same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClosedRemoteSealer(t *testing.T) {
|
||||||
|
ethash := NewTester()
|
||||||
|
// Make sure exit channel has been listened
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
ethash.Close()
|
||||||
|
|
||||||
|
api := &API{ethash}
|
||||||
|
if _, err := api.GetWork(); err != errEthashStopped {
|
||||||
|
t.Error("expect to return an error to indicate ethash is stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := api.SubmitHashRate(hexutil.Uint64(100), common.HexToHash("a")); res {
|
||||||
|
t.Error("expect to return false when submit hashrate to a stopped ethash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ package ethash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -30,6 +32,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoMiningWork = errors.New("no mining work available yet")
|
||||||
|
errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
|
||||||
|
)
|
||||||
|
|
||||||
// Seal implements consensus.Engine, attempting to find a nonce that satisfies
|
// Seal implements consensus.Engine, attempting to find a nonce that satisfies
|
||||||
// the block's difficulty requirements.
|
// the block's difficulty requirements.
|
||||||
func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
||||||
|
|
@ -45,7 +52,6 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
|
||||||
}
|
}
|
||||||
// Create a runner and the multiple search threads it directs
|
// Create a runner and the multiple search threads it directs
|
||||||
abort := make(chan struct{})
|
abort := make(chan struct{})
|
||||||
found := make(chan *types.Block)
|
|
||||||
|
|
||||||
ethash.lock.Lock()
|
ethash.lock.Lock()
|
||||||
threads := ethash.threads
|
threads := ethash.threads
|
||||||
|
|
@ -64,12 +70,16 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
|
||||||
if threads < 0 {
|
if threads < 0 {
|
||||||
threads = 0 // Allows disabling local mining without extra logic around local/remote
|
threads = 0 // Allows disabling local mining without extra logic around local/remote
|
||||||
}
|
}
|
||||||
|
// Push new work to remote sealer
|
||||||
|
if ethash.workCh != nil {
|
||||||
|
ethash.workCh <- block
|
||||||
|
}
|
||||||
var pend sync.WaitGroup
|
var pend sync.WaitGroup
|
||||||
for i := 0; i < threads; i++ {
|
for i := 0; i < threads; i++ {
|
||||||
pend.Add(1)
|
pend.Add(1)
|
||||||
go func(id int, nonce uint64) {
|
go func(id int, nonce uint64) {
|
||||||
defer pend.Done()
|
defer pend.Done()
|
||||||
ethash.mine(block, id, nonce, abort, found)
|
ethash.mine(block, id, nonce, abort, ethash.resultCh)
|
||||||
}(i, uint64(ethash.rand.Int63()))
|
}(i, uint64(ethash.rand.Int63()))
|
||||||
}
|
}
|
||||||
// Wait until sealing is terminated or a nonce is found
|
// Wait until sealing is terminated or a nonce is found
|
||||||
|
|
@ -78,7 +88,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
|
||||||
case <-stop:
|
case <-stop:
|
||||||
// Outside abort, stop all miner threads
|
// Outside abort, stop all miner threads
|
||||||
close(abort)
|
close(abort)
|
||||||
case result = <-found:
|
case result = <-ethash.resultCh:
|
||||||
// One of the threads found a block, abort all others
|
// One of the threads found a block, abort all others
|
||||||
close(abort)
|
close(abort)
|
||||||
case <-ethash.update:
|
case <-ethash.update:
|
||||||
|
|
@ -150,3 +160,136 @@ search:
|
||||||
// during sealing so it's not unmapped while being read.
|
// during sealing so it's not unmapped while being read.
|
||||||
runtime.KeepAlive(dataset)
|
runtime.KeepAlive(dataset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remote starts a standalone goroutine to handle remote mining related stuff.
|
||||||
|
func (ethash *Ethash) remote() {
|
||||||
|
var (
|
||||||
|
works = make(map[common.Hash]*types.Block)
|
||||||
|
rates = make(map[common.Hash]hashrate)
|
||||||
|
currentWork *types.Block
|
||||||
|
)
|
||||||
|
|
||||||
|
// getWork returns a work package for external miner.
|
||||||
|
//
|
||||||
|
// The work package consists of 3 strings:
|
||||||
|
// result[0], 32 bytes hex encoded current block header pow-hash
|
||||||
|
// result[1], 32 bytes hex encoded seed hash used for DAG
|
||||||
|
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||||
|
getWork := func() ([3]string, error) {
|
||||||
|
var res [3]string
|
||||||
|
if currentWork == nil {
|
||||||
|
return res, errNoMiningWork
|
||||||
|
}
|
||||||
|
res[0] = currentWork.HashNoNonce().Hex()
|
||||||
|
res[1] = common.BytesToHash(SeedHash(currentWork.NumberU64())).Hex()
|
||||||
|
|
||||||
|
// Calculate the "target" to be returned to the external sealer.
|
||||||
|
n := big.NewInt(1)
|
||||||
|
n.Lsh(n, 255)
|
||||||
|
n.Div(n, currentWork.Difficulty())
|
||||||
|
n.Lsh(n, 1)
|
||||||
|
res[2] = common.BytesToHash(n.Bytes()).Hex()
|
||||||
|
|
||||||
|
// Trace the seal work fetched by remote sealer.
|
||||||
|
works[currentWork.HashNoNonce()] = currentWork
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// submitWork verifies the submitted pow solution, returning
|
||||||
|
// whether the solution was accepted or not (not can be both a bad pow as well as
|
||||||
|
// any other error, like no pending work or stale mining result).
|
||||||
|
submitWork := func(nonce types.BlockNonce, mixDigest common.Hash, hash common.Hash) bool {
|
||||||
|
// Make sure the work submitted is present
|
||||||
|
block := works[hash]
|
||||||
|
if block == nil {
|
||||||
|
log.Info("Work submitted but none pending", "hash", hash)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the correctness of submitted result.
|
||||||
|
header := block.Header()
|
||||||
|
header.Nonce = nonce
|
||||||
|
header.MixDigest = mixDigest
|
||||||
|
if err := ethash.VerifySeal(nil, header); err != nil {
|
||||||
|
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the result channel is created.
|
||||||
|
if ethash.resultCh == nil {
|
||||||
|
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solutions seems to be valid, return to the miner and notify acceptance.
|
||||||
|
select {
|
||||||
|
case ethash.resultCh <- block.WithSeal(header):
|
||||||
|
delete(works, hash)
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
log.Info("Work submitted is stale", "hash", hash)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case block := <-ethash.workCh:
|
||||||
|
if currentWork != nil && block.ParentHash() != currentWork.ParentHash() {
|
||||||
|
// Start new round mining, throw out all previous work.
|
||||||
|
works = make(map[common.Hash]*types.Block)
|
||||||
|
}
|
||||||
|
// Update current work with new received block.
|
||||||
|
// Note same work can be past twice, happens when changing CPU threads.
|
||||||
|
currentWork = block
|
||||||
|
|
||||||
|
case work := <-ethash.fetchWorkCh:
|
||||||
|
// Return current mining work to remote miner.
|
||||||
|
miningWork, err := getWork()
|
||||||
|
if err != nil {
|
||||||
|
work.errc <- err
|
||||||
|
} else {
|
||||||
|
work.res <- miningWork
|
||||||
|
}
|
||||||
|
|
||||||
|
case result := <-ethash.submitWorkCh:
|
||||||
|
// Verify submitted PoW solution based on maintained mining blocks.
|
||||||
|
if submitWork(result.nonce, result.mixDigest, result.hash) {
|
||||||
|
result.errc <- nil
|
||||||
|
} else {
|
||||||
|
result.errc <- errInvalidSealResult
|
||||||
|
}
|
||||||
|
|
||||||
|
case result := <-ethash.submitRateCh:
|
||||||
|
// Trace remote sealer's hash rate by submitted value.
|
||||||
|
rates[result.id] = hashrate{rate: result.rate, ping: time.Now()}
|
||||||
|
close(result.done)
|
||||||
|
|
||||||
|
case req := <-ethash.fetchRateCh:
|
||||||
|
// Gather all hash rate submitted by remote sealer.
|
||||||
|
var total uint64
|
||||||
|
for _, rate := range rates {
|
||||||
|
// this could overflow
|
||||||
|
total += rate.rate
|
||||||
|
}
|
||||||
|
req <- total
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
// Clear stale submitted hash rate.
|
||||||
|
for id, rate := range rates {
|
||||||
|
if time.Since(rate.ping) > 10*time.Second {
|
||||||
|
delete(rates, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case errc := <-ethash.exitCh:
|
||||||
|
// Exit remote loop if ethash is closed and return relevant error.
|
||||||
|
errc <- nil
|
||||||
|
log.Trace("Ethash remote sealer is exiting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1016,7 +1016,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
||||||
// Do a sanity check that the provided chain is actually ordered and linked
|
// Do a sanity check that the provided chain is actually ordered and linked
|
||||||
for i := 1; i < len(chain); i++ {
|
for i := 1; i < len(chain); i++ {
|
||||||
if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
|
if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
|
||||||
// Chain broke ancestry, log a messge (programming error) and skip insertion
|
// Chain broke ancestry, log a message (programming error) and skip insertion
|
||||||
log.Error("Non contiguous block insert", "number", chain[i].Number(), "hash", chain[i].Hash(),
|
log.Error("Non contiguous block insert", "number", chain[i].Number(), "hash", chain[i].Hash(),
|
||||||
"parent", chain[i].ParentHash(), "prevnumber", chain[i-1].Number(), "prevhash", chain[i-1].Hash())
|
"parent", chain[i].ParentHash(), "prevnumber", chain[i-1].Number(), "prevhash", chain[i-1].Hash())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1146,8 +1146,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||||
return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key)
|
return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
switch i {
|
if i == 0 {
|
||||||
case 0:
|
|
||||||
tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2)))
|
tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
|
||||||
// Do a sanity check that the provided chain is actually ordered and linked
|
// Do a sanity check that the provided chain is actually ordered and linked
|
||||||
for i := 1; i < len(chain); i++ {
|
for i := 1; i < len(chain); i++ {
|
||||||
if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
|
if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
|
||||||
// Chain broke ancestry, log a messge (programming error) and skip insertion
|
// Chain broke ancestry, log a message (programming error) and skip insertion
|
||||||
log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(),
|
log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(),
|
||||||
"parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash())
|
"parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,7 @@ func (l *txPricedList) Removed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap finds all the transactions below the given price threshold, drops them
|
// Cap finds all the transactions below the given price threshold, drops them
|
||||||
// from the priced list and returs them for further removal from the entire pool.
|
// from the priced list and returns them for further removal from the entire pool.
|
||||||
func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions {
|
func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions {
|
||||||
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
|
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
|
||||||
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
|
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ func (c *sha256hash) Run(input []byte) ([]byte, error) {
|
||||||
return h[:], nil
|
return h[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RIPMED160 implemented as a native contract.
|
// RIPEMD160 implemented as a native contract.
|
||||||
type ripemd160hash struct{}
|
type ripemd160hash struct{}
|
||||||
|
|
||||||
// RequiredGas returns the gas required to execute the pre-compiled contract.
|
// RequiredGas returns the gas required to execute the pre-compiled contract.
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
precompiles = PrecompiledContractsByzantium
|
precompiles = PrecompiledContractsByzantium
|
||||||
}
|
}
|
||||||
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
|
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
|
||||||
// Calling a non existing account, don't do antything, but ping the tracer
|
// Calling a non existing account, don't do anything, but ping the tracer
|
||||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||||
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
|
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,7 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpret
|
||||||
)
|
)
|
||||||
|
|
||||||
env.interpreter = evmInterpreter
|
env.interpreter = evmInterpreter
|
||||||
|
evmInterpreter.intPool = poolOfIntPools.get()
|
||||||
// convert args
|
// convert args
|
||||||
byteArgs := make([][]byte, len(args))
|
byteArgs := make([][]byte, len(args))
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
|
@ -229,6 +230,7 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpret
|
||||||
op(&pc, evmInterpreter, nil, nil, stack)
|
op(&pc, evmInterpreter, nil, nil, stack)
|
||||||
stack.pop()
|
stack.pop()
|
||||||
}
|
}
|
||||||
|
poolOfIntPools.put(evmInterpreter.intPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkOpAdd64(b *testing.B) {
|
func BenchmarkOpAdd64(b *testing.B) {
|
||||||
|
|
@ -474,6 +476,7 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||||
)
|
)
|
||||||
|
|
||||||
env.interpreter = evmInterpreter
|
env.interpreter = evmInterpreter
|
||||||
|
evmInterpreter.intPool = poolOfIntPools.get()
|
||||||
mem.Resize(64)
|
mem.Resize(64)
|
||||||
pc := uint64(0)
|
pc := uint64(0)
|
||||||
memStart := big.NewInt(0)
|
memStart := big.NewInt(0)
|
||||||
|
|
@ -484,4 +487,5 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||||
stack.pushN(value, memStart)
|
stack.pushN(value, memStart)
|
||||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||||
}
|
}
|
||||||
|
poolOfIntPools.put(evmInterpreter.intPool)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,7 @@ func (db *Dashboard) collectData() {
|
||||||
sys.ProcessCPU = append(sys.ProcessCPU[1:], processCPU)
|
sys.ProcessCPU = append(sys.ProcessCPU[1:], processCPU)
|
||||||
sys.SystemCPU = append(sys.SystemCPU[1:], systemCPU)
|
sys.SystemCPU = append(sys.SystemCPU[1:], systemCPU)
|
||||||
sys.DiskRead = append(sys.DiskRead[1:], diskRead)
|
sys.DiskRead = append(sys.DiskRead[1:], diskRead)
|
||||||
sys.DiskWrite = append(sys.DiskRead[1:], diskWrite)
|
sys.DiskWrite = append(sys.DiskWrite[1:], diskWrite)
|
||||||
db.lock.Unlock()
|
db.lock.Unlock()
|
||||||
|
|
||||||
db.sendToAll(&Message{
|
db.sendToAll(&Message{
|
||||||
|
|
|
||||||
44
eth/api.go
44
eth/api.go
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
@ -71,15 +70,11 @@ func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
|
||||||
// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
|
// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
|
||||||
type PublicMinerAPI struct {
|
type PublicMinerAPI struct {
|
||||||
e *Ethereum
|
e *Ethereum
|
||||||
agent *miner.RemoteAgent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicMinerAPI create a new PublicMinerAPI instance.
|
// NewPublicMinerAPI create a new PublicMinerAPI instance.
|
||||||
func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI {
|
func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI {
|
||||||
agent := miner.NewRemoteAgent(e.BlockChain(), e.Engine())
|
return &PublicMinerAPI{e}
|
||||||
e.Miner().Register(agent)
|
|
||||||
|
|
||||||
return &PublicMinerAPI{e, agent}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mining returns an indication if this node is currently mining.
|
// Mining returns an indication if this node is currently mining.
|
||||||
|
|
@ -87,37 +82,6 @@ func (api *PublicMinerAPI) Mining() bool {
|
||||||
return api.e.IsMining()
|
return api.e.IsMining()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitWork can be used by external miner to submit their POW solution. It returns an indication if the work was
|
|
||||||
// accepted. Note, this is not an indication if the provided work was valid!
|
|
||||||
func (api *PublicMinerAPI) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) bool {
|
|
||||||
return api.agent.SubmitWork(nonce, digest, solution)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWork returns a work package for external miner. The work package consists of 3 strings
|
|
||||||
// result[0], 32 bytes hex encoded current block header pow-hash
|
|
||||||
// result[1], 32 bytes hex encoded seed hash used for DAG
|
|
||||||
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
|
||||||
func (api *PublicMinerAPI) GetWork() ([3]string, error) {
|
|
||||||
if !api.e.IsMining() {
|
|
||||||
if err := api.e.StartMining(false); err != nil {
|
|
||||||
return [3]string{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
work, err := api.agent.GetWork()
|
|
||||||
if err != nil {
|
|
||||||
return work, fmt.Errorf("mining not ready: %v", err)
|
|
||||||
}
|
|
||||||
return work, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitHashrate can be used for remote miners to submit their hash rate. This enables the node to report the combined
|
|
||||||
// hash rate of all miners which submit work through this node. It accepts the miner hash rate and an identifier which
|
|
||||||
// must be unique between nodes.
|
|
||||||
func (api *PublicMinerAPI) SubmitHashrate(hashrate hexutil.Uint64, id common.Hash) bool {
|
|
||||||
api.agent.SubmitHashrate(id, uint64(hashrate))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateMinerAPI provides private RPC methods to control the miner.
|
// PrivateMinerAPI provides private RPC methods to control the miner.
|
||||||
// These methods can be abused by external users and must be considered insecure for use by untrusted users.
|
// These methods can be abused by external users and must be considered insecure for use by untrusted users.
|
||||||
type PrivateMinerAPI struct {
|
type PrivateMinerAPI struct {
|
||||||
|
|
@ -132,7 +96,8 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
|
||||||
// Start the miner with the given number of threads. If threads is nil the number
|
// Start the miner with the given number of threads. If threads is nil the number
|
||||||
// of workers started is equal to the number of logical CPUs that are usable by
|
// of workers started is equal to the number of logical CPUs that are usable by
|
||||||
// this process. If mining is already running, this method adjust the number of
|
// this process. If mining is already running, this method adjust the number of
|
||||||
// threads allowed to use.
|
// threads allowed to use and updates the minimum price required by the transaction
|
||||||
|
// pool.
|
||||||
func (api *PrivateMinerAPI) Start(threads *int) error {
|
func (api *PrivateMinerAPI) Start(threads *int) error {
|
||||||
// Set the number of threads if the seal engine supports it
|
// Set the number of threads if the seal engine supports it
|
||||||
if threads == nil {
|
if threads == nil {
|
||||||
|
|
@ -153,7 +118,6 @@ func (api *PrivateMinerAPI) Start(threads *int) error {
|
||||||
api.e.lock.RLock()
|
api.e.lock.RLock()
|
||||||
price := api.e.gasPrice
|
price := api.e.gasPrice
|
||||||
api.e.lock.RUnlock()
|
api.e.lock.RUnlock()
|
||||||
|
|
||||||
api.e.txPool.SetGasPrice(price)
|
api.e.txPool.SetGasPrice(price)
|
||||||
return api.e.StartMining(true)
|
return api.e.StartMining(true)
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +162,7 @@ func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
||||||
|
|
||||||
// GetHashrate returns the current hashrate of the miner.
|
// GetHashrate returns the current hashrate of the miner.
|
||||||
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
||||||
return uint64(api.e.miner.HashRate())
|
return api.e.miner.HashRate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateAdminAPI is the collection of Ethereum full node-related APIs
|
// PrivateAdminAPI is the collection of Ethereum full node-related APIs
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||||
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb); err != nil {
|
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
|
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
|
||||||
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
||||||
|
|
||||||
|
|
@ -411,6 +412,7 @@ func (s *Ethereum) Start(srvr *p2p.Server) error {
|
||||||
func (s *Ethereum) Stop() error {
|
func (s *Ethereum) Stop() error {
|
||||||
s.bloomIndexer.Close()
|
s.bloomIndexer.Close()
|
||||||
s.blockchain.Stop()
|
s.blockchain.Stop()
|
||||||
|
s.engine.Close()
|
||||||
s.protocolManager.Stop()
|
s.protocolManager.Stop()
|
||||||
if s.lesServer != nil {
|
if s.lesServer != nil {
|
||||||
s.lesServer.Stop()
|
s.lesServer.Stop()
|
||||||
|
|
@ -421,6 +423,5 @@ func (s *Ethereum) Stop() error {
|
||||||
|
|
||||||
s.chainDb.Close()
|
s.chainDb.Close()
|
||||||
close(s.shutdownChan)
|
close(s.shutdownChan)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -330,15 +330,13 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([
|
||||||
filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics)
|
filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics)
|
||||||
} else {
|
} else {
|
||||||
// Convert the RPC block numbers into internal representations
|
// Convert the RPC block numbers into internal representations
|
||||||
var (
|
begin := rpc.LatestBlockNumber.Int64()
|
||||||
begin int64
|
if crit.FromBlock != nil {
|
||||||
end int64
|
begin = crit.FromBlock.Int64()
|
||||||
)
|
|
||||||
if crit.FromBlock == nil {
|
|
||||||
begin = int64(rpc.LatestBlockNumber)
|
|
||||||
}
|
}
|
||||||
if crit.ToBlock == nil {
|
end := rpc.LatestBlockNumber.Int64()
|
||||||
end = int64(rpc.LatestBlockNumber)
|
if crit.ToBlock != nil {
|
||||||
|
end = crit.ToBlock.Int64()
|
||||||
}
|
}
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics)
|
filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics)
|
||||||
|
|
|
||||||
|
|
@ -338,8 +338,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *event.TypeMuxEvent:
|
case *event.TypeMuxEvent:
|
||||||
switch muxe := e.Data.(type) {
|
if muxe, ok := e.Data.(core.PendingLogsEvent); ok {
|
||||||
case core.PendingLogsEvent:
|
|
||||||
for _, f := range filters[PendingLogsSubscription] {
|
for _, f := range filters[PendingLogsSubscription] {
|
||||||
if e.Time.After(f.created) {
|
if e.Time.After(f.created) {
|
||||||
if matchedLogs := filterLogs(muxe.Logs, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
|
if matchedLogs := filterLogs(muxe.Logs, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -744,8 +744,7 @@ func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
|
||||||
func (pm *ProtocolManager) minedBroadcastLoop() {
|
func (pm *ProtocolManager) minedBroadcastLoop() {
|
||||||
// automatically stops if unsubscribe
|
// automatically stops if unsubscribe
|
||||||
for obj := range pm.minedBlockSub.Chan() {
|
for obj := range pm.minedBlockSub.Chan() {
|
||||||
switch ev := obj.Data.(type) {
|
if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
|
||||||
case core.NewMinedBlockEvent:
|
|
||||||
pm.BroadcastBlock(ev.Block, true) // First propagate block to peers
|
pm.BroadcastBlock(ev.Block, true) // First propagate block to peers
|
||||||
pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest
|
pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -486,12 +486,7 @@ func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(context string, err error) error {
|
func wrapError(context string, err error) error {
|
||||||
var message string
|
return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
|
||||||
switch err := err.(type) {
|
|
||||||
default:
|
|
||||||
message = err.Error()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%v in server-side tracer function '%v'", message, context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
||||||
|
|
|
||||||
|
|
@ -60,15 +60,6 @@ func GOPATH() string {
|
||||||
return os.Getenv("GOPATH")
|
return os.Getenv("GOPATH")
|
||||||
}
|
}
|
||||||
|
|
||||||
// VERSION returns the content of the VERSION file.
|
|
||||||
func VERSION() string {
|
|
||||||
version, err := ioutil.ReadFile("VERSION")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return string(bytes.TrimSpace(version))
|
|
||||||
}
|
|
||||||
|
|
||||||
var warnedAboutGit bool
|
var warnedAboutGit bool
|
||||||
|
|
||||||
// RunGit runs a git subcommand and returns its output.
|
// RunGit runs a git subcommand and returns its output.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ var Modules = map[string]string{
|
||||||
"admin": Admin_JS,
|
"admin": Admin_JS,
|
||||||
"chequebook": Chequebook_JS,
|
"chequebook": Chequebook_JS,
|
||||||
"clique": Clique_JS,
|
"clique": Clique_JS,
|
||||||
|
"ethash": Ethash_JS,
|
||||||
"debug": Debug_JS,
|
"debug": Debug_JS,
|
||||||
"eth": Eth_JS,
|
"eth": Eth_JS,
|
||||||
"miner": Miner_JS,
|
"miner": Miner_JS,
|
||||||
|
|
@ -109,6 +110,34 @@ web3._extend({
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const Ethash_JS = `
|
||||||
|
web3._extend({
|
||||||
|
property: 'ethash',
|
||||||
|
methods: [
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getWork',
|
||||||
|
call: 'ethash_getWork',
|
||||||
|
params: 0
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getHashrate',
|
||||||
|
call: 'ethash_getHashrate',
|
||||||
|
params: 0
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'submitWork',
|
||||||
|
call: 'ethash_submitWork',
|
||||||
|
params: 3,
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'submitHashRate',
|
||||||
|
call: 'ethash_submitHashRate',
|
||||||
|
params: 2,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
||||||
const Admin_JS = `
|
const Admin_JS = `
|
||||||
web3._extend({
|
web3._extend({
|
||||||
property: 'admin',
|
property: 'admin',
|
||||||
|
|
@ -123,6 +152,16 @@ web3._extend({
|
||||||
call: 'admin_removePeer',
|
call: 'admin_removePeer',
|
||||||
params: 1
|
params: 1
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'addTrustedPeer',
|
||||||
|
call: 'admin_addTrustedPeer',
|
||||||
|
params: 1
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'removeTrustedPeer',
|
||||||
|
call: 'admin_removeTrustedPeer',
|
||||||
|
params: 1
|
||||||
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'exportChain',
|
name: 'exportChain',
|
||||||
call: 'admin_exportChain',
|
call: 'admin_exportChain',
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ func (s *LightEthereum) Stop() error {
|
||||||
s.blockchain.Stop()
|
s.blockchain.Stop()
|
||||||
s.protocolManager.Stop()
|
s.protocolManager.Stop()
|
||||||
s.txPool.Stop()
|
s.txPool.Stop()
|
||||||
|
s.engine.Close()
|
||||||
|
|
||||||
s.eventMux.Stop()
|
s.eventMux.Stop()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1158,8 +1158,7 @@ func (pm *ProtocolManager) getHelperTrie(id uint, idx uint64) (common.Hash, stri
|
||||||
|
|
||||||
// getHelperTrieAuxData returns requested auxiliary data for the given HelperTrie request
|
// getHelperTrieAuxData returns requested auxiliary data for the given HelperTrie request
|
||||||
func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte {
|
func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte {
|
||||||
switch {
|
if req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8 {
|
||||||
case req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8:
|
|
||||||
blockNum := binary.BigEndian.Uint64(req.Key)
|
blockNum := binary.BigEndian.Uint64(req.Key)
|
||||||
hash := rawdb.ReadCanonicalHash(pm.chainDb, blockNum)
|
hash := rawdb.ReadCanonicalHash(pm.chainDb, blockNum)
|
||||||
return rawdb.ReadHeaderRLP(pm.chainDb, hash, blockNum)
|
return rawdb.ReadHeaderRLP(pm.chainDb, hash, blockNum)
|
||||||
|
|
|
||||||
|
|
@ -147,21 +147,21 @@ func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer
|
||||||
|
|
||||||
func (exp *exp) syncToExpvar() {
|
func (exp *exp) syncToExpvar() {
|
||||||
exp.registry.Each(func(name string, i interface{}) {
|
exp.registry.Each(func(name string, i interface{}) {
|
||||||
switch i.(type) {
|
switch i := i.(type) {
|
||||||
case metrics.Counter:
|
case metrics.Counter:
|
||||||
exp.publishCounter(name, i.(metrics.Counter))
|
exp.publishCounter(name, i)
|
||||||
case metrics.Gauge:
|
case metrics.Gauge:
|
||||||
exp.publishGauge(name, i.(metrics.Gauge))
|
exp.publishGauge(name, i)
|
||||||
case metrics.GaugeFloat64:
|
case metrics.GaugeFloat64:
|
||||||
exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
|
exp.publishGaugeFloat64(name, i)
|
||||||
case metrics.Histogram:
|
case metrics.Histogram:
|
||||||
exp.publishHistogram(name, i.(metrics.Histogram))
|
exp.publishHistogram(name, i)
|
||||||
case metrics.Meter:
|
case metrics.Meter:
|
||||||
exp.publishMeter(name, i.(metrics.Meter))
|
exp.publishMeter(name, i)
|
||||||
case metrics.Timer:
|
case metrics.Timer:
|
||||||
exp.publishTimer(name, i.(metrics.Timer))
|
exp.publishTimer(name, i)
|
||||||
case metrics.ResettingTimer:
|
case metrics.ResettingTimer:
|
||||||
exp.publishResettingTimer(name, i.(metrics.ResettingTimer))
|
exp.publishResettingTimer(name, i)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
|
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package miner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -28,32 +27,43 @@ import (
|
||||||
type CpuAgent struct {
|
type CpuAgent struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
workCh chan *Work
|
taskCh chan *Package
|
||||||
|
returnCh chan<- *Package
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
quitCurrentOp chan struct{}
|
quitCurrentOp chan struct{}
|
||||||
returnCh chan<- *Result
|
|
||||||
|
|
||||||
chain consensus.ChainReader
|
chain consensus.ChainReader
|
||||||
engine consensus.Engine
|
engine consensus.Engine
|
||||||
|
|
||||||
isMining int32 // isMining indicates whether the agent is currently mining
|
started int32 // started indicates whether the agent is currently started
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCpuAgent(chain consensus.ChainReader, engine consensus.Engine) *CpuAgent {
|
func NewCpuAgent(chain consensus.ChainReader, engine consensus.Engine) *CpuAgent {
|
||||||
miner := &CpuAgent{
|
agent := &CpuAgent{
|
||||||
chain: chain,
|
chain: chain,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
stop: make(chan struct{}, 1),
|
stop: make(chan struct{}, 1),
|
||||||
workCh: make(chan *Work, 1),
|
taskCh: make(chan *Package, 1),
|
||||||
}
|
}
|
||||||
return miner
|
return agent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CpuAgent) Work() chan<- *Work { return self.workCh }
|
func (self *CpuAgent) AssignTask(p *Package) {
|
||||||
func (self *CpuAgent) SetReturnCh(ch chan<- *Result) { self.returnCh = ch }
|
if atomic.LoadInt32(&self.started) == 1 {
|
||||||
|
self.taskCh <- p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (self *CpuAgent) DeliverTo(ch chan<- *Package) { self.returnCh = ch }
|
||||||
|
|
||||||
|
func (self *CpuAgent) Start() {
|
||||||
|
if !atomic.CompareAndSwapInt32(&self.started, 0, 1) {
|
||||||
|
return // agent already started
|
||||||
|
}
|
||||||
|
go self.update()
|
||||||
|
}
|
||||||
|
|
||||||
func (self *CpuAgent) Stop() {
|
func (self *CpuAgent) Stop() {
|
||||||
if !atomic.CompareAndSwapInt32(&self.isMining, 1, 0) {
|
if !atomic.CompareAndSwapInt32(&self.started, 1, 0) {
|
||||||
return // agent already stopped
|
return // agent already stopped
|
||||||
}
|
}
|
||||||
self.stop <- struct{}{}
|
self.stop <- struct{}{}
|
||||||
|
|
@ -61,31 +71,24 @@ done:
|
||||||
// Empty work channel
|
// Empty work channel
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-self.workCh:
|
case <-self.taskCh:
|
||||||
default:
|
default:
|
||||||
break done
|
break done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CpuAgent) Start() {
|
|
||||||
if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1) {
|
|
||||||
return // agent already started
|
|
||||||
}
|
|
||||||
go self.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *CpuAgent) update() {
|
func (self *CpuAgent) update() {
|
||||||
out:
|
out:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case work := <-self.workCh:
|
case p := <-self.taskCh:
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
if self.quitCurrentOp != nil {
|
if self.quitCurrentOp != nil {
|
||||||
close(self.quitCurrentOp)
|
close(self.quitCurrentOp)
|
||||||
}
|
}
|
||||||
self.quitCurrentOp = make(chan struct{})
|
self.quitCurrentOp = make(chan struct{})
|
||||||
go self.mine(work, self.quitCurrentOp)
|
go self.mine(p, self.quitCurrentOp)
|
||||||
self.mu.Unlock()
|
self.mu.Unlock()
|
||||||
case <-self.stop:
|
case <-self.stop:
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
|
|
@ -99,10 +102,11 @@ out:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
|
func (self *CpuAgent) mine(p *Package, stop <-chan struct{}) {
|
||||||
if result, err := self.engine.Seal(self.chain, work.Block, stop); result != nil {
|
var err error
|
||||||
log.Info("Successfully sealed new block", "number", result.Number(), "hash", result.Hash())
|
if p.Block, err = self.engine.Seal(self.chain, p.Block, stop); p.Block != nil {
|
||||||
self.returnCh <- &Result{work, result}
|
log.Info("Successfully sealed new block", "number", p.Block.Number(), "hash", p.Block.Hash())
|
||||||
|
self.returnCh <- p
|
||||||
} else {
|
} else {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Block sealing failed", "err", err)
|
log.Warn("Block sealing failed", "err", err)
|
||||||
|
|
@ -110,10 +114,3 @@ func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
|
||||||
self.returnCh <- nil
|
self.returnCh <- nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CpuAgent) GetHashRate() int64 {
|
|
||||||
if pow, ok := self.engine.(consensus.PoW); ok {
|
|
||||||
return int64(pow.Hashrate())
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,8 @@ type Backend interface {
|
||||||
// Miner creates blocks and searches for proof-of-work values.
|
// Miner creates blocks and searches for proof-of-work values.
|
||||||
type Miner struct {
|
type Miner struct {
|
||||||
mux *event.TypeMux
|
mux *event.TypeMux
|
||||||
|
|
||||||
worker *worker
|
worker *worker
|
||||||
|
|
||||||
coinbase common.Address
|
coinbase common.Address
|
||||||
mining int32
|
|
||||||
eth Backend
|
eth Backend
|
||||||
engine consensus.Engine
|
engine consensus.Engine
|
||||||
|
|
||||||
|
|
@ -62,7 +59,7 @@ func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine con
|
||||||
eth: eth,
|
eth: eth,
|
||||||
mux: mux,
|
mux: mux,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
worker: newWorker(config, engine, common.Address{}, eth, mux),
|
worker: newWorker(config, engine, eth, mux),
|
||||||
canStart: 1,
|
canStart: 1,
|
||||||
}
|
}
|
||||||
miner.Register(NewCpuAgent(eth.BlockChain(), engine))
|
miner.Register(NewCpuAgent(eth.BlockChain(), engine))
|
||||||
|
|
@ -111,23 +108,16 @@ func (self *Miner) Start(coinbase common.Address) {
|
||||||
log.Info("Network syncing, will start miner afterwards")
|
log.Info("Network syncing, will start miner afterwards")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
atomic.StoreInt32(&self.mining, 1)
|
|
||||||
|
|
||||||
log.Info("Starting mining operation")
|
|
||||||
self.worker.start()
|
self.worker.start()
|
||||||
self.worker.commitNewWork()
|
self.worker.commitNewWork()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Miner) Stop() {
|
func (self *Miner) Stop() {
|
||||||
self.worker.stop()
|
self.worker.stop()
|
||||||
atomic.StoreInt32(&self.mining, 0)
|
|
||||||
atomic.StoreInt32(&self.shouldStart, 0)
|
atomic.StoreInt32(&self.shouldStart, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Miner) Register(agent Agent) {
|
func (self *Miner) Register(agent Agent) {
|
||||||
if self.Mining() {
|
|
||||||
agent.Start()
|
|
||||||
}
|
|
||||||
self.worker.register(agent)
|
self.worker.register(agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,22 +126,14 @@ func (self *Miner) Unregister(agent Agent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Miner) Mining() bool {
|
func (self *Miner) Mining() bool {
|
||||||
return atomic.LoadInt32(&self.mining) > 0
|
return self.worker.isRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Miner) HashRate() (tot int64) {
|
func (self *Miner) HashRate() uint64 {
|
||||||
if pow, ok := self.engine.(consensus.PoW); ok {
|
if pow, ok := self.engine.(consensus.PoW); ok {
|
||||||
tot += int64(pow.Hashrate())
|
return uint64(pow.Hashrate())
|
||||||
}
|
}
|
||||||
// do we care this might race? is it worth we're rewriting some
|
return 0
|
||||||
// aspects of the worker/locking up agents so we can get an accurate
|
|
||||||
// hashrate?
|
|
||||||
for agent := range self.worker.agents {
|
|
||||||
if _, ok := agent.(*CpuAgent); !ok {
|
|
||||||
tot += agent.GetHashRate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Miner) SetExtra(extra []byte) error {
|
func (self *Miner) SetExtra(extra []byte) error {
|
||||||
|
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
// Copyright 2015 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 miner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type hashrate struct {
|
|
||||||
ping time.Time
|
|
||||||
rate uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteAgent struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
quitCh chan struct{}
|
|
||||||
workCh chan *Work
|
|
||||||
returnCh chan<- *Result
|
|
||||||
|
|
||||||
chain consensus.ChainReader
|
|
||||||
engine consensus.Engine
|
|
||||||
currentWork *Work
|
|
||||||
work map[common.Hash]*Work
|
|
||||||
|
|
||||||
hashrateMu sync.RWMutex
|
|
||||||
hashrate map[common.Hash]hashrate
|
|
||||||
|
|
||||||
running int32 // running indicates whether the agent is active. Call atomically
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteAgent(chain consensus.ChainReader, engine consensus.Engine) *RemoteAgent {
|
|
||||||
return &RemoteAgent{
|
|
||||||
chain: chain,
|
|
||||||
engine: engine,
|
|
||||||
work: make(map[common.Hash]*Work),
|
|
||||||
hashrate: make(map[common.Hash]hashrate),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) SubmitHashrate(id common.Hash, rate uint64) {
|
|
||||||
a.hashrateMu.Lock()
|
|
||||||
defer a.hashrateMu.Unlock()
|
|
||||||
|
|
||||||
a.hashrate[id] = hashrate{time.Now(), rate}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) Work() chan<- *Work {
|
|
||||||
return a.workCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) SetReturnCh(returnCh chan<- *Result) {
|
|
||||||
a.returnCh = returnCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) Start() {
|
|
||||||
if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.quitCh = make(chan struct{})
|
|
||||||
a.workCh = make(chan *Work, 1)
|
|
||||||
go a.loop(a.workCh, a.quitCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) Stop() {
|
|
||||||
if !atomic.CompareAndSwapInt32(&a.running, 1, 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(a.quitCh)
|
|
||||||
close(a.workCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHashRate returns the accumulated hashrate of all identifier combined
|
|
||||||
func (a *RemoteAgent) GetHashRate() (tot int64) {
|
|
||||||
a.hashrateMu.RLock()
|
|
||||||
defer a.hashrateMu.RUnlock()
|
|
||||||
|
|
||||||
// this could overflow
|
|
||||||
for _, hashrate := range a.hashrate {
|
|
||||||
tot += int64(hashrate.rate)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) GetWork() ([3]string, error) {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
var res [3]string
|
|
||||||
|
|
||||||
if a.currentWork != nil {
|
|
||||||
block := a.currentWork.Block
|
|
||||||
|
|
||||||
res[0] = block.HashNoNonce().Hex()
|
|
||||||
seedHash := ethash.SeedHash(block.NumberU64())
|
|
||||||
res[1] = common.BytesToHash(seedHash).Hex()
|
|
||||||
// Calculate the "target" to be returned to the external miner
|
|
||||||
n := big.NewInt(1)
|
|
||||||
n.Lsh(n, 255)
|
|
||||||
n.Div(n, block.Difficulty())
|
|
||||||
n.Lsh(n, 1)
|
|
||||||
res[2] = common.BytesToHash(n.Bytes()).Hex()
|
|
||||||
|
|
||||||
a.work[block.HashNoNonce()] = a.currentWork
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
return res, errors.New("No work available yet, don't panic.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitWork tries to inject a pow solution into the remote agent, returning
|
|
||||||
// whether the solution was accepted or not (not can be both a bad pow as well as
|
|
||||||
// any other error, like no work pending).
|
|
||||||
func (a *RemoteAgent) SubmitWork(nonce types.BlockNonce, mixDigest, hash common.Hash) bool {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
// Make sure the work submitted is present
|
|
||||||
work := a.work[hash]
|
|
||||||
if work == nil {
|
|
||||||
log.Info("Work submitted but none pending", "hash", hash)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Make sure the Engine solutions is indeed valid
|
|
||||||
result := work.Block.Header()
|
|
||||||
result.Nonce = nonce
|
|
||||||
result.MixDigest = mixDigest
|
|
||||||
|
|
||||||
if err := a.engine.VerifySeal(a.chain, result); err != nil {
|
|
||||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
block := work.Block.WithSeal(result)
|
|
||||||
|
|
||||||
// Solutions seems to be valid, return to the miner and notify acceptance
|
|
||||||
a.returnCh <- &Result{work, block}
|
|
||||||
delete(a.work, hash)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop monitors mining events on the work and quit channels, updating the internal
|
|
||||||
// state of the remote miner until a termination is requested.
|
|
||||||
//
|
|
||||||
// Note, the reason the work and quit channels are passed as parameters is because
|
|
||||||
// RemoteAgent.Start() constantly recreates these channels, so the loop code cannot
|
|
||||||
// assume data stability in these member fields.
|
|
||||||
func (a *RemoteAgent) loop(workCh chan *Work, quitCh chan struct{}) {
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quitCh:
|
|
||||||
return
|
|
||||||
case work := <-workCh:
|
|
||||||
a.mu.Lock()
|
|
||||||
a.currentWork = work
|
|
||||||
a.mu.Unlock()
|
|
||||||
case <-ticker.C:
|
|
||||||
// cleanup
|
|
||||||
a.mu.Lock()
|
|
||||||
for hash, work := range a.work {
|
|
||||||
if time.Since(work.createdAt) > 7*(12*time.Second) {
|
|
||||||
delete(a.work, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.mu.Unlock()
|
|
||||||
|
|
||||||
a.hashrateMu.Lock()
|
|
||||||
for id, hashrate := range a.hashrate {
|
|
||||||
if time.Since(hashrate.ping) > 10*time.Second {
|
|
||||||
delete(a.hashrate, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.hashrateMu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
miner/worker.go
191
miner/worker.go
|
|
@ -51,18 +51,16 @@ const (
|
||||||
chainSideChanSize = 10
|
chainSideChanSize = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// Agent can register themself with the worker
|
// Agent can register themselves with the worker
|
||||||
type Agent interface {
|
type Agent interface {
|
||||||
Work() chan<- *Work
|
AssignTask(*Package)
|
||||||
SetReturnCh(chan<- *Result)
|
DeliverTo(chan<- *Package)
|
||||||
Stop()
|
|
||||||
Start()
|
Start()
|
||||||
GetHashRate() int64
|
Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work is the workers current environment and holds
|
// Env is the workers current environment and holds all of the current state information.
|
||||||
// all of the current state information
|
type Env struct {
|
||||||
type Work struct {
|
|
||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
|
|
||||||
|
|
@ -73,8 +71,6 @@ type Work struct {
|
||||||
tcount int // tx count in cycle
|
tcount int // tx count in cycle
|
||||||
gasPool *core.GasPool // available gas used to pack transactions
|
gasPool *core.GasPool // available gas used to pack transactions
|
||||||
|
|
||||||
Block *types.Block // the new block
|
|
||||||
|
|
||||||
header *types.Header
|
header *types.Header
|
||||||
txs []*types.Transaction
|
txs []*types.Transaction
|
||||||
receipts []*types.Receipt
|
receipts []*types.Receipt
|
||||||
|
|
@ -82,8 +78,10 @@ type Work struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
// Package contains all information for consensus engine sealing and result submitting.
|
||||||
Work *Work
|
type Package struct {
|
||||||
|
Receipts []*types.Receipt
|
||||||
|
State *state.StateDB
|
||||||
Block *types.Block
|
Block *types.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,10 +100,9 @@ type worker struct {
|
||||||
chainHeadSub event.Subscription
|
chainHeadSub event.Subscription
|
||||||
chainSideCh chan core.ChainSideEvent
|
chainSideCh chan core.ChainSideEvent
|
||||||
chainSideSub event.Subscription
|
chainSideSub event.Subscription
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
agents map[Agent]struct{}
|
agents map[Agent]struct{}
|
||||||
recv chan *Result
|
recv chan *Package
|
||||||
|
|
||||||
eth Backend
|
eth Backend
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
|
|
@ -116,7 +113,7 @@ type worker struct {
|
||||||
extra []byte
|
extra []byte
|
||||||
|
|
||||||
currentMu sync.Mutex
|
currentMu sync.Mutex
|
||||||
current *Work
|
current *Env
|
||||||
|
|
||||||
snapshotMu sync.RWMutex
|
snapshotMu sync.RWMutex
|
||||||
snapshotBlock *types.Block
|
snapshotBlock *types.Block
|
||||||
|
|
@ -128,11 +125,10 @@ type worker struct {
|
||||||
unconfirmed *unconfirmedBlocks // set of locally mined blocks pending canonicalness confirmations
|
unconfirmed *unconfirmedBlocks // set of locally mined blocks pending canonicalness confirmations
|
||||||
|
|
||||||
// atomic status counters
|
// atomic status counters
|
||||||
mining int32
|
running int32 // The indicator whether the consensus engine is running or not.
|
||||||
atWork int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
|
func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux) *worker {
|
||||||
worker := &worker{
|
worker := &worker{
|
||||||
config: config,
|
config: config,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
|
|
@ -142,11 +138,10 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase com
|
||||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
||||||
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
||||||
chainDb: eth.ChainDb(),
|
chainDb: eth.ChainDb(),
|
||||||
recv: make(chan *Result, resultQueueSize),
|
recv: make(chan *Package, resultQueueSize),
|
||||||
chain: eth.BlockChain(),
|
chain: eth.BlockChain(),
|
||||||
proc: eth.BlockChain().Validator(),
|
proc: eth.BlockChain().Validator(),
|
||||||
possibleUncles: make(map[common.Hash]*types.Block),
|
possibleUncles: make(map[common.Hash]*types.Block),
|
||||||
coinbase: coinbase,
|
|
||||||
agents: make(map[Agent]struct{}),
|
agents: make(map[Agent]struct{}),
|
||||||
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
|
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
|
||||||
}
|
}
|
||||||
|
|
@ -176,62 +171,50 @@ func (self *worker) setExtra(extra []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *worker) pending() (*types.Block, *state.StateDB) {
|
func (self *worker) pending() (*types.Block, *state.StateDB) {
|
||||||
if atomic.LoadInt32(&self.mining) == 0 {
|
|
||||||
// return a snapshot to avoid contention on currentMu mutex
|
// return a snapshot to avoid contention on currentMu mutex
|
||||||
self.snapshotMu.RLock()
|
self.snapshotMu.RLock()
|
||||||
defer self.snapshotMu.RUnlock()
|
defer self.snapshotMu.RUnlock()
|
||||||
return self.snapshotBlock, self.snapshotState.Copy()
|
return self.snapshotBlock, self.snapshotState.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentMu.Lock()
|
|
||||||
defer self.currentMu.Unlock()
|
|
||||||
return self.current.Block, self.current.state.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *worker) pendingBlock() *types.Block {
|
func (self *worker) pendingBlock() *types.Block {
|
||||||
if atomic.LoadInt32(&self.mining) == 0 {
|
|
||||||
// return a snapshot to avoid contention on currentMu mutex
|
// return a snapshot to avoid contention on currentMu mutex
|
||||||
self.snapshotMu.RLock()
|
self.snapshotMu.RLock()
|
||||||
defer self.snapshotMu.RUnlock()
|
defer self.snapshotMu.RUnlock()
|
||||||
return self.snapshotBlock
|
return self.snapshotBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentMu.Lock()
|
|
||||||
defer self.currentMu.Unlock()
|
|
||||||
return self.current.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *worker) start() {
|
func (self *worker) start() {
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
defer self.mu.Unlock()
|
defer self.mu.Unlock()
|
||||||
|
atomic.StoreInt32(&self.running, 1)
|
||||||
atomic.StoreInt32(&self.mining, 1)
|
|
||||||
|
|
||||||
// spin up agents
|
|
||||||
for agent := range self.agents {
|
for agent := range self.agents {
|
||||||
agent.Start()
|
agent.Start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *worker) stop() {
|
func (self *worker) stop() {
|
||||||
self.wg.Wait()
|
|
||||||
|
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
defer self.mu.Unlock()
|
defer self.mu.Unlock()
|
||||||
if atomic.LoadInt32(&self.mining) == 1 {
|
|
||||||
|
atomic.StoreInt32(&self.running, 0)
|
||||||
for agent := range self.agents {
|
for agent := range self.agents {
|
||||||
agent.Stop()
|
agent.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
atomic.StoreInt32(&self.mining, 0)
|
|
||||||
atomic.StoreInt32(&self.atWork, 0)
|
func (self *worker) isRunning() bool {
|
||||||
|
return atomic.LoadInt32(&self.running) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *worker) register(agent Agent) {
|
func (self *worker) register(agent Agent) {
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
defer self.mu.Unlock()
|
defer self.mu.Unlock()
|
||||||
self.agents[agent] = struct{}{}
|
self.agents[agent] = struct{}{}
|
||||||
agent.SetReturnCh(self.recv)
|
agent.DeliverTo(self.recv)
|
||||||
|
if self.isRunning() {
|
||||||
|
agent.Start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *worker) unregister(agent Agent) {
|
func (self *worker) unregister(agent Agent) {
|
||||||
|
|
@ -266,7 +249,7 @@ func (self *worker) update() {
|
||||||
// Note all transactions received may not be continuous with transactions
|
// Note all transactions received may not be continuous with transactions
|
||||||
// already included in the current mining block. These transactions will
|
// already included in the current mining block. These transactions will
|
||||||
// be automatically eliminated.
|
// be automatically eliminated.
|
||||||
if atomic.LoadInt32(&self.mining) == 0 {
|
if !self.isRunning() {
|
||||||
self.currentMu.Lock()
|
self.currentMu.Lock()
|
||||||
txs := make(map[common.Address]types.Transactions)
|
txs := make(map[common.Address]types.Transactions)
|
||||||
for _, tx := range ev.Txs {
|
for _, tx := range ev.Txs {
|
||||||
|
|
@ -298,25 +281,25 @@ func (self *worker) update() {
|
||||||
func (self *worker) wait() {
|
func (self *worker) wait() {
|
||||||
for {
|
for {
|
||||||
for result := range self.recv {
|
for result := range self.recv {
|
||||||
atomic.AddInt32(&self.atWork, -1)
|
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
block := result.Block
|
block := result.Block
|
||||||
work := result.Work
|
|
||||||
|
|
||||||
// Update the block hash in all logs since it is now available and not when the
|
// Update the block hash in all logs since it is now available and not when the
|
||||||
// receipt/log of individual transactions were created.
|
// receipt/log of individual transactions were created.
|
||||||
for _, r := range work.receipts {
|
for _, r := range result.Receipts {
|
||||||
for _, l := range r.Logs {
|
for _, l := range r.Logs {
|
||||||
l.BlockHash = block.Hash()
|
l.BlockHash = block.Hash()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, log := range work.state.Logs() {
|
for _, log := range result.State.Logs() {
|
||||||
log.BlockHash = block.Hash()
|
log.BlockHash = block.Hash()
|
||||||
}
|
}
|
||||||
stat, err := self.chain.WriteBlockWithState(block, work.receipts, work.state)
|
self.currentMu.Lock()
|
||||||
|
stat, err := self.chain.WriteBlockWithState(block, result.Receipts, result.State)
|
||||||
|
self.currentMu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed writing block to chain", "err", err)
|
log.Error("Failed writing block to chain", "err", err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -325,7 +308,7 @@ func (self *worker) wait() {
|
||||||
self.mux.Post(core.NewMinedBlockEvent{Block: block})
|
self.mux.Post(core.NewMinedBlockEvent{Block: block})
|
||||||
var (
|
var (
|
||||||
events []interface{}
|
events []interface{}
|
||||||
logs = work.state.Logs()
|
logs = result.State.Logs()
|
||||||
)
|
)
|
||||||
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
|
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
|
||||||
if stat == core.CanonStatTy {
|
if stat == core.CanonStatTy {
|
||||||
|
|
@ -340,15 +323,9 @@ func (self *worker) wait() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// push sends a new work task to currently live miner agents.
|
// push sends a new work task to currently live miner agents.
|
||||||
func (self *worker) push(work *Work) {
|
func (self *worker) push(p *Package) {
|
||||||
if atomic.LoadInt32(&self.mining) != 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for agent := range self.agents {
|
for agent := range self.agents {
|
||||||
atomic.AddInt32(&self.atWork, 1)
|
agent.AssignTask(p)
|
||||||
if ch := agent.Work(); ch != nil {
|
|
||||||
ch <- work
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,7 +335,7 @@ func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
work := &Work{
|
env := &Env{
|
||||||
config: self.config,
|
config: self.config,
|
||||||
signer: types.NewEIP155Signer(self.config.ChainID),
|
signer: types.NewEIP155Signer(self.config.ChainID),
|
||||||
state: state,
|
state: state,
|
||||||
|
|
@ -372,15 +349,15 @@ func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error
|
||||||
// when 08 is processed ancestors contain 07 (quick block)
|
// when 08 is processed ancestors contain 07 (quick block)
|
||||||
for _, ancestor := range self.chain.GetBlocksFromHash(parent.Hash(), 7) {
|
for _, ancestor := range self.chain.GetBlocksFromHash(parent.Hash(), 7) {
|
||||||
for _, uncle := range ancestor.Uncles() {
|
for _, uncle := range ancestor.Uncles() {
|
||||||
work.family.Add(uncle.Hash())
|
env.family.Add(uncle.Hash())
|
||||||
}
|
}
|
||||||
work.family.Add(ancestor.Hash())
|
env.family.Add(ancestor.Hash())
|
||||||
work.ancestors.Add(ancestor.Hash())
|
env.ancestors.Add(ancestor.Hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep track of transactions which return errors so they can be removed
|
// Keep track of transactions which return errors so they can be removed
|
||||||
work.tcount = 0
|
env.tcount = 0
|
||||||
self.current = work
|
self.current = env
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,8 +391,12 @@ func (self *worker) commitNewWork() {
|
||||||
Extra: self.extra,
|
Extra: self.extra,
|
||||||
Time: big.NewInt(tstamp),
|
Time: big.NewInt(tstamp),
|
||||||
}
|
}
|
||||||
// Only set the coinbase if we are mining (avoid spurious block rewards)
|
// Only set the coinbase if our consensus engine is running (avoid spurious block rewards)
|
||||||
if atomic.LoadInt32(&self.mining) == 1 {
|
if self.isRunning() {
|
||||||
|
if self.coinbase == (common.Address{}) {
|
||||||
|
log.Error("Refusing to mine without etherbase")
|
||||||
|
return
|
||||||
|
}
|
||||||
header.Coinbase = self.coinbase
|
header.Coinbase = self.coinbase
|
||||||
}
|
}
|
||||||
if err := self.engine.Prepare(self.chain, header); err != nil {
|
if err := self.engine.Prepare(self.chain, header); err != nil {
|
||||||
|
|
@ -442,17 +423,10 @@ func (self *worker) commitNewWork() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Create the current work task and check any fork transitions needed
|
// Create the current work task and check any fork transitions needed
|
||||||
work := self.current
|
env := self.current
|
||||||
if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {
|
if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {
|
||||||
misc.ApplyDAOHardFork(work.state)
|
misc.ApplyDAOHardFork(env.state)
|
||||||
}
|
}
|
||||||
pending, err := self.eth.TxPool().Pending()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to fetch pending transactions", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
|
|
||||||
work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
|
|
||||||
|
|
||||||
// compute uncles for the new block.
|
// compute uncles for the new block.
|
||||||
var (
|
var (
|
||||||
|
|
@ -463,7 +437,7 @@ func (self *worker) commitNewWork() {
|
||||||
if len(uncles) == 2 {
|
if len(uncles) == 2 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := self.commitUncle(work, uncle.Header()); err != nil {
|
if err := self.commitUncle(env, uncle.Header()); err != nil {
|
||||||
log.Trace("Bad uncle found and will be removed", "hash", hash)
|
log.Trace("Bad uncle found and will be removed", "hash", hash)
|
||||||
log.Trace(fmt.Sprint(uncle))
|
log.Trace(fmt.Sprint(uncle))
|
||||||
|
|
||||||
|
|
@ -476,32 +450,62 @@ func (self *worker) commitNewWork() {
|
||||||
for _, hash := range badUncles {
|
for _, hash := range badUncles {
|
||||||
delete(self.possibleUncles, hash)
|
delete(self.possibleUncles, hash)
|
||||||
}
|
}
|
||||||
// Create the new block to seal with the consensus engine
|
|
||||||
if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, uncles, work.receipts); err != nil {
|
var (
|
||||||
|
emptyBlock *types.Block
|
||||||
|
fullBlock *types.Block
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create an empty block based on temporary copied state for sealing in advance without waiting block
|
||||||
|
// execution finished.
|
||||||
|
emptyState := env.state.Copy()
|
||||||
|
if emptyBlock, err = self.engine.Finalize(self.chain, header, emptyState, nil, uncles, nil); err != nil {
|
||||||
|
log.Error("Failed to finalize block for temporary sealing", "err", err)
|
||||||
|
} else {
|
||||||
|
// Push empty work in advance without applying pending transaction.
|
||||||
|
// The reason is transactions execution can cost a lot and sealer need to
|
||||||
|
// take advantage of this part time.
|
||||||
|
if self.isRunning() {
|
||||||
|
log.Info("Commit new empty mining work", "number", emptyBlock.Number(), "uncles", len(uncles))
|
||||||
|
self.push(&Package{nil, emptyState, emptyBlock})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the block with all available pending transactions.
|
||||||
|
pending, err := self.eth.TxPool().Pending()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to fetch pending transactions", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
|
||||||
|
env.commitTransactions(self.mux, txs, self.chain, self.coinbase)
|
||||||
|
|
||||||
|
// Create the full block to seal with the consensus engine
|
||||||
|
if fullBlock, err = self.engine.Finalize(self.chain, header, env.state, env.txs, uncles, env.receipts); err != nil {
|
||||||
log.Error("Failed to finalize block for sealing", "err", err)
|
log.Error("Failed to finalize block for sealing", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// We only care about logging if we're actually mining.
|
// We only care about logging if we're actually mining.
|
||||||
if atomic.LoadInt32(&self.mining) == 1 {
|
if self.isRunning() {
|
||||||
log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart)))
|
log.Info("Commit new full mining work", "number", fullBlock.Number(), "txs", env.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart)))
|
||||||
self.unconfirmed.Shift(work.Block.NumberU64() - 1)
|
self.unconfirmed.Shift(fullBlock.NumberU64() - 1)
|
||||||
|
self.push(&Package{env.receipts, env.state, fullBlock})
|
||||||
}
|
}
|
||||||
self.push(work)
|
|
||||||
self.updateSnapshot()
|
self.updateSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *worker) commitUncle(work *Work, uncle *types.Header) error {
|
func (self *worker) commitUncle(env *Env, uncle *types.Header) error {
|
||||||
hash := uncle.Hash()
|
hash := uncle.Hash()
|
||||||
if work.uncles.Contains(hash) {
|
if env.uncles.Contains(hash) {
|
||||||
return fmt.Errorf("uncle not unique")
|
return fmt.Errorf("uncle not unique")
|
||||||
}
|
}
|
||||||
if !work.ancestors.Contains(uncle.ParentHash) {
|
if !env.ancestors.Contains(uncle.ParentHash) {
|
||||||
return fmt.Errorf("uncle's parent unknown (%x)", uncle.ParentHash[0:4])
|
return fmt.Errorf("uncle's parent unknown (%x)", uncle.ParentHash[0:4])
|
||||||
}
|
}
|
||||||
if work.family.Contains(hash) {
|
if env.family.Contains(hash) {
|
||||||
return fmt.Errorf("uncle already in family (%x)", hash)
|
return fmt.Errorf("uncle already in family (%x)", hash)
|
||||||
}
|
}
|
||||||
work.uncles.Add(uncle.Hash())
|
env.uncles.Add(uncle.Hash())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -509,16 +513,25 @@ func (self *worker) updateSnapshot() {
|
||||||
self.snapshotMu.Lock()
|
self.snapshotMu.Lock()
|
||||||
defer self.snapshotMu.Unlock()
|
defer self.snapshotMu.Unlock()
|
||||||
|
|
||||||
|
var uncles []*types.Header
|
||||||
|
self.current.uncles.Each(func(item interface{}) bool {
|
||||||
|
if header, ok := item.(*types.Header); ok {
|
||||||
|
uncles = append(uncles, header)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
self.snapshotBlock = types.NewBlock(
|
self.snapshotBlock = types.NewBlock(
|
||||||
self.current.header,
|
self.current.header,
|
||||||
self.current.txs,
|
self.current.txs,
|
||||||
nil,
|
uncles,
|
||||||
self.current.receipts,
|
self.current.receipts,
|
||||||
)
|
)
|
||||||
self.snapshotState = self.current.state.Copy()
|
self.snapshotState = self.current.state.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) {
|
func (env *Env) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) {
|
||||||
if env.gasPool == nil {
|
if env.gasPool == nil {
|
||||||
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
|
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
|
||||||
}
|
}
|
||||||
|
|
@ -603,7 +616,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) {
|
func (env *Env) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) {
|
||||||
snap := env.state.Snapshot()
|
snap := env.state.Snapshot()
|
||||||
|
|
||||||
receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, &env.header.GasUsed, vm.Config{})
|
receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, &env.header.GasUsed, vm.Config{})
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ func (msg *CallMsg) SetData(data []byte) { msg.msg.Data = common.CopyBytes
|
||||||
func (msg *CallMsg) SetTo(address *Address) {
|
func (msg *CallMsg) SetTo(address *Address) {
|
||||||
if address == nil {
|
if address == nil {
|
||||||
msg.msg.To = nil
|
msg.msg.To = nil
|
||||||
|
return
|
||||||
}
|
}
|
||||||
msg.msg.To = &address.address
|
msg.msg.To = &address.address
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
|
||||||
// Create the empty networking stack
|
// Create the empty networking stack
|
||||||
nodeConf := &node.Config{
|
nodeConf := &node.Config{
|
||||||
Name: clientIdentifier,
|
Name: clientIdentifier,
|
||||||
Version: params.Version,
|
Version: params.VersionWithMeta,
|
||||||
DataDir: datadir,
|
DataDir: datadir,
|
||||||
KeyStoreDir: filepath.Join(datadir, "keystore"), // Mobile should never use internal keystores!
|
KeyStoreDir: filepath.Join(datadir, "keystore"), // Mobile should never use internal keystores!
|
||||||
P2P: p2p.Config{
|
P2P: p2p.Config{
|
||||||
|
|
|
||||||
35
node/api.go
35
node/api.go
|
|
@ -59,7 +59,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePeer disconnects from a a remote node if the connection exists
|
// RemovePeer disconnects from a remote node if the connection exists
|
||||||
func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
|
func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
|
||||||
// Make sure the server is running, fail otherwise
|
// Make sure the server is running, fail otherwise
|
||||||
server := api.node.Server()
|
server := api.node.Server()
|
||||||
|
|
@ -75,6 +75,37 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddTrustedPeer allows a remote node to always connect, even if slots are full
|
||||||
|
func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
|
||||||
|
// Make sure the server is running, fail otherwise
|
||||||
|
server := api.node.Server()
|
||||||
|
if server == nil {
|
||||||
|
return false, ErrNodeStopped
|
||||||
|
}
|
||||||
|
node, err := discover.ParseNode(url)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
|
}
|
||||||
|
server.AddTrustedPeer(node)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTrustedPeer removes a remote node from the trusted peer set, but it
|
||||||
|
// does not disconnect it automatically.
|
||||||
|
func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
|
||||||
|
// Make sure the server is running, fail otherwise
|
||||||
|
server := api.node.Server()
|
||||||
|
if server == nil {
|
||||||
|
return false, ErrNodeStopped
|
||||||
|
}
|
||||||
|
node, err := discover.ParseNode(url)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
|
}
|
||||||
|
server.RemoveTrustedPeer(node)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PeerEvents creates an RPC subscription which receives peer events from the
|
// PeerEvents creates an RPC subscription which receives peer events from the
|
||||||
// node's p2p.Server
|
// node's p2p.Server
|
||||||
func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
|
func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
|
||||||
|
|
@ -157,7 +188,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts); err != nil {
|
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"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/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -119,6 +120,10 @@ type Config struct {
|
||||||
// exposed.
|
// exposed.
|
||||||
HTTPModules []string `toml:",omitempty"`
|
HTTPModules []string `toml:",omitempty"`
|
||||||
|
|
||||||
|
// HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC
|
||||||
|
// interface.
|
||||||
|
HTTPTimeouts rpc.HTTPTimeouts
|
||||||
|
|
||||||
// WSHost is the host interface on which to start the websocket RPC server. If
|
// WSHost is the host interface on which to start the websocket RPC server. If
|
||||||
// this field is empty, no websocket API endpoint will be started.
|
// this field is empty, no websocket API endpoint will be started.
|
||||||
WSHost string `toml:",omitempty"`
|
WSHost string `toml:",omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -39,6 +40,7 @@ var DefaultConfig = Config{
|
||||||
HTTPPort: DefaultHTTPPort,
|
HTTPPort: DefaultHTTPPort,
|
||||||
HTTPModules: []string{"net", "web3"},
|
HTTPModules: []string{"net", "web3"},
|
||||||
HTTPVirtualHosts: []string{"localhost"},
|
HTTPVirtualHosts: []string{"localhost"},
|
||||||
|
HTTPTimeouts: rpc.DefaultHTTPTimeouts,
|
||||||
WSPort: DefaultWSPort,
|
WSPort: DefaultWSPort,
|
||||||
WSModules: []string{"net", "web3"},
|
WSModules: []string{"net", "web3"},
|
||||||
P2P: p2p.Config{
|
P2P: p2p.Config{
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil {
|
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts); err != nil {
|
||||||
n.stopIPC()
|
n.stopIPC()
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
return err
|
||||||
|
|
@ -331,12 +331,12 @@ func (n *Node) stopIPC() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// startHTTP initializes and starts the HTTP RPC endpoint.
|
// startHTTP initializes and starts the HTTP RPC endpoint.
|
||||||
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string) error {
|
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts) error {
|
||||||
// Short circuit if the HTTP endpoint isn't being exposed
|
// Short circuit if the HTTP endpoint isn't being exposed
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts)
|
listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts, timeouts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,7 @@ func potentialGateways() (gws []net.IP) {
|
||||||
return gws
|
return gws
|
||||||
}
|
}
|
||||||
for _, addr := range ifaddrs {
|
for _, addr := range ifaddrs {
|
||||||
switch x := addr.(type) {
|
if x, ok := addr.(*net.IPNet); ok {
|
||||||
case *net.IPNet:
|
|
||||||
if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) {
|
if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) {
|
||||||
ip := x.IP.Mask(x.Mask).To4()
|
ip := x.IP.Mask(x.Mask).To4()
|
||||||
if ip != nil {
|
if ip != nil {
|
||||||
|
|
|
||||||
|
|
@ -81,14 +81,11 @@ func (n *upnp) internalAddress() (net.IP, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
switch x := addr.(type) {
|
if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
|
||||||
case *net.IPNet:
|
|
||||||
if x.Contains(devaddr.IP) {
|
|
||||||
return x.IP, nil
|
return x.IP, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
|
return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ func (p *Peer) String() string {
|
||||||
|
|
||||||
// Inbound returns true if the peer is an inbound connection
|
// Inbound returns true if the peer is an inbound connection
|
||||||
func (p *Peer) Inbound() bool {
|
func (p *Peer) Inbound() bool {
|
||||||
return p.rw.flags&inboundConn != 0
|
return p.rw.is(inboundConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPeer(conn *conn, protocols []Protocol) *Peer {
|
func newPeer(conn *conn, protocols []Protocol) *Peer {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -169,6 +170,8 @@ type Server struct {
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
addstatic chan *discover.Node
|
addstatic chan *discover.Node
|
||||||
removestatic chan *discover.Node
|
removestatic chan *discover.Node
|
||||||
|
addtrusted chan *discover.Node
|
||||||
|
removetrusted chan *discover.Node
|
||||||
posthandshake chan *conn
|
posthandshake chan *conn
|
||||||
addpeer chan *conn
|
addpeer chan *conn
|
||||||
delpeer chan peerDrop
|
delpeer chan peerDrop
|
||||||
|
|
@ -185,7 +188,7 @@ type peerDrop struct {
|
||||||
requested bool // true if signaled by the peer
|
requested bool // true if signaled by the peer
|
||||||
}
|
}
|
||||||
|
|
||||||
type connFlag int
|
type connFlag int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dynDialedConn connFlag = 1 << iota
|
dynDialedConn connFlag = 1 << iota
|
||||||
|
|
@ -250,7 +253,23 @@ func (f connFlag) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) is(f connFlag) bool {
|
func (c *conn) is(f connFlag) bool {
|
||||||
return c.flags&f != 0
|
flags := connFlag(atomic.LoadInt32((*int32)(&c.flags)))
|
||||||
|
return flags&f != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) set(f connFlag, val bool) {
|
||||||
|
for {
|
||||||
|
oldFlags := connFlag(atomic.LoadInt32((*int32)(&c.flags)))
|
||||||
|
flags := oldFlags
|
||||||
|
if val {
|
||||||
|
flags |= f
|
||||||
|
} else {
|
||||||
|
flags &= ^f
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapInt32((*int32)(&c.flags), int32(oldFlags), int32(flags)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peers returns all connected peers.
|
// Peers returns all connected peers.
|
||||||
|
|
@ -300,6 +319,23 @@ func (srv *Server) RemovePeer(node *discover.Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddTrustedPeer adds the given node to a reserved whitelist which allows the
|
||||||
|
// node to always connect, even if the slot are full.
|
||||||
|
func (srv *Server) AddTrustedPeer(node *discover.Node) {
|
||||||
|
select {
|
||||||
|
case srv.addtrusted <- node:
|
||||||
|
case <-srv.quit:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTrustedPeer removes the given node from the trusted peer set.
|
||||||
|
func (srv *Server) RemoveTrustedPeer(node *discover.Node) {
|
||||||
|
select {
|
||||||
|
case srv.removetrusted <- node:
|
||||||
|
case <-srv.quit:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SubscribePeers subscribes the given channel to peer events
|
// SubscribePeers subscribes the given channel to peer events
|
||||||
func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription {
|
func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription {
|
||||||
return srv.peerFeed.Subscribe(ch)
|
return srv.peerFeed.Subscribe(ch)
|
||||||
|
|
@ -340,8 +376,8 @@ func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *discover
|
||||||
// It blocks until all active connections have been closed.
|
// It blocks until all active connections have been closed.
|
||||||
func (srv *Server) Stop() {
|
func (srv *Server) Stop() {
|
||||||
srv.lock.Lock()
|
srv.lock.Lock()
|
||||||
defer srv.lock.Unlock()
|
|
||||||
if !srv.running {
|
if !srv.running {
|
||||||
|
srv.lock.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srv.running = false
|
srv.running = false
|
||||||
|
|
@ -350,6 +386,7 @@ func (srv *Server) Stop() {
|
||||||
srv.listener.Close()
|
srv.listener.Close()
|
||||||
}
|
}
|
||||||
close(srv.quit)
|
close(srv.quit)
|
||||||
|
srv.lock.Unlock()
|
||||||
srv.loopWG.Wait()
|
srv.loopWG.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,6 +447,8 @@ func (srv *Server) Start() (err error) {
|
||||||
srv.posthandshake = make(chan *conn)
|
srv.posthandshake = make(chan *conn)
|
||||||
srv.addstatic = make(chan *discover.Node)
|
srv.addstatic = make(chan *discover.Node)
|
||||||
srv.removestatic = make(chan *discover.Node)
|
srv.removestatic = make(chan *discover.Node)
|
||||||
|
srv.addtrusted = make(chan *discover.Node)
|
||||||
|
srv.removetrusted = make(chan *discover.Node)
|
||||||
srv.peerOp = make(chan peerOpFunc)
|
srv.peerOp = make(chan peerOpFunc)
|
||||||
srv.peerOpDone = make(chan struct{})
|
srv.peerOpDone = make(chan struct{})
|
||||||
|
|
||||||
|
|
@ -546,8 +585,7 @@ func (srv *Server) run(dialstate dialer) {
|
||||||
queuedTasks []task // tasks that can't run yet
|
queuedTasks []task // tasks that can't run yet
|
||||||
)
|
)
|
||||||
// Put trusted nodes into a map to speed up checks.
|
// Put trusted nodes into a map to speed up checks.
|
||||||
// Trusted peers are loaded on startup and cannot be
|
// Trusted peers are loaded on startup or added via AddTrustedPeer RPC.
|
||||||
// modified while the server is running.
|
|
||||||
for _, n := range srv.TrustedNodes {
|
for _, n := range srv.TrustedNodes {
|
||||||
trusted[n.ID] = true
|
trusted[n.ID] = true
|
||||||
}
|
}
|
||||||
|
|
@ -599,12 +637,32 @@ running:
|
||||||
case n := <-srv.removestatic:
|
case n := <-srv.removestatic:
|
||||||
// This channel is used by RemovePeer to send a
|
// This channel is used by RemovePeer to send a
|
||||||
// disconnect request to a peer and begin the
|
// disconnect request to a peer and begin the
|
||||||
// stop keeping the node connected
|
// stop keeping the node connected.
|
||||||
srv.log.Trace("Removing static node", "node", n)
|
srv.log.Trace("Removing static node", "node", n)
|
||||||
dialstate.removeStatic(n)
|
dialstate.removeStatic(n)
|
||||||
if p, ok := peers[n.ID]; ok {
|
if p, ok := peers[n.ID]; ok {
|
||||||
p.Disconnect(DiscRequested)
|
p.Disconnect(DiscRequested)
|
||||||
}
|
}
|
||||||
|
case n := <-srv.addtrusted:
|
||||||
|
// This channel is used by AddTrustedPeer to add an enode
|
||||||
|
// to the trusted node set.
|
||||||
|
srv.log.Trace("Adding trusted node", "node", n)
|
||||||
|
trusted[n.ID] = true
|
||||||
|
// Mark any already-connected peer as trusted
|
||||||
|
if p, ok := peers[n.ID]; ok {
|
||||||
|
p.rw.set(trustedConn, true)
|
||||||
|
}
|
||||||
|
case n := <-srv.removetrusted:
|
||||||
|
// This channel is used by RemoveTrustedPeer to remove an enode
|
||||||
|
// from the trusted node set.
|
||||||
|
srv.log.Trace("Removing trusted node", "node", n)
|
||||||
|
if _, ok := trusted[n.ID]; ok {
|
||||||
|
delete(trusted, n.ID)
|
||||||
|
}
|
||||||
|
// Unmark any already-connected peer as trusted
|
||||||
|
if p, ok := peers[n.ID]; ok {
|
||||||
|
p.rw.set(trustedConn, false)
|
||||||
|
}
|
||||||
case op := <-srv.peerOp:
|
case op := <-srv.peerOp:
|
||||||
// This channel is used by Peers and PeerCount.
|
// This channel is used by Peers and PeerCount.
|
||||||
op(peers)
|
op(peers)
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,8 @@ func TestServerDial(t *testing.T) {
|
||||||
|
|
||||||
// tell the server to connect
|
// tell the server to connect
|
||||||
tcpAddr := listener.Addr().(*net.TCPAddr)
|
tcpAddr := listener.Addr().(*net.TCPAddr)
|
||||||
srv.AddPeer(&discover.Node{ID: remid, IP: tcpAddr.IP, TCP: uint16(tcpAddr.Port)})
|
node := &discover.Node{ID: remid, IP: tcpAddr.IP, TCP: uint16(tcpAddr.Port)}
|
||||||
|
srv.AddPeer(node)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case conn := <-accepted:
|
case conn := <-accepted:
|
||||||
|
|
@ -170,6 +171,29 @@ func TestServerDial(t *testing.T) {
|
||||||
if !reflect.DeepEqual(peers, []*Peer{peer}) {
|
if !reflect.DeepEqual(peers, []*Peer{peer}) {
|
||||||
t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer})
|
t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test AddTrustedPeer/RemoveTrustedPeer and changing Trusted flags
|
||||||
|
// Particularly for race conditions on changing the flag state.
|
||||||
|
if peer := srv.Peers()[0]; peer.Info().Network.Trusted {
|
||||||
|
t.Errorf("peer is trusted prematurely: %v", peer)
|
||||||
|
}
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
srv.AddTrustedPeer(node)
|
||||||
|
if peer := srv.Peers()[0]; !peer.Info().Network.Trusted {
|
||||||
|
t.Errorf("peer is not trusted after AddTrustedPeer: %v", peer)
|
||||||
|
}
|
||||||
|
srv.RemoveTrustedPeer(node)
|
||||||
|
if peer := srv.Peers()[0]; peer.Info().Network.Trusted {
|
||||||
|
t.Errorf("peer is trusted after RemoveTrustedPeer: %v", peer)
|
||||||
|
}
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
// Trigger potential race conditions
|
||||||
|
peer = srv.Peers()[0]
|
||||||
|
_ = peer.Inbound()
|
||||||
|
_ = peer.Info()
|
||||||
|
<-done
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
t.Error("server did not launch peer within one second")
|
t.Error("server did not launch peer within one second")
|
||||||
}
|
}
|
||||||
|
|
@ -351,7 +375,8 @@ func TestServerAtCap(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Try inserting a non-trusted connection.
|
// Try inserting a non-trusted connection.
|
||||||
c := newconn(randomID())
|
anotherID := randomID()
|
||||||
|
c := newconn(anotherID)
|
||||||
if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers {
|
if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers {
|
||||||
t.Error("wrong error for insert:", err)
|
t.Error("wrong error for insert:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -364,6 +389,87 @@ func TestServerAtCap(t *testing.T) {
|
||||||
t.Error("Server did not set trusted flag")
|
t.Error("Server did not set trusted flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove from trusted set and try again
|
||||||
|
srv.RemoveTrustedPeer(&discover.Node{ID: trustedID})
|
||||||
|
c = newconn(trustedID)
|
||||||
|
if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers {
|
||||||
|
t.Error("wrong error for insert:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add anotherID to trusted set and try again
|
||||||
|
srv.AddTrustedPeer(&discover.Node{ID: anotherID})
|
||||||
|
c = newconn(anotherID)
|
||||||
|
if err := srv.checkpoint(c, srv.posthandshake); err != nil {
|
||||||
|
t.Error("unexpected error for trusted conn @posthandshake:", err)
|
||||||
|
}
|
||||||
|
if !c.is(trustedConn) {
|
||||||
|
t.Error("Server did not set trusted flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerPeerLimits(t *testing.T) {
|
||||||
|
srvkey := newkey()
|
||||||
|
|
||||||
|
clientid := randomID()
|
||||||
|
clientnode := &discover.Node{ID: clientid}
|
||||||
|
|
||||||
|
var tp *setupTransport = &setupTransport{
|
||||||
|
id: clientid,
|
||||||
|
phs: &protoHandshake{
|
||||||
|
ID: clientid,
|
||||||
|
// Force "DiscUselessPeer" due to unmatching caps
|
||||||
|
// Caps: []Cap{discard.cap()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var flags connFlag = dynDialedConn
|
||||||
|
var dialDest *discover.Node = &discover.Node{ID: clientid}
|
||||||
|
|
||||||
|
srv := &Server{
|
||||||
|
Config: Config{
|
||||||
|
PrivateKey: srvkey,
|
||||||
|
MaxPeers: 0,
|
||||||
|
NoDial: true,
|
||||||
|
Protocols: []Protocol{discard},
|
||||||
|
},
|
||||||
|
newTransport: func(fd net.Conn) transport { return tp },
|
||||||
|
log: log.New(),
|
||||||
|
}
|
||||||
|
if err := srv.Start(); err != nil {
|
||||||
|
t.Fatalf("couldn't start server: %v", err)
|
||||||
|
}
|
||||||
|
defer srv.Stop()
|
||||||
|
|
||||||
|
// Check that server is full (MaxPeers=0)
|
||||||
|
conn, _ := net.Pipe()
|
||||||
|
srv.SetupConn(conn, flags, dialDest)
|
||||||
|
if tp.closeErr != DiscTooManyPeers {
|
||||||
|
t.Errorf("unexpected close error: %q", tp.closeErr)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
srv.AddTrustedPeer(clientnode)
|
||||||
|
|
||||||
|
// Check that server allows a trusted peer despite being full.
|
||||||
|
conn, _ = net.Pipe()
|
||||||
|
srv.SetupConn(conn, flags, dialDest)
|
||||||
|
if tp.closeErr == DiscTooManyPeers {
|
||||||
|
t.Errorf("failed to bypass MaxPeers with trusted node: %q", tp.closeErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tp.closeErr != DiscUselessPeer {
|
||||||
|
t.Errorf("unexpected close error: %q", tp.closeErr)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
srv.RemoveTrustedPeer(clientnode)
|
||||||
|
|
||||||
|
// Check that server is full again.
|
||||||
|
conn, _ = net.Pipe()
|
||||||
|
srv.SetupConn(conn, flags, dialDest)
|
||||||
|
if tp.closeErr != DiscTooManyPeers {
|
||||||
|
t.Errorf("unexpected close error: %q", tp.closeErr)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerSetupConn(t *testing.T) {
|
func TestServerSetupConn(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -353,8 +353,7 @@ func (sn *SimNode) NodeInfo() *p2p.NodeInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSocketBuffer(conn net.Conn, socketReadBuffer int, socketWriteBuffer int) error {
|
func setSocketBuffer(conn net.Conn, socketReadBuffer int, socketWriteBuffer int) error {
|
||||||
switch v := conn.(type) {
|
if v, ok := conn.(*net.UnixConn); ok {
|
||||||
case *net.UnixConn:
|
|
||||||
err := v.SetReadBuffer(socketReadBuffer)
|
err := v.SetReadBuffer(socketReadBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -23,21 +23,40 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 8 // Minor version component of the current release
|
VersionMinor = 8 // Minor version component of the current release
|
||||||
VersionPatch = 13 // Patch version component of the current release
|
VersionPatch = 14 // Patch version component of the current release
|
||||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version holds the textual version string.
|
// Version holds the textual version string.
|
||||||
var Version = func() string {
|
var Version = func() string {
|
||||||
v := fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
|
return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// VersionWithMeta holds the textual version string including the metadata.
|
||||||
|
var VersionWithMeta = func() string {
|
||||||
|
v := Version
|
||||||
if VersionMeta != "" {
|
if VersionMeta != "" {
|
||||||
v += "-" + VersionMeta
|
v += "-" + VersionMeta
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func VersionWithCommit(gitCommit string) string {
|
// ArchiveVersion holds the textual version string used for Geth archives.
|
||||||
|
// e.g. "1.8.11-dea1ce05" for stable releases, or
|
||||||
|
// "1.8.13-unstable-21c059b6" for unstable releases
|
||||||
|
func ArchiveVersion(gitCommit string) string {
|
||||||
vsn := Version
|
vsn := Version
|
||||||
|
if VersionMeta != "stable" {
|
||||||
|
vsn += "-" + VersionMeta
|
||||||
|
}
|
||||||
|
if len(gitCommit) >= 8 {
|
||||||
|
vsn += "-" + gitCommit[:8]
|
||||||
|
}
|
||||||
|
return vsn
|
||||||
|
}
|
||||||
|
|
||||||
|
func VersionWithCommit(gitCommit string) string {
|
||||||
|
vsn := VersionWithMeta
|
||||||
if len(gitCommit) >= 8 {
|
if len(gitCommit) >= 8 {
|
||||||
vsn += "-" + gitCommit[:8]
|
vsn += "-" + gitCommit[:8]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules
|
// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules
|
||||||
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string) (net.Listener, *Server, error) {
|
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string, timeouts HTTPTimeouts) (net.Listener, *Server, error) {
|
||||||
// Generate the whitelist based on the allowed modules
|
// Generate the whitelist based on the allowed modules
|
||||||
whitelist := make(map[string]bool)
|
whitelist := make(map[string]bool)
|
||||||
for _, module := range modules {
|
for _, module := range modules {
|
||||||
|
|
@ -47,7 +47,7 @@ func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []str
|
||||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
go NewHTTPServer(cors, vhosts, handler).Serve(listener)
|
go NewHTTPServer(cors, vhosts, timeouts, handler).Serve(listener)
|
||||||
return listener, handler, err
|
return listener, handler, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
56
rpc/http.go
56
rpc/http.go
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -66,6 +67,38 @@ func (hc *httpConn) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPTimeouts represents the configuration params for the HTTP RPC server.
|
||||||
|
type HTTPTimeouts struct {
|
||||||
|
// ReadTimeout is the maximum duration for reading the entire
|
||||||
|
// request, including the body.
|
||||||
|
//
|
||||||
|
// Because ReadTimeout does not let Handlers make per-request
|
||||||
|
// decisions on each request body's acceptable deadline or
|
||||||
|
// upload rate, most users will prefer to use
|
||||||
|
// ReadHeaderTimeout. It is valid to use them both.
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
|
||||||
|
// WriteTimeout is the maximum duration before timing out
|
||||||
|
// writes of the response. It is reset whenever a new
|
||||||
|
// request's header is read. Like ReadTimeout, it does not
|
||||||
|
// let Handlers make decisions on a per-request basis.
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
// IdleTimeout is the maximum amount of time to wait for the
|
||||||
|
// next request when keep-alives are enabled. If IdleTimeout
|
||||||
|
// is zero, the value of ReadTimeout is used. If both are
|
||||||
|
// zero, ReadHeaderTimeout is used.
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultHTTPTimeouts represents the default timeout values used if further
|
||||||
|
// configuration is not provided.
|
||||||
|
var DefaultHTTPTimeouts = HTTPTimeouts{
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
IdleTimeout: 120 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
|
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
|
||||||
// using the provided HTTP Client.
|
// using the provided HTTP Client.
|
||||||
func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
|
func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
|
||||||
|
|
@ -161,15 +194,30 @@ func (t *httpReadWriteNopCloser) Close() error {
|
||||||
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
||||||
//
|
//
|
||||||
// Deprecated: Server implements http.Handler
|
// Deprecated: Server implements http.Handler
|
||||||
func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server {
|
func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv *Server) *http.Server {
|
||||||
// Wrap the CORS-handler within a host-handler
|
// Wrap the CORS-handler within a host-handler
|
||||||
handler := newCorsHandler(srv, cors)
|
handler := newCorsHandler(srv, cors)
|
||||||
handler = newVHostHandler(vhosts, handler)
|
handler = newVHostHandler(vhosts, handler)
|
||||||
|
|
||||||
|
// Make sure timeout values are meaningful
|
||||||
|
if timeouts.ReadTimeout < time.Second {
|
||||||
|
log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", DefaultHTTPTimeouts.ReadTimeout)
|
||||||
|
timeouts.ReadTimeout = DefaultHTTPTimeouts.ReadTimeout
|
||||||
|
}
|
||||||
|
if timeouts.WriteTimeout < time.Second {
|
||||||
|
log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", DefaultHTTPTimeouts.WriteTimeout)
|
||||||
|
timeouts.WriteTimeout = DefaultHTTPTimeouts.WriteTimeout
|
||||||
|
}
|
||||||
|
if timeouts.IdleTimeout < time.Second {
|
||||||
|
log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", DefaultHTTPTimeouts.IdleTimeout)
|
||||||
|
timeouts.IdleTimeout = DefaultHTTPTimeouts.IdleTimeout
|
||||||
|
}
|
||||||
|
// Bundle and start the HTTP server
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: timeouts.ReadTimeout,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: timeouts.WriteTimeout,
|
||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: timeouts.IdleTimeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,11 @@ type decodedCallData struct {
|
||||||
// String implements stringer interface, tries to use the underlying value-type
|
// String implements stringer interface, tries to use the underlying value-type
|
||||||
func (arg decodedArgument) String() string {
|
func (arg decodedArgument) String() string {
|
||||||
var value string
|
var value string
|
||||||
switch arg.value.(type) {
|
switch val := arg.value.(type) {
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
value = arg.value.(fmt.Stringer).String()
|
value = val.String()
|
||||||
default:
|
default:
|
||||||
value = fmt.Sprintf("%v", arg.value)
|
value = fmt.Sprintf("%v", val)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value)
|
return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -897,11 +897,11 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
||||||
ctx, sp = spancontext.StartSpan(
|
ctx, sp = spancontext.StartSpan(
|
||||||
ctx,
|
ctx,
|
||||||
"http.get.file")
|
"http.get.file")
|
||||||
|
defer sp.Finish()
|
||||||
|
|
||||||
// ensure the root path has a trailing slash so that relative URLs work
|
// ensure the root path has a trailing slash so that relative URLs work
|
||||||
if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
|
if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
|
||||||
http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
|
http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -912,7 +912,6 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getFileFail.Inc(1)
|
getFileFail.Inc(1)
|
||||||
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
|
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -928,7 +927,6 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
||||||
if noneMatchEtag != "" {
|
if noneMatchEtag != "" {
|
||||||
if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) {
|
if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) {
|
||||||
Respond(w, r, "Not Modified", http.StatusNotModified)
|
Respond(w, r, "Not Modified", http.StatusNotModified)
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -942,7 +940,6 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
||||||
getFileFail.Inc(1)
|
getFileFail.Inc(1)
|
||||||
Respond(w, r, err.Error(), http.StatusInternalServerError)
|
Respond(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -953,14 +950,12 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getFileFail.Inc(1)
|
getFileFail.Inc(1)
|
||||||
Respond(w, r, err.Error(), http.StatusInternalServerError)
|
Respond(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", r.ruid)
|
log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", r.ruid)
|
||||||
//show a nice page links to available entries
|
//show a nice page links to available entries
|
||||||
ShowMultipleChoices(w, r, list)
|
ShowMultipleChoices(w, r, list)
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -968,23 +963,11 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
||||||
if _, err := reader.Size(ctx, nil); err != nil {
|
if _, err := reader.Size(ctx, nil); err != nil {
|
||||||
getFileNotFound.Inc(1)
|
getFileNotFound.Inc(1)
|
||||||
Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound)
|
Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound)
|
||||||
sp.Finish()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(newBufferedReadSeeker(reader, getFileBufferSize))
|
|
||||||
if err != nil {
|
|
||||||
getFileNotFound.Inc(1)
|
|
||||||
Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound)
|
|
||||||
sp.Finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("got response in buffer", "len", len(buf), "ruid", r.ruid)
|
|
||||||
sp.Finish()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
http.ServeContent(w, &r.Request, "", time.Now(), bytes.NewReader(buf))
|
http.ServeContent(w, &r.Request, "", time.Now(), newBufferedReadSeeker(reader, getFileBufferSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The size of buffer used for bufio.Reader on LazyChunkReader passed to
|
// The size of buffer used for bufio.Reader on LazyChunkReader passed to
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func (s *Simulation) SetNodeItem(id discover.NodeID, key interface{}, value inte
|
||||||
s.buckets[id].Store(key, value)
|
s.buckets[id].Store(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeItems returns a map of items from all nodes that are all set under the
|
// NodesItems returns a map of items from all nodes that are all set under the
|
||||||
// same BucketKey.
|
// same BucketKey.
|
||||||
func (s *Simulation) NodesItems(key interface{}) (values map[discover.NodeID]interface{}) {
|
func (s *Simulation) NodesItems(key interface{}) (values map[discover.NodeID]interface{}) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ func (s *Simulation) ConnectToLastNode(id discover.NodeID) (err error) {
|
||||||
// ConnectToRandomNode connects the node with provieded NodeID
|
// ConnectToRandomNode connects the node with provieded NodeID
|
||||||
// to a random node that is up.
|
// to a random node that is up.
|
||||||
func (s *Simulation) ConnectToRandomNode(id discover.NodeID) (err error) {
|
func (s *Simulation) ConnectToRandomNode(id discover.NodeID) (err error) {
|
||||||
n := s.randomUpNode(id)
|
n := s.RandomUpNode(id)
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return ErrNodeNotFound
|
return ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +135,7 @@ func (s *Simulation) ConnectNodesStar(id discover.NodeID, ids []discover.NodeID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectNodesStar connects all nodes in a star topology
|
// ConnectNodesStarPivot connects all nodes in a star topology
|
||||||
// with the center at already set pivot node.
|
// with the center at already set pivot node.
|
||||||
// If ids argument is nil, all nodes that are up will be connected.
|
// If ids argument is nil, all nodes that are up will be connected.
|
||||||
func (s *Simulation) ConnectNodesStarPivot(ids []discover.NodeID) (err error) {
|
func (s *Simulation) ConnectNodesStarPivot(ids []discover.NodeID) (err error) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
|
||||||
|
|
@ -71,24 +72,32 @@ func (f *PeerEventsFilter) MsgCode(c uint64) *PeerEventsFilter {
|
||||||
func (s *Simulation) PeerEvents(ctx context.Context, ids []discover.NodeID, filters ...*PeerEventsFilter) <-chan PeerEvent {
|
func (s *Simulation) PeerEvents(ctx context.Context, ids []discover.NodeID, filters ...*PeerEventsFilter) <-chan PeerEvent {
|
||||||
eventC := make(chan PeerEvent)
|
eventC := make(chan PeerEvent)
|
||||||
|
|
||||||
|
// wait group to make sure all subscriptions to admin peerEvents are established
|
||||||
|
// before this function returns.
|
||||||
|
var subsWG sync.WaitGroup
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
s.shutdownWG.Add(1)
|
s.shutdownWG.Add(1)
|
||||||
|
subsWG.Add(1)
|
||||||
go func(id discover.NodeID) {
|
go func(id discover.NodeID) {
|
||||||
defer s.shutdownWG.Done()
|
defer s.shutdownWG.Done()
|
||||||
|
|
||||||
client, err := s.Net.GetNode(id).Client()
|
client, err := s.Net.GetNode(id).Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
subsWG.Done()
|
||||||
eventC <- PeerEvent{NodeID: id, Error: err}
|
eventC <- PeerEvent{NodeID: id, Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
events := make(chan *p2p.PeerEvent)
|
events := make(chan *p2p.PeerEvent)
|
||||||
sub, err := client.Subscribe(ctx, "admin", events, "peerEvents")
|
sub, err := client.Subscribe(ctx, "admin", events, "peerEvents")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
subsWG.Done()
|
||||||
eventC <- PeerEvent{NodeID: id, Error: err}
|
eventC <- PeerEvent{NodeID: id, Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer sub.Unsubscribe()
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
subsWG.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
@ -153,5 +162,7 @@ func (s *Simulation) PeerEvents(ctx context.Context, ids []discover.NodeID, filt
|
||||||
}(id)
|
}(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait all subscriptions
|
||||||
|
subsWG.Wait()
|
||||||
return eventC
|
return eventC
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ var (
|
||||||
DefaultHTTPSimAddr = ":8888"
|
DefaultHTTPSimAddr = ":8888"
|
||||||
)
|
)
|
||||||
|
|
||||||
//`With`(builder) pattern constructor for Simulation to
|
//WithServer implements the builder pattern constructor for Simulation to
|
||||||
//start with a HTTP server
|
//start with a HTTP server
|
||||||
func (s *Simulation) WithServer(addr string) *Simulation {
|
func (s *Simulation) WithServer(addr string) *Simulation {
|
||||||
//assign default addr if nothing provided
|
//assign default addr if nothing provided
|
||||||
|
|
@ -46,7 +46,12 @@ func (s *Simulation) WithServer(addr string) *Simulation {
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.handler,
|
Handler: s.handler,
|
||||||
}
|
}
|
||||||
go s.httpSrv.ListenAndServe()
|
go func() {
|
||||||
|
err := s.httpSrv.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error starting the HTTP server", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +60,7 @@ func (s *Simulation) addSimulationRoutes() {
|
||||||
s.handler.POST("/runsim", s.RunSimulation)
|
s.handler.POST("/runsim", s.RunSimulation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartNetwork starts all nodes in the network
|
// RunSimulation is the actual POST endpoint runner
|
||||||
func (s *Simulation) RunSimulation(w http.ResponseWriter, req *http.Request) {
|
func (s *Simulation) RunSimulation(w http.ResponseWriter, req *http.Request) {
|
||||||
log.Debug("RunSimulation endpoint running")
|
log.Debug("RunSimulation endpoint running")
|
||||||
s.runC <- struct{}{}
|
s.runC <- struct{}{}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,12 @@ func sendRunSignal(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Request failed: %v", err)
|
t.Fatalf("Request failed: %v", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer func() {
|
||||||
|
err := resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error closing response body", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
log.Debug("Signal sent")
|
log.Debug("Signal sent")
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
t.Fatalf("err %s", resp.Status)
|
t.Fatalf("err %s", resp.Status)
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ func (s *Simulation) AddNodesAndConnectStar(count int, opts ...AddNodeOption) (i
|
||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Upload a snapshot
|
//UploadSnapshot uploads a snapshot to the simulation
|
||||||
//This method tries to open the json file provided, applies the config to all nodes
|
//This method tries to open the json file provided, applies the config to all nodes
|
||||||
//and then loads the snapshot into the Simulation network
|
//and then loads the snapshot into the Simulation network
|
||||||
func (s *Simulation) UploadSnapshot(snapshotFile string, opts ...AddNodeOption) error {
|
func (s *Simulation) UploadSnapshot(snapshotFile string, opts ...AddNodeOption) error {
|
||||||
|
|
@ -203,7 +203,12 @@ func (s *Simulation) UploadSnapshot(snapshotFile string, opts ...AddNodeOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error closing snapshot file", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
jsonbyte, err := ioutil.ReadAll(f)
|
jsonbyte, err := ioutil.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -294,7 +299,7 @@ func (s *Simulation) StopNode(id discover.NodeID) (err error) {
|
||||||
|
|
||||||
// StopRandomNode stops a random node.
|
// StopRandomNode stops a random node.
|
||||||
func (s *Simulation) StopRandomNode() (id discover.NodeID, err error) {
|
func (s *Simulation) StopRandomNode() (id discover.NodeID, err error) {
|
||||||
n := s.randomUpNode()
|
n := s.RandomUpNode()
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return id, ErrNodeNotFound
|
return id, ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -324,18 +329,18 @@ func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomUpNode returns a random SimNode that is up.
|
// RandomUpNode returns a random SimNode that is up.
|
||||||
// Arguments are NodeIDs for nodes that should not be returned.
|
// Arguments are NodeIDs for nodes that should not be returned.
|
||||||
func (s *Simulation) randomUpNode(exclude ...discover.NodeID) *adapters.SimNode {
|
func (s *Simulation) RandomUpNode(exclude ...discover.NodeID) *adapters.SimNode {
|
||||||
return s.randomNode(s.UpNodeIDs(), exclude...)
|
return s.randomNode(s.UpNodeIDs(), exclude...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomUpNode returns a random SimNode that is not up.
|
// randomDownNode returns a random SimNode that is not up.
|
||||||
func (s *Simulation) randomDownNode(exclude ...discover.NodeID) *adapters.SimNode {
|
func (s *Simulation) randomDownNode(exclude ...discover.NodeID) *adapters.SimNode {
|
||||||
return s.randomNode(s.DownNodeIDs(), exclude...)
|
return s.randomNode(s.DownNodeIDs(), exclude...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomUpNode returns a random SimNode from the slice of NodeIDs.
|
// randomNode returns a random SimNode from the slice of NodeIDs.
|
||||||
func (s *Simulation) randomNode(ids []discover.NodeID, exclude ...discover.NodeID) *adapters.SimNode {
|
func (s *Simulation) randomNode(ids []discover.NodeID, exclude ...discover.NodeID) *adapters.SimNode {
|
||||||
for _, e := range exclude {
|
for _, e := range exclude {
|
||||||
var i int
|
var i int
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func (s *Simulation) Service(name string, id discover.NodeID) node.Service {
|
||||||
// RandomService returns a single Service by name on a
|
// RandomService returns a single Service by name on a
|
||||||
// randomly chosen node that is up.
|
// randomly chosen node that is up.
|
||||||
func (s *Simulation) RandomService(name string) node.Service {
|
func (s *Simulation) RandomService(name string) node.Service {
|
||||||
n := s.randomUpNode()
|
n := s.RandomUpNode()
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ type Simulation struct {
|
||||||
// where all "global" state related to the service should be kept.
|
// where all "global" state related to the service should be kept.
|
||||||
// All cleanups needed for constructed service and any other constructed
|
// All cleanups needed for constructed service and any other constructed
|
||||||
// objects should ne provided in a single returned cleanup function.
|
// objects should ne provided in a single returned cleanup function.
|
||||||
|
// Returned cleanup function will be called by Close function
|
||||||
|
// after network shutdown.
|
||||||
type ServiceFunc func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error)
|
type ServiceFunc func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error)
|
||||||
|
|
||||||
// New creates a new Simulation instance with new
|
// New creates a new Simulation instance with new
|
||||||
|
|
@ -161,6 +163,18 @@ var maxParallelCleanups = 10
|
||||||
// simulation.
|
// simulation.
|
||||||
func (s *Simulation) Close() {
|
func (s *Simulation) Close() {
|
||||||
close(s.done)
|
close(s.done)
|
||||||
|
|
||||||
|
// Close all connections before calling the Network Shutdown.
|
||||||
|
// It is possible that p2p.Server.Stop will block if there are
|
||||||
|
// existing connections.
|
||||||
|
for _, c := range s.Net.Conns {
|
||||||
|
if c.Up {
|
||||||
|
s.Net.Disconnect(c.One, c.Other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.shutdownWG.Wait()
|
||||||
|
s.Net.Shutdown()
|
||||||
|
|
||||||
sem := make(chan struct{}, maxParallelCleanups)
|
sem := make(chan struct{}, maxParallelCleanups)
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
cleanupFuncs := make([]func(), len(s.cleanupFuncs))
|
cleanupFuncs := make([]func(), len(s.cleanupFuncs))
|
||||||
|
|
@ -170,16 +184,19 @@ func (s *Simulation) Close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
var cleanupWG sync.WaitGroup
|
||||||
for _, cleanup := range cleanupFuncs {
|
for _, cleanup := range cleanupFuncs {
|
||||||
s.shutdownWG.Add(1)
|
cleanupWG.Add(1)
|
||||||
sem <- struct{}{}
|
sem <- struct{}{}
|
||||||
go func(cleanup func()) {
|
go func(cleanup func()) {
|
||||||
defer s.shutdownWG.Done()
|
defer cleanupWG.Done()
|
||||||
defer func() { <-sem }()
|
defer func() { <-sem }()
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
}(cleanup)
|
}(cleanup)
|
||||||
}
|
}
|
||||||
|
cleanupWG.Wait()
|
||||||
|
|
||||||
if s.httpSrv != nil {
|
if s.httpSrv != nil {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
@ -189,8 +206,6 @@ func (s *Simulation) Close() {
|
||||||
}
|
}
|
||||||
close(s.runC)
|
close(s.runC)
|
||||||
}
|
}
|
||||||
s.shutdownWG.Wait()
|
|
||||||
s.Net.Shutdown()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done returns a channel that is closed when the simulation
|
// Done returns a channel that is closed when the simulation
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func TestRun(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
r := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error {
|
r := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(time.Second)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,135 +18,70 @@ package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
crand "crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
|
||||||
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
"github.com/ethereum/go-ethereum/swarm/network"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/pot"
|
||||||
"github.com/ethereum/go-ethereum/swarm/state"
|
"github.com/ethereum/go-ethereum/swarm/state"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/mock"
|
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
|
||||||
colorable "github.com/mattn/go-colorable"
|
colorable "github.com/mattn/go-colorable"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
deliveries map[discover.NodeID]*Delivery
|
|
||||||
stores map[discover.NodeID]storage.ChunkStore
|
|
||||||
toAddr func(discover.NodeID) *network.BzzAddr
|
|
||||||
peerCount func(discover.NodeID) int
|
|
||||||
adapter = flag.String("adapter", "sim", "type of simulation: sim|exec|docker")
|
|
||||||
loglevel = flag.Int("loglevel", 2, "verbosity of logs")
|
loglevel = flag.Int("loglevel", 2, "verbosity of logs")
|
||||||
nodes = flag.Int("nodes", 0, "number of nodes")
|
nodes = flag.Int("nodes", 0, "number of nodes")
|
||||||
chunks = flag.Int("chunks", 0, "number of chunks")
|
chunks = flag.Int("chunks", 0, "number of chunks")
|
||||||
useMockStore = flag.Bool("mockstore", false, "disabled mock store (default: enabled)")
|
useMockStore = flag.Bool("mockstore", false, "disabled mock store (default: enabled)")
|
||||||
)
|
longrunning = flag.Bool("longrunning", false, "do run long-running tests")
|
||||||
|
|
||||||
|
bucketKeyDB = simulation.BucketKey("db")
|
||||||
|
bucketKeyStore = simulation.BucketKey("store")
|
||||||
|
bucketKeyFileStore = simulation.BucketKey("filestore")
|
||||||
|
bucketKeyNetStore = simulation.BucketKey("netstore")
|
||||||
|
bucketKeyDelivery = simulation.BucketKey("delivery")
|
||||||
|
bucketKeyRegistry = simulation.BucketKey("registry")
|
||||||
|
|
||||||
var (
|
|
||||||
defaultSkipCheck bool
|
|
||||||
waitPeerErrC chan error
|
|
||||||
chunkSize = 4096
|
chunkSize = 4096
|
||||||
registries map[discover.NodeID]*TestRegistry
|
pof = pot.DefaultPof(256)
|
||||||
createStoreFunc func(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error)
|
|
||||||
getRetrieveFunc = defaultRetrieveFunc
|
|
||||||
subscriptionCount = 0
|
|
||||||
globalStore mock.GlobalStorer
|
|
||||||
globalStoreDir string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var services = adapters.Services{
|
|
||||||
"streamer": NewStreamerService,
|
|
||||||
"intervalsStreamer": newIntervalsStreamerService,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
// register the Delivery service which will run as a devp2p
|
rand.Seed(time.Now().UnixNano())
|
||||||
// protocol when using the exec adapter
|
|
||||||
adapters.RegisterServices(services)
|
|
||||||
|
|
||||||
log.PrintOrigins(true)
|
log.PrintOrigins(true)
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createGlobalStore() {
|
func createGlobalStore() (string, *mockdb.GlobalStore, error) {
|
||||||
var err error
|
var globalStore *mockdb.GlobalStore
|
||||||
globalStoreDir, err = ioutil.TempDir("", "global.store")
|
globalStoreDir, err := ioutil.TempDir("", "global.store")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error initiating global store temp directory!", "err", err)
|
log.Error("Error initiating global store temp directory!", "err", err)
|
||||||
return
|
return "", nil, err
|
||||||
}
|
}
|
||||||
globalStore, err = db.NewGlobalStore(globalStoreDir)
|
globalStore, err = mockdb.NewGlobalStore(globalStoreDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error initiating global store!", "err", err)
|
log.Error("Error initiating global store!", "err", err)
|
||||||
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
return globalStoreDir, globalStore, nil
|
||||||
|
|
||||||
// NewStreamerService
|
|
||||||
func NewStreamerService(ctx *adapters.ServiceContext) (node.Service, error) {
|
|
||||||
var err error
|
|
||||||
id := ctx.Config.ID
|
|
||||||
addr := toAddr(id)
|
|
||||||
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
|
||||||
stores[id], err = createStoreFunc(id, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
store := stores[id].(*storage.LocalStore)
|
|
||||||
db := storage.NewDBAPI(store)
|
|
||||||
delivery := NewDelivery(kad, db)
|
|
||||||
deliveries[id] = delivery
|
|
||||||
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
|
||||||
SkipCheck: defaultSkipCheck,
|
|
||||||
DoRetrieve: false,
|
|
||||||
})
|
|
||||||
RegisterSwarmSyncerServer(r, db)
|
|
||||||
RegisterSwarmSyncerClient(r, db)
|
|
||||||
go func() {
|
|
||||||
waitPeerErrC <- waitForPeers(r, 1*time.Second, peerCount(id))
|
|
||||||
}()
|
|
||||||
fileStore := storage.NewFileStore(storage.NewNetStore(store, getRetrieveFunc(id)), storage.NewFileStoreParams())
|
|
||||||
testRegistry := &TestRegistry{Registry: r, fileStore: fileStore}
|
|
||||||
registries[id] = testRegistry
|
|
||||||
return testRegistry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultRetrieveFunc(id discover.NodeID) func(ctx context.Context, chunk *storage.Chunk) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func datadirsCleanup() {
|
|
||||||
for _, id := range ids {
|
|
||||||
os.RemoveAll(datadirs[id])
|
|
||||||
}
|
|
||||||
if globalStoreDir != "" {
|
|
||||||
os.RemoveAll(globalStoreDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//local stores need to be cleaned up after the sim is done
|
|
||||||
func localStoreCleanup() {
|
|
||||||
log.Info("Cleaning up...")
|
|
||||||
for _, id := range ids {
|
|
||||||
registries[id].Close()
|
|
||||||
stores[id].Close()
|
|
||||||
}
|
|
||||||
log.Info("Local store cleanup done")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStreamerTester(t *testing.T) (*p2ptest.ProtocolTester, *Registry, *storage.LocalStore, func(), error) {
|
func newStreamerTester(t *testing.T) (*p2ptest.ProtocolTester, *Registry, *storage.LocalStore, func(), error) {
|
||||||
|
|
@ -174,9 +109,7 @@ func newStreamerTester(t *testing.T) (*p2ptest.ProtocolTester, *Registry, *stora
|
||||||
|
|
||||||
db := storage.NewDBAPI(localStore)
|
db := storage.NewDBAPI(localStore)
|
||||||
delivery := NewDelivery(to, db)
|
delivery := NewDelivery(to, db)
|
||||||
streamer := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
streamer := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), nil)
|
||||||
SkipCheck: defaultSkipCheck,
|
|
||||||
})
|
|
||||||
teardown := func() {
|
teardown := func() {
|
||||||
streamer.Close()
|
streamer.Close()
|
||||||
removeDataDir()
|
removeDataDir()
|
||||||
|
|
@ -233,22 +166,6 @@ func (rrs *roundRobinStore) Close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestRegistry struct {
|
|
||||||
*Registry
|
|
||||||
fileStore *storage.FileStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestRegistry) APIs() []rpc.API {
|
|
||||||
a := r.Registry.APIs()
|
|
||||||
a = append(a, rpc.API{
|
|
||||||
Namespace: "stream",
|
|
||||||
Version: "3.0",
|
|
||||||
Service: r,
|
|
||||||
Public: true,
|
|
||||||
})
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAll(fileStore *storage.FileStore, hash []byte) (int64, error) {
|
func readAll(fileStore *storage.FileStore, hash []byte) (int64, error) {
|
||||||
r, _ := fileStore.Retrieve(context.TODO(), hash)
|
r, _ := fileStore.Retrieve(context.TODO(), hash)
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
|
|
@ -265,185 +182,74 @@ func readAll(fileStore *storage.FileStore, hash []byte) (int64, error) {
|
||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TestRegistry) ReadAll(hash common.Hash) (int64, error) {
|
func uploadFilesToNodes(sim *simulation.Simulation) ([]storage.Address, []string, error) {
|
||||||
return readAll(r.fileStore, hash[:])
|
nodes := sim.UpNodeIDs()
|
||||||
|
nodeCnt := len(nodes)
|
||||||
|
log.Debug(fmt.Sprintf("Uploading %d files to nodes", nodeCnt))
|
||||||
|
//array holding generated files
|
||||||
|
rfiles := make([]string, nodeCnt)
|
||||||
|
//array holding the root hashes of the files
|
||||||
|
rootAddrs := make([]storage.Address, nodeCnt)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
//for every node, generate a file and upload
|
||||||
|
for i, id := range nodes {
|
||||||
|
item, ok := sim.NodeItem(id, bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("Error accessing localstore")
|
||||||
}
|
}
|
||||||
|
fileStore := item.(*storage.FileStore)
|
||||||
func (r *TestRegistry) Start(server *p2p.Server) error {
|
//generate a file
|
||||||
return r.Registry.Start(server)
|
rfiles[i], err = generateRandomFile()
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestRegistry) Stop() error {
|
|
||||||
return r.Registry.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestExternalRegistry struct {
|
|
||||||
*Registry
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestExternalRegistry) APIs() []rpc.API {
|
|
||||||
a := r.Registry.APIs()
|
|
||||||
a = append(a, rpc.API{
|
|
||||||
Namespace: "stream",
|
|
||||||
Version: "3.0",
|
|
||||||
Service: r,
|
|
||||||
Public: true,
|
|
||||||
})
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestExternalRegistry) GetHashes(ctx context.Context, peerId discover.NodeID, s Stream) (*rpc.Subscription, error) {
|
|
||||||
peer := r.getPeer(peerId)
|
|
||||||
|
|
||||||
client, err := peer.getClient(ctx, s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
//store it (upload it) on the FileStore
|
||||||
c := client.Client.(*testExternalClient)
|
ctx := context.TODO()
|
||||||
|
rk, wait, err := fileStore.Store(ctx, strings.NewReader(rfiles[i]), int64(len(rfiles[i])), false)
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
log.Debug("Uploaded random string file to node")
|
||||||
if !supported {
|
|
||||||
return nil, fmt.Errorf("Subscribe not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
sub := notifier.CreateSubscription()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// if we begin sending event immediately some events
|
|
||||||
// will probably be dropped since the subscription ID might not be send to
|
|
||||||
// the client.
|
|
||||||
// ref: rpc/subscription_test.go#L65
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case h := <-c.hashes:
|
|
||||||
<-c.enableNotificationsC // wait for notification subscription to complete
|
|
||||||
if err := notifier.Notify(sub.ID, h); err != nil {
|
|
||||||
log.Warn(fmt.Sprintf("rpc sub notifier notify stream %s: %v", s, err))
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(fmt.Sprintf("caught subscription error in stream %s: %v", s, err))
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
case <-notifier.Closed():
|
err = wait(ctx)
|
||||||
log.Trace(fmt.Sprintf("rpc sub notifier closed"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return sub, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestExternalRegistry) EnableNotifications(peerId discover.NodeID, s Stream) error {
|
|
||||||
peer := r.getPeer(peerId)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
client, err := peer.getClient(ctx, s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
rootAddrs[i] = rk
|
||||||
|
}
|
||||||
|
return rootAddrs, rfiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
close(client.Client.(*testExternalClient).enableNotificationsC)
|
//generate a random file (string)
|
||||||
|
func generateRandomFile() (string, error) {
|
||||||
return nil
|
//generate a random file size between minFileSize and maxFileSize
|
||||||
|
fileSize := rand.Intn(maxFileSize-minFileSize) + minFileSize
|
||||||
|
log.Debug(fmt.Sprintf("Generated file with filesize %d kB", fileSize))
|
||||||
|
b := make([]byte, fileSize*1024)
|
||||||
|
_, err := crand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error generating random file.", "err", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge functionalities of testExternalClient and testExternalServer
|
//create a local store for the given node
|
||||||
// with testClient and testServer.
|
func createTestLocalStorageForID(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, string, error) {
|
||||||
|
var datadir string
|
||||||
type testExternalClient struct {
|
var err error
|
||||||
hashes chan []byte
|
datadir, err = ioutil.TempDir("", fmt.Sprintf("syncer-test-%s", id.TerminalString()))
|
||||||
db *storage.DBAPI
|
if err != nil {
|
||||||
enableNotificationsC chan struct{}
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
var store storage.ChunkStore
|
||||||
func newTestExternalClient(db *storage.DBAPI) *testExternalClient {
|
params := storage.NewDefaultLocalStoreParams()
|
||||||
return &testExternalClient{
|
params.ChunkDbPath = datadir
|
||||||
hashes: make(chan []byte),
|
params.BaseKey = addr.Over()
|
||||||
db: db,
|
store, err = storage.NewTestLocalStoreForAddr(params)
|
||||||
enableNotificationsC: make(chan struct{}),
|
if err != nil {
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
return nil, "", err
|
||||||
}
|
}
|
||||||
}
|
return store, datadir, nil
|
||||||
|
|
||||||
func (c *testExternalClient) NeedData(ctx context.Context, hash []byte) func() {
|
|
||||||
chunk, _ := c.db.GetOrCreateRequest(ctx, hash)
|
|
||||||
if chunk.ReqC == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.hashes <- hash
|
|
||||||
return func() {
|
|
||||||
chunk.WaitToStore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testExternalClient) BatchDone(Stream, uint64, []byte, []byte) func() (*TakeoverProof, error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testExternalClient) Close() {}
|
|
||||||
|
|
||||||
const testExternalServerBatchSize = 10
|
|
||||||
|
|
||||||
type testExternalServer struct {
|
|
||||||
t string
|
|
||||||
keyFunc func(key []byte, index uint64)
|
|
||||||
sessionAt uint64
|
|
||||||
maxKeys uint64
|
|
||||||
streamer *TestExternalRegistry
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestExternalServer(t string, sessionAt, maxKeys uint64, keyFunc func(key []byte, index uint64)) *testExternalServer {
|
|
||||||
if keyFunc == nil {
|
|
||||||
keyFunc = binary.BigEndian.PutUint64
|
|
||||||
}
|
|
||||||
return &testExternalServer{
|
|
||||||
t: t,
|
|
||||||
keyFunc: keyFunc,
|
|
||||||
sessionAt: sessionAt,
|
|
||||||
maxKeys: maxKeys,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testExternalServer) SetNextBatch(from uint64, to uint64) ([]byte, uint64, uint64, *HandoverProof, error) {
|
|
||||||
if from == 0 && to == 0 {
|
|
||||||
from = s.sessionAt
|
|
||||||
to = s.sessionAt + testExternalServerBatchSize
|
|
||||||
}
|
|
||||||
if to-from > testExternalServerBatchSize {
|
|
||||||
to = from + testExternalServerBatchSize - 1
|
|
||||||
}
|
|
||||||
if from >= s.maxKeys && to > s.maxKeys {
|
|
||||||
return nil, 0, 0, nil, io.EOF
|
|
||||||
}
|
|
||||||
if to > s.maxKeys {
|
|
||||||
to = s.maxKeys
|
|
||||||
}
|
|
||||||
b := make([]byte, HashSize*(to-from+1))
|
|
||||||
for i := from; i <= to; i++ {
|
|
||||||
s.keyFunc(b[(i-from)*HashSize:(i-from+1)*HashSize], i)
|
|
||||||
}
|
|
||||||
return b, from, to, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testExternalServer) GetData(context.Context, []byte) ([]byte, error) {
|
|
||||||
return make([]byte, 4096), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testExternalServer) Close() {}
|
|
||||||
|
|
||||||
// Sets the global value defaultSkipCheck.
|
|
||||||
// It should be used in test function defer to reset the global value
|
|
||||||
// to the original value.
|
|
||||||
//
|
|
||||||
// defer setDefaultSkipCheck(defaultSkipCheck)
|
|
||||||
// defaultSkipCheck = skipCheck
|
|
||||||
//
|
|
||||||
// This works as defer function arguments evaluations are evaluated as ususal,
|
|
||||||
// but only the function body invocation is deferred.
|
|
||||||
func setDefaultSkipCheck(skipCheck bool) {
|
|
||||||
defaultSkipCheck = skipCheck
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,19 @@ import (
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
"github.com/ethereum/go-ethereum/swarm/network"
|
||||||
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing"
|
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/state"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -308,159 +309,164 @@ func TestDeliveryFromNodes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck bool) {
|
func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck bool) {
|
||||||
defaultSkipCheck = skipCheck
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
toAddr = network.NewAddrFromNodeID
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
createStoreFunc = createTestLocalStorageFromSim
|
|
||||||
conf := &streamTesting.RunConfig{
|
|
||||||
Adapter: *adapter,
|
|
||||||
NodeCount: nodes,
|
|
||||||
ConnLevel: conns,
|
|
||||||
ToAddr: toAddr,
|
|
||||||
Services: services,
|
|
||||||
EnableMsgEvents: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
sim, teardown, err := streamTesting.NewSimulation(conf)
|
id := ctx.Config.ID
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
addr := network.NewAddrFromNodeID(id)
|
||||||
defer func() {
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
rpcSubscriptionsWg.Wait()
|
|
||||||
teardown()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
stores = make(map[discover.NodeID]storage.ChunkStore)
|
bucket.Store(bucketKeyStore, store)
|
||||||
for i, id := range sim.IDs {
|
cleanup = func() {
|
||||||
stores[id] = sim.Stores[i]
|
os.RemoveAll(datadir)
|
||||||
}
|
store.Close()
|
||||||
registries = make(map[discover.NodeID]*TestRegistry)
|
|
||||||
deliveries = make(map[discover.NodeID]*Delivery)
|
|
||||||
peerCount = func(id discover.NodeID) int {
|
|
||||||
if sim.IDs[0] == id || sim.IDs[nodes-1] == id {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
// here we distribute chunks of a random file into Stores of nodes 1 to nodes
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
rrFileStore := storage.NewFileStore(newRoundRobinStore(sim.Stores[1:]...), storage.NewFileStoreParams())
|
SkipCheck: skipCheck,
|
||||||
size := chunkCount * chunkSize
|
|
||||||
ctx := context.TODO()
|
|
||||||
fileHash, wait, err := rrFileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
|
||||||
// wait until all chunks stored
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
err = wait(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
errc := make(chan error, 1)
|
|
||||||
waitPeerErrC = make(chan error)
|
|
||||||
quitC := make(chan struct{})
|
|
||||||
defer close(quitC)
|
|
||||||
|
|
||||||
action := func(ctx context.Context) error {
|
|
||||||
// each node Subscribes to each other's swarmChunkServerStreamName
|
|
||||||
// need to wait till an aynchronous process registers the peers in streamer.peers
|
|
||||||
// that is used by Subscribe
|
|
||||||
// using a global err channel to share betweem action and node service
|
|
||||||
i := 0
|
|
||||||
for err := range waitPeerErrC {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for peers: %s", err)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i == nodes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// each node subscribes to the upstream swarm chunk server stream
|
|
||||||
// which responds to chunk retrieve requests all but the last node in the chain does not
|
|
||||||
for j := 0; j < nodes-1; j++ {
|
|
||||||
id := sim.IDs[j]
|
|
||||||
err := sim.CallClient(id, func(client *rpc.Client) error {
|
|
||||||
doneC, err := streamTesting.WatchDisconnections(id, client, errc, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-doneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
sid := sim.IDs[j+1]
|
|
||||||
return client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream(swarmChunkServerStreamName, "", false), NewRange(0, 0), Top)
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
bucket.Store(bucketKeyRegistry, r)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create a retriever FileStore for the pivot node
|
|
||||||
delivery := deliveries[sim.IDs[0]]
|
|
||||||
retrieveFunc := func(ctx context.Context, chunk *storage.Chunk) error {
|
retrieveFunc := func(ctx context.Context, chunk *storage.Chunk) error {
|
||||||
return delivery.RequestFromPeers(ctx, chunk.Addr[:], skipCheck)
|
return delivery.RequestFromPeers(ctx, chunk.Addr[:], skipCheck)
|
||||||
}
|
}
|
||||||
netStore := storage.NewNetStore(sim.Stores[0].(*storage.LocalStore), retrieveFunc)
|
netStore := storage.NewNetStore(localStore, retrieveFunc)
|
||||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
log.Info("Adding nodes to simulation")
|
||||||
|
_, err := sim.AddNodesAndConnectChain(nodes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Starting simulation")
|
||||||
|
ctx := context.Background()
|
||||||
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
|
nodeIDs := sim.UpNodeIDs()
|
||||||
|
//determine the pivot node to be the first node of the simulation
|
||||||
|
sim.SetPivotNode(nodeIDs[0])
|
||||||
|
//distribute chunks of a random file into Stores of nodes 1 to nodes
|
||||||
|
//we will do this by creating a file store with an underlying round-robin store:
|
||||||
|
//the file store will create a hash for the uploaded file, but every chunk will be
|
||||||
|
//distributed to different nodes via round-robin scheduling
|
||||||
|
log.Debug("Writing file to round-robin file store")
|
||||||
|
//to do this, we create an array for chunkstores (length minus one, the pivot node)
|
||||||
|
stores := make([]storage.ChunkStore, len(nodeIDs)-1)
|
||||||
|
//we then need to get all stores from the sim....
|
||||||
|
lStores := sim.NodesItems(bucketKeyStore)
|
||||||
|
i := 0
|
||||||
|
//...iterate the buckets...
|
||||||
|
for id, bucketVal := range lStores {
|
||||||
|
//...and remove the one which is the pivot node
|
||||||
|
if id == *sim.PivotNodeID() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//the other ones are added to the array...
|
||||||
|
stores[i] = bucketVal.(storage.ChunkStore)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
//...which then gets passed to the round-robin file store
|
||||||
|
roundRobinFileStore := storage.NewFileStore(newRoundRobinStore(stores...), storage.NewFileStoreParams())
|
||||||
|
//now we can actually upload a (random) file to the round-robin store
|
||||||
|
size := chunkCount * chunkSize
|
||||||
|
log.Debug("Storing data to file store")
|
||||||
|
fileHash, wait, err := roundRobinFileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
||||||
|
// wait until all chunks stored
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Waiting for kademlia")
|
||||||
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//each of the nodes (except pivot node) subscribes to the stream of the next node
|
||||||
|
for j, node := range nodeIDs[0 : nodes-1] {
|
||||||
|
sid := nodeIDs[j+1]
|
||||||
|
item, ok := sim.NodeItem(node, bucketKeyRegistry)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No registry")
|
||||||
|
}
|
||||||
|
registry := item.(*Registry)
|
||||||
|
err = registry.Subscribe(sid, NewStream(swarmChunkServerStreamName, "", false), NewRange(0, 0), Top)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the pivot node's filestore
|
||||||
|
item, ok := sim.NodeItem(*sim.PivotNodeID(), bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No filestore")
|
||||||
|
}
|
||||||
|
pivotFileStore := item.(*storage.FileStore)
|
||||||
|
log.Debug("Starting retrieval routine")
|
||||||
go func() {
|
go func() {
|
||||||
// start the retrieval on the pivot node - this will spawn retrieve requests for missing chunks
|
// start the retrieval on the pivot node - this will spawn retrieve requests for missing chunks
|
||||||
// we must wait for the peer connections to have started before requesting
|
// we must wait for the peer connections to have started before requesting
|
||||||
n, err := readAll(fileStore, fileHash)
|
n, err := readAll(pivotFileStore, fileHash)
|
||||||
log.Info(fmt.Sprintf("retrieved %v", fileHash), "read", n, "err", err)
|
log.Info(fmt.Sprintf("retrieved %v", fileHash), "read", n, "err", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- fmt.Errorf("requesting chunks action error: %v", err)
|
t.Fatalf("requesting chunks action error: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
|
||||||
|
log.Debug("Watching for disconnections")
|
||||||
|
disconnections := sim.PeerEvents(
|
||||||
|
context.Background(),
|
||||||
|
sim.NodeIDs(),
|
||||||
|
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for d := range disconnections {
|
||||||
|
if d.Error != nil {
|
||||||
|
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer)
|
||||||
|
t.Fatal(d.Error)
|
||||||
}
|
}
|
||||||
check := func(ctx context.Context, id discover.NodeID) (bool, error) {
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return false, err
|
|
||||||
case <-ctx.Done():
|
|
||||||
return false, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
//finally check that the pivot node gets all chunks via the root hash
|
||||||
|
log.Debug("Check retrieval")
|
||||||
|
success := true
|
||||||
var total int64
|
var total int64
|
||||||
err := sim.CallClient(id, func(client *rpc.Client) error {
|
total, err = readAll(pivotFileStore, fileHash)
|
||||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
if err != nil {
|
||||||
defer cancel()
|
return err
|
||||||
return client.CallContext(ctx, &total, "stream_readAll", common.BytesToHash(fileHash))
|
}
|
||||||
})
|
|
||||||
log.Info(fmt.Sprintf("check if %08x is available locally: number of bytes read %v/%v (error: %v)", fileHash, total, size, err))
|
log.Info(fmt.Sprintf("check if %08x is available locally: number of bytes read %v/%v (error: %v)", fileHash, total, size, err))
|
||||||
if err != nil || total != int64(size) {
|
if err != nil || total != int64(size) {
|
||||||
return false, nil
|
success = false
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.Step = &simulations.Step{
|
if !success {
|
||||||
Action: action,
|
return fmt.Errorf("Test failed, chunks not available on all nodes")
|
||||||
Trigger: streamTesting.Trigger(10*time.Millisecond, quitC, sim.IDs[0]),
|
|
||||||
// we are only testing the pivot node (net.Nodes[0])
|
|
||||||
Expect: &simulations.Expectation{
|
|
||||||
Nodes: sim.IDs[0:1],
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
startedAt := time.Now()
|
|
||||||
timeout := 300 * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
||||||
defer cancel()
|
|
||||||
result, err := sim.Run(ctx, conf)
|
|
||||||
finishedAt := time.Now()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Setting up simulation failed: %v", err)
|
|
||||||
}
|
}
|
||||||
|
log.Debug("Test terminated successfully")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
t.Fatalf("Simulation failed: %s", result.Error)
|
t.Fatal(result.Error)
|
||||||
}
|
}
|
||||||
streamTesting.CheckResult(t, result, startedAt, finishedAt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkDeliveryFromNodesWithoutCheck(b *testing.B) {
|
func BenchmarkDeliveryFromNodesWithoutCheck(b *testing.B) {
|
||||||
|
|
@ -490,138 +496,86 @@ func BenchmarkDeliveryFromNodesWithCheck(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skipCheck bool) {
|
func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skipCheck bool) {
|
||||||
defaultSkipCheck = skipCheck
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
toAddr = network.NewAddrFromNodeID
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
createStoreFunc = createTestLocalStorageFromSim
|
|
||||||
registries = make(map[discover.NodeID]*TestRegistry)
|
|
||||||
|
|
||||||
timeout := 300 * time.Second
|
id := ctx.Config.ID
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
addr := network.NewAddrFromNodeID(id)
|
||||||
defer cancel()
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
|
|
||||||
conf := &streamTesting.RunConfig{
|
|
||||||
Adapter: *adapter,
|
|
||||||
NodeCount: nodes,
|
|
||||||
ConnLevel: conns,
|
|
||||||
ToAddr: toAddr,
|
|
||||||
Services: services,
|
|
||||||
EnableMsgEvents: false,
|
|
||||||
}
|
|
||||||
sim, teardown, err := streamTesting.NewSimulation(conf)
|
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
|
||||||
defer func() {
|
|
||||||
rpcSubscriptionsWg.Wait()
|
|
||||||
teardown()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err.Error())
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
bucket.Store(bucketKeyStore, store)
|
||||||
|
cleanup = func() {
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
store.Close()
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
stores = make(map[discover.NodeID]storage.ChunkStore)
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
deliveries = make(map[discover.NodeID]*Delivery)
|
SkipCheck: skipCheck,
|
||||||
for i, id := range sim.IDs {
|
DoSync: true,
|
||||||
stores[id] = sim.Stores[i]
|
SyncUpdateDelay: 0,
|
||||||
}
|
|
||||||
peerCount = func(id discover.NodeID) int {
|
|
||||||
if sim.IDs[0] == id || sim.IDs[nodes-1] == id {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
// wait channel for all nodes all peer connections to set up
|
|
||||||
waitPeerErrC = make(chan error)
|
|
||||||
|
|
||||||
// create a FileStore for the last node in the chain which we are gonna write to
|
|
||||||
remoteFileStore := storage.NewFileStore(sim.Stores[nodes-1], storage.NewFileStoreParams())
|
|
||||||
|
|
||||||
// channel to signal simulation initialisation with action call complete
|
|
||||||
// or node disconnections
|
|
||||||
disconnectC := make(chan error)
|
|
||||||
quitC := make(chan struct{})
|
|
||||||
|
|
||||||
initC := make(chan error)
|
|
||||||
|
|
||||||
action := func(ctx context.Context) error {
|
|
||||||
// each node Subscribes to each other's swarmChunkServerStreamName
|
|
||||||
// need to wait till an aynchronous process registers the peers in streamer.peers
|
|
||||||
// that is used by Subscribe
|
|
||||||
// waitPeerErrC using a global err channel to share betweem action and node service
|
|
||||||
i := 0
|
|
||||||
for err := range waitPeerErrC {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for peers: %s", err)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i == nodes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
// each node except the last one subscribes to the upstream swarm chunk server stream
|
|
||||||
// which responds to chunk retrieve requests
|
|
||||||
for j := 0; j < nodes-1; j++ {
|
|
||||||
id := sim.IDs[j]
|
|
||||||
err = sim.CallClient(id, func(client *rpc.Client) error {
|
|
||||||
doneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-doneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
sid := sim.IDs[j+1] // the upstream peer's id
|
|
||||||
return client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream(swarmChunkServerStreamName, "", false), NewRange(0, 0), Top)
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initC <- err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// the check function is only triggered when the benchmark finishes
|
|
||||||
trigger := make(chan discover.NodeID)
|
|
||||||
check := func(ctx context.Context, id discover.NodeID) (_ bool, err error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.Step = &simulations.Step{
|
|
||||||
Action: action,
|
|
||||||
Trigger: trigger,
|
|
||||||
// we are only testing the pivot node (net.Nodes[0])
|
|
||||||
Expect: &simulations.Expectation{
|
|
||||||
Nodes: sim.IDs[0:1],
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the simulation in the background
|
|
||||||
errc := make(chan error)
|
|
||||||
go func() {
|
|
||||||
_, err := sim.Run(ctx, conf)
|
|
||||||
close(quitC)
|
|
||||||
errc <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
// wait for simulation action to complete stream subscriptions
|
|
||||||
err = <-initC
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("simulation failed to initialise. expected no error. got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a retriever FileStore for the pivot node
|
|
||||||
// by now deliveries are set for each node by the streamer service
|
|
||||||
delivery := deliveries[sim.IDs[0]]
|
|
||||||
retrieveFunc := func(ctx context.Context, chunk *storage.Chunk) error {
|
retrieveFunc := func(ctx context.Context, chunk *storage.Chunk) error {
|
||||||
return delivery.RequestFromPeers(ctx, chunk.Addr[:], skipCheck)
|
return delivery.RequestFromPeers(ctx, chunk.Addr[:], skipCheck)
|
||||||
}
|
}
|
||||||
netStore := storage.NewNetStore(sim.Stores[0].(*storage.LocalStore), retrieveFunc)
|
netStore := storage.NewNetStore(localStore, retrieveFunc)
|
||||||
|
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
log.Info("Initializing test config")
|
||||||
|
_, err := sim.AddNodesAndConnectChain(nodes)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
|
nodeIDs := sim.UpNodeIDs()
|
||||||
|
node := nodeIDs[len(nodeIDs)-1]
|
||||||
|
|
||||||
|
item, ok := sim.NodeItem(node, bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
b.Fatal("No filestore")
|
||||||
|
}
|
||||||
|
remoteFileStore := item.(*storage.FileStore)
|
||||||
|
|
||||||
|
pivotNode := nodeIDs[0]
|
||||||
|
item, ok = sim.NodeItem(pivotNode, bucketKeyNetStore)
|
||||||
|
if !ok {
|
||||||
|
b.Fatal("No filestore")
|
||||||
|
}
|
||||||
|
netStore := item.(*storage.NetStore)
|
||||||
|
|
||||||
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnections := sim.PeerEvents(
|
||||||
|
context.Background(),
|
||||||
|
sim.NodeIDs(),
|
||||||
|
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for d := range disconnections {
|
||||||
|
if d.Error != nil {
|
||||||
|
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer)
|
||||||
|
b.Fatal(d.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
// benchmark loop
|
// benchmark loop
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
|
@ -670,38 +624,18 @@ Loop:
|
||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
|
||||||
select {
|
|
||||||
case err = <-disconnectC:
|
|
||||||
if err != nil {
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if misses > 0 {
|
if misses > 0 {
|
||||||
err = fmt.Errorf("%v chunk not found out of %v", misses, total)
|
err = fmt.Errorf("%v chunk not found out of %v", misses, total)
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
|
||||||
case <-quitC:
|
|
||||||
case trigger <- sim.IDs[0]:
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = <-errc
|
|
||||||
} else {
|
|
||||||
if e := <-errc; e != nil {
|
|
||||||
b.Errorf("sim.Run function error: %v", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchmark over, trigger the check function to conclude the simulation
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("expected no error. got %v", err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
b.Fatal(result.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestLocalStorageFromSim(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error) {
|
|
||||||
return stores[id], nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,52 +22,22 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
"github.com/ethereum/go-ethereum/swarm/network"
|
||||||
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing"
|
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||||
"github.com/ethereum/go-ethereum/swarm/state"
|
"github.com/ethereum/go-ethereum/swarm/state"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
externalStreamName = "externalStream"
|
|
||||||
externalStreamSessionAt uint64 = 50
|
|
||||||
externalStreamMaxKeys uint64 = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
func newIntervalsStreamerService(ctx *adapters.ServiceContext) (node.Service, error) {
|
|
||||||
id := ctx.Config.ID
|
|
||||||
addr := toAddr(id)
|
|
||||||
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
|
||||||
store := stores[id].(*storage.LocalStore)
|
|
||||||
db := storage.NewDBAPI(store)
|
|
||||||
delivery := NewDelivery(kad, db)
|
|
||||||
deliveries[id] = delivery
|
|
||||||
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
|
||||||
SkipCheck: defaultSkipCheck,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.RegisterClientFunc(externalStreamName, func(p *Peer, t string, live bool) (Client, error) {
|
|
||||||
return newTestExternalClient(db), nil
|
|
||||||
})
|
|
||||||
r.RegisterServerFunc(externalStreamName, func(p *Peer, t string, live bool) (Server, error) {
|
|
||||||
return newTestExternalServer(t, externalStreamSessionAt, externalStreamMaxKeys, nil), nil
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
waitPeerErrC <- waitForPeers(r, 1*time.Second, peerCount(id))
|
|
||||||
}()
|
|
||||||
return &TestExternalRegistry{r}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntervals(t *testing.T) {
|
func TestIntervals(t *testing.T) {
|
||||||
testIntervals(t, true, nil, false)
|
testIntervals(t, true, nil, false)
|
||||||
testIntervals(t, false, NewRange(9, 26), false)
|
testIntervals(t, false, NewRange(9, 26), false)
|
||||||
|
|
@ -81,95 +51,112 @@ func TestIntervals(t *testing.T) {
|
||||||
func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
nodes := 2
|
nodes := 2
|
||||||
chunkCount := dataChunkCount
|
chunkCount := dataChunkCount
|
||||||
|
externalStreamName := "externalStream"
|
||||||
|
externalStreamSessionAt := uint64(50)
|
||||||
|
externalStreamMaxKeys := uint64(100)
|
||||||
|
|
||||||
defer setDefaultSkipCheck(defaultSkipCheck)
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
defaultSkipCheck = skipCheck
|
"intervalsStreamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
|
|
||||||
toAddr = network.NewAddrFromNodeID
|
id := ctx.Config.ID
|
||||||
conf := &streamTesting.RunConfig{
|
addr := network.NewAddrFromNodeID(id)
|
||||||
Adapter: *adapter,
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
NodeCount: nodes,
|
if err != nil {
|
||||||
ConnLevel: 1,
|
return nil, nil, err
|
||||||
ToAddr: toAddr,
|
|
||||||
Services: services,
|
|
||||||
DefaultService: "intervalsStreamer",
|
|
||||||
}
|
}
|
||||||
|
bucket.Store(bucketKeyStore, store)
|
||||||
|
cleanup = func() {
|
||||||
|
store.Close()
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
sim, teardown, err := streamTesting.NewSimulation(conf)
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
SkipCheck: skipCheck,
|
||||||
defer func() {
|
})
|
||||||
rpcSubscriptionsWg.Wait()
|
bucket.Store(bucketKeyRegistry, r)
|
||||||
teardown()
|
|
||||||
}()
|
r.RegisterClientFunc(externalStreamName, func(p *Peer, t string, live bool) (Client, error) {
|
||||||
|
return newTestExternalClient(db), nil
|
||||||
|
})
|
||||||
|
r.RegisterServerFunc(externalStreamName, func(p *Peer, t string, live bool) (Server, error) {
|
||||||
|
return newTestExternalServer(t, externalStreamSessionAt, externalStreamMaxKeys, nil), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams())
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
log.Info("Adding nodes to simulation")
|
||||||
|
_, err := sim.AddNodesAndConnectChain(nodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stores = make(map[discover.NodeID]storage.ChunkStore)
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
|
||||||
deliveries = make(map[discover.NodeID]*Delivery)
|
defer cancel()
|
||||||
for i, id := range sim.IDs {
|
|
||||||
stores[id] = sim.Stores[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
peerCount = func(id discover.NodeID) int {
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
return 1
|
nodeIDs := sim.UpNodeIDs()
|
||||||
}
|
storer := nodeIDs[0]
|
||||||
|
checker := nodeIDs[1]
|
||||||
|
|
||||||
|
item, ok := sim.NodeItem(storer, bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No filestore")
|
||||||
|
}
|
||||||
|
fileStore := item.(*storage.FileStore)
|
||||||
|
|
||||||
fileStore := storage.NewFileStore(sim.Stores[0], storage.NewFileStoreParams())
|
|
||||||
size := chunkCount * chunkSize
|
size := chunkCount * chunkSize
|
||||||
ctx := context.TODO()
|
|
||||||
_, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
_, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Store error: %v", "err", err)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = wait(ctx)
|
err = wait(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Wait error: %v", "err", err)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
errc := make(chan error, 1)
|
item, ok = sim.NodeItem(checker, bucketKeyRegistry)
|
||||||
waitPeerErrC = make(chan error)
|
if !ok {
|
||||||
quitC := make(chan struct{})
|
return fmt.Errorf("No registry")
|
||||||
defer close(quitC)
|
|
||||||
|
|
||||||
action := func(ctx context.Context) error {
|
|
||||||
i := 0
|
|
||||||
for err := range waitPeerErrC {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for peers: %s", err)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i == nodes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id := sim.IDs[1]
|
|
||||||
|
|
||||||
err := sim.CallClient(id, func(client *rpc.Client) error {
|
|
||||||
|
|
||||||
sid := sim.IDs[0]
|
|
||||||
|
|
||||||
doneC, err := streamTesting.WatchDisconnections(id, client, errc, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-doneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 100*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err = client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream(externalStreamName, "", live), history, Top)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
registry := item.(*Registry)
|
||||||
|
|
||||||
liveErrC := make(chan error)
|
liveErrC := make(chan error)
|
||||||
historyErrC := make(chan error)
|
historyErrC := make(chan error)
|
||||||
|
|
||||||
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
log.Error("WaitKademlia error: %v", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Watching for disconnections")
|
||||||
|
disconnections := sim.PeerEvents(
|
||||||
|
context.Background(),
|
||||||
|
sim.NodeIDs(),
|
||||||
|
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for d := range disconnections {
|
||||||
|
if d.Error != nil {
|
||||||
|
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer)
|
||||||
|
t.Fatal(d.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if !live {
|
if !live {
|
||||||
close(liveErrC)
|
close(liveErrC)
|
||||||
|
|
@ -182,17 +169,16 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// live stream
|
// live stream
|
||||||
liveHashesChan := make(chan []byte)
|
var liveHashesChan chan []byte
|
||||||
liveSubscription, err := client.Subscribe(ctx, "stream", liveHashesChan, "getHashes", sid, NewStream(externalStreamName, "", true))
|
liveHashesChan, err = getHashes(ctx, registry, storer, NewStream(externalStreamName, "", true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Subscription error: %v", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer liveSubscription.Unsubscribe()
|
|
||||||
|
|
||||||
i := externalStreamSessionAt
|
i := externalStreamSessionAt
|
||||||
|
|
||||||
// we have subscribed, enable notifications
|
// we have subscribed, enable notifications
|
||||||
err = client.CallContext(ctx, nil, "stream_enableNotifications", sid, NewStream(externalStreamName, "", true))
|
err = enableNotifications(registry, storer, NewStream(externalStreamName, "", true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -209,8 +195,6 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
if i > externalStreamMaxKeys {
|
if i > externalStreamMaxKeys {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case err = <-liveSubscription.Err():
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -229,12 +213,11 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// history stream
|
// history stream
|
||||||
historyHashesChan := make(chan []byte)
|
var historyHashesChan chan []byte
|
||||||
historySubscription, err := client.Subscribe(ctx, "stream", historyHashesChan, "getHashes", sid, NewStream(externalStreamName, "", false))
|
historyHashesChan, err = getHashes(ctx, registry, storer, NewStream(externalStreamName, "", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer historySubscription.Unsubscribe()
|
|
||||||
|
|
||||||
var i uint64
|
var i uint64
|
||||||
historyTo := externalStreamMaxKeys
|
historyTo := externalStreamMaxKeys
|
||||||
|
|
@ -246,7 +229,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have subscribed, enable notifications
|
// we have subscribed, enable notifications
|
||||||
err = client.CallContext(ctx, nil, "stream_enableNotifications", sid, NewStream(externalStreamName, "", false))
|
err = enableNotifications(registry, storer, NewStream(externalStreamName, "", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -263,14 +246,16 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
if i > historyTo {
|
if i > historyTo {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case err = <-historySubscription.Err():
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
err = registry.Subscribe(storer, NewStream(externalStreamName, "", live), history, Top)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := <-liveErrC; err != nil {
|
if err := <-liveErrC; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -280,38 +265,123 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return err
|
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatal(result.Error)
|
||||||
}
|
}
|
||||||
check := func(ctx context.Context, id discover.NodeID) (bool, error) {
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return false, err
|
|
||||||
case <-ctx.Done():
|
|
||||||
return false, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.Step = &simulations.Step{
|
func getHashes(ctx context.Context, r *Registry, peerID discover.NodeID, s Stream) (chan []byte, error) {
|
||||||
Action: action,
|
peer := r.getPeer(peerID)
|
||||||
Trigger: streamTesting.Trigger(10*time.Millisecond, quitC, sim.IDs[0]),
|
|
||||||
Expect: &simulations.Expectation{
|
client, err := peer.getClient(ctx, s)
|
||||||
Nodes: sim.IDs[1:1],
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
startedAt := time.Now()
|
|
||||||
timeout := 300 * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
||||||
defer cancel()
|
|
||||||
result, err := sim.Run(ctx, conf)
|
|
||||||
finishedAt := time.Now()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Setting up simulation failed: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if result.Error != nil {
|
|
||||||
t.Fatalf("Simulation failed: %s", result.Error)
|
c := client.Client.(*testExternalClient)
|
||||||
|
|
||||||
|
return c.hashes, nil
|
||||||
}
|
}
|
||||||
streamTesting.CheckResult(t, result, startedAt, finishedAt)
|
|
||||||
|
func enableNotifications(r *Registry, peerID discover.NodeID, s Stream) error {
|
||||||
|
peer := r.getPeer(peerID)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client, err := peer.getClient(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(client.Client.(*testExternalClient).enableNotificationsC)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testExternalClient struct {
|
||||||
|
hashes chan []byte
|
||||||
|
db *storage.DBAPI
|
||||||
|
enableNotificationsC chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestExternalClient(db *storage.DBAPI) *testExternalClient {
|
||||||
|
return &testExternalClient{
|
||||||
|
hashes: make(chan []byte),
|
||||||
|
db: db,
|
||||||
|
enableNotificationsC: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testExternalClient) NeedData(ctx context.Context, hash []byte) func() {
|
||||||
|
chunk, _ := c.db.GetOrCreateRequest(ctx, hash)
|
||||||
|
if chunk.ReqC == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.hashes <- hash
|
||||||
|
//NOTE: This was failing on go1.9.x with a deadlock.
|
||||||
|
//Sometimes this function would just block
|
||||||
|
//It is commented now, but it may be well worth after the chunk refactor
|
||||||
|
//to re-enable this and see if the problem has been addressed
|
||||||
|
/*
|
||||||
|
return func() {
|
||||||
|
return chunk.WaitToStore()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testExternalClient) BatchDone(Stream, uint64, []byte, []byte) func() (*TakeoverProof, error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testExternalClient) Close() {}
|
||||||
|
|
||||||
|
const testExternalServerBatchSize = 10
|
||||||
|
|
||||||
|
type testExternalServer struct {
|
||||||
|
t string
|
||||||
|
keyFunc func(key []byte, index uint64)
|
||||||
|
sessionAt uint64
|
||||||
|
maxKeys uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestExternalServer(t string, sessionAt, maxKeys uint64, keyFunc func(key []byte, index uint64)) *testExternalServer {
|
||||||
|
if keyFunc == nil {
|
||||||
|
keyFunc = binary.BigEndian.PutUint64
|
||||||
|
}
|
||||||
|
return &testExternalServer{
|
||||||
|
t: t,
|
||||||
|
keyFunc: keyFunc,
|
||||||
|
sessionAt: sessionAt,
|
||||||
|
maxKeys: maxKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testExternalServer) SetNextBatch(from uint64, to uint64) ([]byte, uint64, uint64, *HandoverProof, error) {
|
||||||
|
if from == 0 && to == 0 {
|
||||||
|
from = s.sessionAt
|
||||||
|
to = s.sessionAt + testExternalServerBatchSize
|
||||||
|
}
|
||||||
|
if to-from > testExternalServerBatchSize {
|
||||||
|
to = from + testExternalServerBatchSize - 1
|
||||||
|
}
|
||||||
|
if from >= s.maxKeys && to > s.maxKeys {
|
||||||
|
return nil, 0, 0, nil, io.EOF
|
||||||
|
}
|
||||||
|
if to > s.maxKeys {
|
||||||
|
to = s.maxKeys
|
||||||
|
}
|
||||||
|
b := make([]byte, HashSize*(to-from+1))
|
||||||
|
for i := from; i <= to; i++ {
|
||||||
|
s.keyFunc(b[(i-from)*HashSize:(i-from+1)*HashSize], i)
|
||||||
|
}
|
||||||
|
return b, from, to, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testExternalServer) GetData(context.Context, []byte) ([]byte, error) {
|
||||||
|
return make([]byte, 4096), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testExternalServer) Close() {}
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,19 @@ package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
crand "crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"os"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
"github.com/ethereum/go-ethereum/swarm/network"
|
||||||
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing"
|
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/state"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,40 +39,6 @@ const (
|
||||||
maxFileSize = 40
|
maxFileSize = 40
|
||||||
)
|
)
|
||||||
|
|
||||||
func initRetrievalTest() {
|
|
||||||
//global func to get overlay address from discover ID
|
|
||||||
toAddr = func(id discover.NodeID) *network.BzzAddr {
|
|
||||||
addr := network.NewAddrFromNodeID(id)
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
//global func to create local store
|
|
||||||
createStoreFunc = createTestLocalStorageForId
|
|
||||||
//local stores
|
|
||||||
stores = make(map[discover.NodeID]storage.ChunkStore)
|
|
||||||
//data directories for each node and store
|
|
||||||
datadirs = make(map[discover.NodeID]string)
|
|
||||||
//deliveries for each node
|
|
||||||
deliveries = make(map[discover.NodeID]*Delivery)
|
|
||||||
//global retrieve func
|
|
||||||
getRetrieveFunc = func(id discover.NodeID) func(ctx context.Context, chunk *storage.Chunk) error {
|
|
||||||
return func(ctx context.Context, chunk *storage.Chunk) error {
|
|
||||||
skipCheck := true
|
|
||||||
return deliveries[id].RequestFromPeers(ctx, chunk.Addr[:], skipCheck)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//registries, map of discover.NodeID to its streamer
|
|
||||||
registries = make(map[discover.NodeID]*TestRegistry)
|
|
||||||
//not needed for this test but required from common_test for NewStreamService
|
|
||||||
waitPeerErrC = make(chan error)
|
|
||||||
//also not needed for this test but required for NewStreamService
|
|
||||||
peerCount = func(id discover.NodeID) int {
|
|
||||||
if ids[0] == id || ids[len(ids)-1] == id {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//This test is a retrieval test for nodes.
|
//This test is a retrieval test for nodes.
|
||||||
//A configurable number of nodes can be
|
//A configurable number of nodes can be
|
||||||
//provided to the test.
|
//provided to the test.
|
||||||
|
|
@ -81,7 +46,10 @@ func initRetrievalTest() {
|
||||||
//Number of nodes can be provided via commandline too.
|
//Number of nodes can be provided via commandline too.
|
||||||
func TestFileRetrieval(t *testing.T) {
|
func TestFileRetrieval(t *testing.T) {
|
||||||
if *nodes != 0 {
|
if *nodes != 0 {
|
||||||
fileRetrievalTest(t, *nodes)
|
err := runFileRetrievalTest(*nodes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nodeCnt := []int{16}
|
nodeCnt := []int{16}
|
||||||
//if the `longrunning` flag has been provided
|
//if the `longrunning` flag has been provided
|
||||||
|
|
@ -90,7 +58,10 @@ func TestFileRetrieval(t *testing.T) {
|
||||||
nodeCnt = append(nodeCnt, 32, 64, 128)
|
nodeCnt = append(nodeCnt, 32, 64, 128)
|
||||||
}
|
}
|
||||||
for _, n := range nodeCnt {
|
for _, n := range nodeCnt {
|
||||||
fileRetrievalTest(t, n)
|
err := runFileRetrievalTest(n)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +76,10 @@ func TestRetrieval(t *testing.T) {
|
||||||
//if nodes/chunks have been provided via commandline,
|
//if nodes/chunks have been provided via commandline,
|
||||||
//run the tests with these values
|
//run the tests with these values
|
||||||
if *nodes != 0 && *chunks != 0 {
|
if *nodes != 0 && *chunks != 0 {
|
||||||
retrievalTest(t, *chunks, *nodes)
|
err := runRetrievalTest(*chunks, *nodes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var nodeCnt []int
|
var nodeCnt []int
|
||||||
var chnkCnt []int
|
var chnkCnt []int
|
||||||
|
|
@ -121,76 +95,17 @@ func TestRetrieval(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, n := range nodeCnt {
|
for _, n := range nodeCnt {
|
||||||
for _, c := range chnkCnt {
|
for _, c := range chnkCnt {
|
||||||
retrievalTest(t, c, n)
|
err := runRetrievalTest(c, n)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Every test runs 3 times, a live, a history, and a live AND history
|
|
||||||
func fileRetrievalTest(t *testing.T, nodeCount int) {
|
|
||||||
//test live and NO history
|
|
||||||
log.Info("Testing live and no history", "nodeCount", nodeCount)
|
|
||||||
live = true
|
|
||||||
history = false
|
|
||||||
err := runFileRetrievalTest(nodeCount)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
//test history only
|
|
||||||
log.Info("Testing history only", "nodeCount", nodeCount)
|
|
||||||
live = false
|
|
||||||
history = true
|
|
||||||
err = runFileRetrievalTest(nodeCount)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
//finally test live and history
|
|
||||||
log.Info("Testing live and history", "nodeCount", nodeCount)
|
|
||||||
live = true
|
|
||||||
err = runFileRetrievalTest(nodeCount)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Every test runs 3 times, a live, a history, and a live AND history
|
|
||||||
func retrievalTest(t *testing.T, chunkCount int, nodeCount int) {
|
|
||||||
//test live and NO history
|
|
||||||
log.Info("Testing live and no history", "chunkCount", chunkCount, "nodeCount", nodeCount)
|
|
||||||
live = true
|
|
||||||
history = false
|
|
||||||
err := runRetrievalTest(chunkCount, nodeCount)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
//test history only
|
|
||||||
log.Info("Testing history only", "chunkCount", chunkCount, "nodeCount", nodeCount)
|
|
||||||
live = false
|
|
||||||
history = true
|
|
||||||
err = runRetrievalTest(chunkCount, nodeCount)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
//finally test live and history
|
|
||||||
log.Info("Testing live and history", "chunkCount", chunkCount, "nodeCount", nodeCount)
|
|
||||||
live = true
|
|
||||||
err = runRetrievalTest(chunkCount, nodeCount)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
The upload is done by dependency to the global
|
|
||||||
`live` and `history` variables;
|
|
||||||
|
|
||||||
If `live` is set, first stream subscriptions are established,
|
|
||||||
then files are uploaded to nodes.
|
|
||||||
|
|
||||||
If `history` is enabled, first upload files, then build up subscriptions.
|
|
||||||
|
|
||||||
The test loads a snapshot file to construct the swarm network,
|
The test loads a snapshot file to construct the swarm network,
|
||||||
assuming that the snapshot file identifies a healthy
|
assuming that the snapshot file identifies a healthy
|
||||||
kademlia network. Nevertheless a health check runs in the
|
kademlia network. Nevertheless a health check runs in the
|
||||||
|
|
@ -199,261 +114,129 @@ simulation's `action` function.
|
||||||
The snapshot should have 'streamer' in its service list.
|
The snapshot should have 'streamer' in its service list.
|
||||||
*/
|
*/
|
||||||
func runFileRetrievalTest(nodeCount int) error {
|
func runFileRetrievalTest(nodeCount int) error {
|
||||||
//for every run (live, history), int the variables
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
initRetrievalTest()
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
//the ids of the snapshot nodes, initiate only now as we need nodeCount
|
|
||||||
ids = make([]discover.NodeID, nodeCount)
|
id := ctx.Config.ID
|
||||||
//channel to check for disconnection errors
|
addr := network.NewAddrFromNodeID(id)
|
||||||
disconnectC := make(chan error)
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
//channel to close disconnection watcher routine
|
if err != nil {
|
||||||
quitC := make(chan struct{})
|
return nil, nil, err
|
||||||
//the test conf (using same as in `snapshot_sync_test`
|
}
|
||||||
conf = &synctestConfig{}
|
bucket.Store(bucketKeyStore, store)
|
||||||
|
cleanup = func() {
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
store.Close()
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
|
DoSync: true,
|
||||||
|
SyncUpdateDelay: 3 * time.Second,
|
||||||
|
})
|
||||||
|
|
||||||
|
fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams())
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
log.Info("Initializing test config")
|
||||||
|
|
||||||
|
conf := &synctestConfig{}
|
||||||
|
//map of discover ID to indexes of chunks expected at that ID
|
||||||
|
conf.idToChunksMap = make(map[discover.NodeID][]int)
|
||||||
//map of overlay address to discover ID
|
//map of overlay address to discover ID
|
||||||
conf.addrToIdMap = make(map[string]discover.NodeID)
|
conf.addrToIDMap = make(map[string]discover.NodeID)
|
||||||
//array where the generated chunk hashes will be stored
|
//array where the generated chunk hashes will be stored
|
||||||
conf.hashes = make([]storage.Address, 0)
|
conf.hashes = make([]storage.Address, 0)
|
||||||
//load nodes from the snapshot file
|
|
||||||
net, err := initNetWithSnapshot(nodeCount)
|
err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
|
||||||
//do cleanup after test is terminated
|
ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||||
defer func() {
|
defer cancelSimRun()
|
||||||
//shutdown the snapshot network
|
|
||||||
net.Shutdown()
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
//after the test, clean up local stores initialized with createLocalStoreForId
|
nodeIDs := sim.UpNodeIDs()
|
||||||
localStoreCleanup()
|
for _, n := range nodeIDs {
|
||||||
//finally clear all data directories
|
//get the kademlia overlay address from this ID
|
||||||
datadirsCleanup()
|
a := network.ToOverlayAddr(n.Bytes())
|
||||||
}()
|
|
||||||
//get the nodes of the network
|
|
||||||
nodes := net.GetNodes()
|
|
||||||
//iterate over all nodes...
|
|
||||||
for c := 0; c < len(nodes); c++ {
|
|
||||||
//create an array of discovery nodeIDS
|
|
||||||
ids[c] = nodes[c].ID()
|
|
||||||
a := network.ToOverlayAddr(ids[c].Bytes())
|
|
||||||
//append it to the array of all overlay addresses
|
//append it to the array of all overlay addresses
|
||||||
conf.addrs = append(conf.addrs, a)
|
conf.addrs = append(conf.addrs, a)
|
||||||
conf.addrToIdMap[string(a)] = ids[c]
|
//the proximity calculation is on overlay addr,
|
||||||
|
//the p2p/simulations check func triggers on discover.NodeID,
|
||||||
|
//so we need to know which overlay addr maps to which nodeID
|
||||||
|
conf.addrToIDMap[string(a)] = n
|
||||||
}
|
}
|
||||||
|
|
||||||
//needed for healthy call
|
|
||||||
ppmap = network.NewPeerPotMap(testMinProxBinSize, conf.addrs)
|
|
||||||
|
|
||||||
//an array for the random files
|
//an array for the random files
|
||||||
var randomFiles []string
|
var randomFiles []string
|
||||||
//channel to signal when the upload has finished
|
//channel to signal when the upload has finished
|
||||||
uploadFinished := make(chan struct{})
|
//uploadFinished := make(chan struct{})
|
||||||
//channel to trigger new node checks
|
//channel to trigger new node checks
|
||||||
trigger := make(chan discover.NodeID)
|
|
||||||
//simulation action
|
|
||||||
action := func(ctx context.Context) error {
|
|
||||||
//first run the health check on all nodes,
|
|
||||||
//wait until nodes are all healthy
|
|
||||||
ticker := time.NewTicker(200 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
healthy := true
|
|
||||||
for _, id := range ids {
|
|
||||||
r := registries[id]
|
|
||||||
//PeerPot for this node
|
|
||||||
addr := common.Bytes2Hex(r.addr.OAddr)
|
|
||||||
pp := ppmap[addr]
|
|
||||||
//call Healthy RPC
|
|
||||||
h := r.delivery.overlay.Healthy(pp)
|
|
||||||
//print info
|
|
||||||
log.Debug(r.delivery.overlay.String())
|
|
||||||
log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full))
|
|
||||||
if !h.GotNN || !h.Full {
|
|
||||||
healthy = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if healthy {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if history {
|
conf.hashes, randomFiles, err = uploadFilesToNodes(sim)
|
||||||
log.Info("Uploading for history")
|
|
||||||
//If testing only history, we upload the chunk(s) first
|
|
||||||
conf.hashes, randomFiles, err = uploadFilesToNodes(nodes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
|
||||||
//variables needed to wait for all subscriptions established before uploading
|
|
||||||
errc := make(chan error)
|
|
||||||
|
|
||||||
//now setup and start event watching in order to know when we can upload
|
|
||||||
ctx, watchCancel := context.WithTimeout(context.Background(), MaxTimeout*time.Second)
|
|
||||||
defer watchCancel()
|
|
||||||
|
|
||||||
log.Info("Setting up stream subscription")
|
|
||||||
//We need two iterations, one to subscribe to the subscription events
|
|
||||||
//(so we know when setup phase is finished), and one to
|
|
||||||
//actually run the stream subscriptions. We can't do it in the same iteration,
|
|
||||||
//because while the first nodes in the loop are setting up subscriptions,
|
|
||||||
//the latter ones have not subscribed to listen to peer events yet,
|
|
||||||
//and then we miss events.
|
|
||||||
|
|
||||||
//first iteration: setup disconnection watcher and subscribe to peer events
|
|
||||||
for j, id := range ids {
|
|
||||||
log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j))
|
|
||||||
client, err := net.GetNode(id).Client()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
wsDoneC := watchSubscriptionEvents(ctx, id, client, errc, quitC)
|
|
||||||
// doneC is nil, the error happened which is sent to errc channel, already
|
|
||||||
if wsDoneC == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-wsDoneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
//watch for peers disconnecting
|
|
||||||
wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-wdDoneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//second iteration: start syncing and setup stream subscriptions
|
|
||||||
for j, id := range ids {
|
|
||||||
log.Trace(fmt.Sprintf("Start syncing and stream subscriptions: %d", j))
|
|
||||||
client, err := net.GetNode(id).Client()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//start syncing!
|
|
||||||
var cnt int
|
|
||||||
err = client.CallContext(ctx, &cnt, "stream_startSyncing")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//increment the number of subscriptions we need to wait for
|
|
||||||
//by the count returned from startSyncing (SYNC subscriptions)
|
|
||||||
subscriptionCount += cnt
|
|
||||||
//now also add the number of RETRIEVAL_REQUEST subscriptions
|
|
||||||
for snid := range registries[id].peers {
|
|
||||||
subscriptionCount++
|
|
||||||
err = client.CallContext(ctx, nil, "stream_subscribeStream", snid, NewStream(swarmChunkServerStreamName, "", false), nil, Top)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//now wait until the number of expected subscriptions has been finished
|
|
||||||
//`watchSubscriptionEvents` will write with a `nil` value to errc
|
|
||||||
//every time a `SubscriptionMsg` has been received
|
|
||||||
for err := range errc {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//`nil` received, decrement count
|
|
||||||
subscriptionCount--
|
|
||||||
//all subscriptions received
|
|
||||||
if subscriptionCount == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Stream subscriptions successfully requested, action terminated")
|
|
||||||
|
|
||||||
if live {
|
|
||||||
//upload generated files to nodes
|
|
||||||
var hashes []storage.Address
|
|
||||||
var rfiles []string
|
|
||||||
hashes, rfiles, err = uploadFilesToNodes(nodes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conf.hashes = append(conf.hashes, hashes...)
|
|
||||||
randomFiles = append(randomFiles, rfiles...)
|
|
||||||
//signal to the trigger loop that the upload has finished
|
|
||||||
uploadFinished <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//check defines what will be checked during the test
|
|
||||||
check := func(ctx context.Context, id discover.NodeID) (bool, error) {
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return false, ctx.Err()
|
|
||||||
case e := <-disconnectC:
|
|
||||||
log.Error(e.Error())
|
|
||||||
return false, fmt.Errorf("Disconnect event detected, network unhealthy")
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
log.Trace(fmt.Sprintf("Checking node: %s", id))
|
|
||||||
//if there are more than one chunk, test only succeeds if all expected chunks are found
|
|
||||||
allSuccess := true
|
|
||||||
|
|
||||||
|
// File retrieval check is repeated until all uploaded files are retrieved from all nodes
|
||||||
|
// or until the timeout is reached.
|
||||||
|
allSuccess := false
|
||||||
|
for !allSuccess {
|
||||||
|
for _, id := range nodeIDs {
|
||||||
|
//for each expected chunk, check if it is in the local store
|
||||||
|
localChunks := conf.idToChunksMap[id]
|
||||||
|
localSuccess := true
|
||||||
|
for _, ch := range localChunks {
|
||||||
|
//get the real chunk by the index in the index array
|
||||||
|
chunk := conf.hashes[ch]
|
||||||
|
log.Trace(fmt.Sprintf("node has chunk: %s:", chunk))
|
||||||
|
//check if the expected chunk is indeed in the localstore
|
||||||
|
var err error
|
||||||
//check on the node's FileStore (netstore)
|
//check on the node's FileStore (netstore)
|
||||||
fileStore := registries[id].fileStore
|
item, ok := sim.NodeItem(id, bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No registry")
|
||||||
|
}
|
||||||
|
fileStore := item.(*storage.FileStore)
|
||||||
//check all chunks
|
//check all chunks
|
||||||
for i, hash := range conf.hashes {
|
for i, hash := range conf.hashes {
|
||||||
reader, _ := fileStore.Retrieve(context.TODO(), hash)
|
reader, _ := fileStore.Retrieve(context.TODO(), hash)
|
||||||
//check that we can read the file size and that it corresponds to the generated file size
|
//check that we can read the file size and that it corresponds to the generated file size
|
||||||
if s, err := reader.Size(context.TODO(), nil); err != nil || s != int64(len(randomFiles[i])) {
|
if s, err := reader.Size(ctx, nil); err != nil || s != int64(len(randomFiles[i])) {
|
||||||
allSuccess = false
|
allSuccess = false
|
||||||
log.Warn("Retrieve error", "err", err, "hash", hash, "nodeId", id)
|
log.Warn("Retrieve error", "err", err, "hash", hash, "nodeId", id)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(fmt.Sprintf("File with root hash %x successfully retrieved", hash))
|
log.Debug(fmt.Sprintf("File with root hash %x successfully retrieved", hash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
return allSuccess, nil
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
|
||||||
}
|
localSuccess = false
|
||||||
|
} else {
|
||||||
//for each tick, run the checks on all nodes
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
|
||||||
timingTicker := time.NewTicker(5 * time.Second)
|
|
||||||
defer timingTicker.Stop()
|
|
||||||
go func() {
|
|
||||||
//for live upload, we should wait for uploads to have finished
|
|
||||||
//before starting to trigger the checks, due to file size
|
|
||||||
if live {
|
|
||||||
<-uploadFinished
|
|
||||||
}
|
|
||||||
for range timingTicker.C {
|
|
||||||
for i := 0; i < len(ids); i++ {
|
|
||||||
log.Trace(fmt.Sprintf("triggering step %d, id %s", i, ids[i]))
|
|
||||||
trigger <- ids[i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
allSuccess = localSuccess
|
||||||
|
}
|
||||||
log.Info("Starting simulation run...")
|
}
|
||||||
|
if !allSuccess {
|
||||||
timeout := MaxTimeout * time.Second
|
return fmt.Errorf("Not all chunks succeeded!")
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
}
|
||||||
defer cancel()
|
return nil
|
||||||
|
|
||||||
//run the simulation
|
|
||||||
result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{
|
|
||||||
Action: action,
|
|
||||||
Trigger: trigger,
|
|
||||||
Expect: &simulations.Expectation{
|
|
||||||
Nodes: ids,
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
|
|
@ -466,14 +249,6 @@ func runFileRetrievalTest(nodeCount int) error {
|
||||||
/*
|
/*
|
||||||
The test generates the given number of chunks.
|
The test generates the given number of chunks.
|
||||||
|
|
||||||
The upload is done by dependency to the global
|
|
||||||
`live` and `history` variables;
|
|
||||||
|
|
||||||
If `live` is set, first stream subscriptions are established, then
|
|
||||||
upload to a random node.
|
|
||||||
|
|
||||||
If `history` is enabled, first upload then build up subscriptions.
|
|
||||||
|
|
||||||
The test loads a snapshot file to construct the swarm network,
|
The test loads a snapshot file to construct the swarm network,
|
||||||
assuming that the snapshot file identifies a healthy
|
assuming that the snapshot file identifies a healthy
|
||||||
kademlia network. Nevertheless a health check runs in the
|
kademlia network. Nevertheless a health check runs in the
|
||||||
|
|
@ -482,259 +257,129 @@ simulation's `action` function.
|
||||||
The snapshot should have 'streamer' in its service list.
|
The snapshot should have 'streamer' in its service list.
|
||||||
*/
|
*/
|
||||||
func runRetrievalTest(chunkCount int, nodeCount int) error {
|
func runRetrievalTest(chunkCount int, nodeCount int) error {
|
||||||
//for every run (live, history), int the variables
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
initRetrievalTest()
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
//the ids of the snapshot nodes, initiate only now as we need nodeCount
|
|
||||||
ids = make([]discover.NodeID, nodeCount)
|
id := ctx.Config.ID
|
||||||
//channel to check for disconnection errors
|
addr := network.NewAddrFromNodeID(id)
|
||||||
disconnectC := make(chan error)
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
//channel to close disconnection watcher routine
|
if err != nil {
|
||||||
quitC := make(chan struct{})
|
return nil, nil, err
|
||||||
//the test conf (using same as in `snapshot_sync_test`
|
}
|
||||||
conf = &synctestConfig{}
|
bucket.Store(bucketKeyStore, store)
|
||||||
|
cleanup = func() {
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
store.Close()
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
|
DoSync: true,
|
||||||
|
SyncUpdateDelay: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams())
|
||||||
|
bucketKeyFileStore = simulation.BucketKey("filestore")
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
conf := &synctestConfig{}
|
||||||
|
//map of discover ID to indexes of chunks expected at that ID
|
||||||
|
conf.idToChunksMap = make(map[discover.NodeID][]int)
|
||||||
//map of overlay address to discover ID
|
//map of overlay address to discover ID
|
||||||
conf.addrToIdMap = make(map[string]discover.NodeID)
|
conf.addrToIDMap = make(map[string]discover.NodeID)
|
||||||
//array where the generated chunk hashes will be stored
|
//array where the generated chunk hashes will be stored
|
||||||
conf.hashes = make([]storage.Address, 0)
|
conf.hashes = make([]storage.Address, 0)
|
||||||
//load nodes from the snapshot file
|
|
||||||
net, err := initNetWithSnapshot(nodeCount)
|
err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
|
||||||
//do cleanup after test is terminated
|
ctx := context.Background()
|
||||||
defer func() {
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
//shutdown the snapshot network
|
nodeIDs := sim.UpNodeIDs()
|
||||||
net.Shutdown()
|
for _, n := range nodeIDs {
|
||||||
//after the test, clean up local stores initialized with createLocalStoreForId
|
//get the kademlia overlay address from this ID
|
||||||
localStoreCleanup()
|
a := network.ToOverlayAddr(n.Bytes())
|
||||||
//finally clear all data directories
|
|
||||||
datadirsCleanup()
|
|
||||||
}()
|
|
||||||
//get the nodes of the network
|
|
||||||
nodes := net.GetNodes()
|
|
||||||
//select one index at random...
|
|
||||||
idx := rand.Intn(len(nodes))
|
|
||||||
//...and get the the node at that index
|
|
||||||
//this is the node selected for upload
|
|
||||||
uploadNode := nodes[idx]
|
|
||||||
//iterate over all nodes...
|
|
||||||
for c := 0; c < len(nodes); c++ {
|
|
||||||
//create an array of discovery nodeIDS
|
|
||||||
ids[c] = nodes[c].ID()
|
|
||||||
a := network.ToOverlayAddr(ids[c].Bytes())
|
|
||||||
//append it to the array of all overlay addresses
|
//append it to the array of all overlay addresses
|
||||||
conf.addrs = append(conf.addrs, a)
|
conf.addrs = append(conf.addrs, a)
|
||||||
conf.addrToIdMap[string(a)] = ids[c]
|
//the proximity calculation is on overlay addr,
|
||||||
|
//the p2p/simulations check func triggers on discover.NodeID,
|
||||||
|
//so we need to know which overlay addr maps to which nodeID
|
||||||
|
conf.addrToIDMap[string(a)] = n
|
||||||
}
|
}
|
||||||
|
|
||||||
//needed for healthy call
|
//an array for the random files
|
||||||
ppmap = network.NewPeerPotMap(testMinProxBinSize, conf.addrs)
|
var randomFiles []string
|
||||||
|
//this is the node selected for upload
|
||||||
trigger := make(chan discover.NodeID)
|
node := sim.RandomUpNode()
|
||||||
//simulation action
|
item, ok := sim.NodeItem(node.ID, bucketKeyStore)
|
||||||
action := func(ctx context.Context) error {
|
if !ok {
|
||||||
//first run the health check on all nodes,
|
return fmt.Errorf("No localstore")
|
||||||
//wait until nodes are all healthy
|
|
||||||
ticker := time.NewTicker(200 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
healthy := true
|
|
||||||
for _, id := range ids {
|
|
||||||
r := registries[id]
|
|
||||||
//PeerPot for this node
|
|
||||||
addr := common.Bytes2Hex(network.ToOverlayAddr(id.Bytes()))
|
|
||||||
pp := ppmap[addr]
|
|
||||||
//call Healthy RPC
|
|
||||||
h := r.delivery.overlay.Healthy(pp)
|
|
||||||
//print info
|
|
||||||
log.Debug(r.delivery.overlay.String())
|
|
||||||
log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full))
|
|
||||||
if !h.GotNN || !h.Full {
|
|
||||||
healthy = false
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
lstore := item.(*storage.LocalStore)
|
||||||
if healthy {
|
conf.hashes, err = uploadFileToSingleNodeStore(node.ID, chunkCount, lstore)
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if history {
|
|
||||||
log.Info("Uploading for history")
|
|
||||||
//If testing only history, we upload the chunk(s) first
|
|
||||||
conf.hashes, err = uploadFileToSingleNodeStore(uploadNode.ID(), chunkCount)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
|
||||||
//variables needed to wait for all subscriptions established before uploading
|
|
||||||
errc := make(chan error)
|
|
||||||
|
|
||||||
//now setup and start event watching in order to know when we can upload
|
|
||||||
ctx, watchCancel := context.WithTimeout(context.Background(), MaxTimeout*time.Second)
|
|
||||||
defer watchCancel()
|
|
||||||
|
|
||||||
log.Info("Setting up stream subscription")
|
|
||||||
//We need two iterations, one to subscribe to the subscription events
|
|
||||||
//(so we know when setup phase is finished), and one to
|
|
||||||
//actually run the stream subscriptions. We can't do it in the same iteration,
|
|
||||||
//because while the first nodes in the loop are setting up subscriptions,
|
|
||||||
//the latter ones have not subscribed to listen to peer events yet,
|
|
||||||
//and then we miss events.
|
|
||||||
|
|
||||||
//first iteration: setup disconnection watcher and subscribe to peer events
|
|
||||||
for j, id := range ids {
|
|
||||||
log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j))
|
|
||||||
client, err := net.GetNode(id).Client()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//check for `SubscribeMsg` events to know when setup phase is complete
|
// File retrieval check is repeated until all uploaded files are retrieved from all nodes
|
||||||
wsDoneC := watchSubscriptionEvents(ctx, id, client, errc, quitC)
|
// or until the timeout is reached.
|
||||||
// doneC is nil, the error happened which is sent to errc channel, already
|
allSuccess := false
|
||||||
if wsDoneC == nil {
|
for !allSuccess {
|
||||||
continue
|
for _, id := range nodeIDs {
|
||||||
}
|
//for each expected chunk, check if it is in the local store
|
||||||
rpcSubscriptionsWg.Add(1)
|
localChunks := conf.idToChunksMap[id]
|
||||||
go func() {
|
localSuccess := true
|
||||||
<-wsDoneC
|
for _, ch := range localChunks {
|
||||||
rpcSubscriptionsWg.Done()
|
//get the real chunk by the index in the index array
|
||||||
}()
|
chunk := conf.hashes[ch]
|
||||||
|
log.Trace(fmt.Sprintf("node has chunk: %s:", chunk))
|
||||||
//watch for peers disconnecting
|
//check if the expected chunk is indeed in the localstore
|
||||||
wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
|
var err error
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-wdDoneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//second iteration: start syncing and setup stream subscriptions
|
|
||||||
for j, id := range ids {
|
|
||||||
log.Trace(fmt.Sprintf("Start syncing and stream subscriptions: %d", j))
|
|
||||||
client, err := net.GetNode(id).Client()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//start syncing!
|
|
||||||
var cnt int
|
|
||||||
err = client.CallContext(ctx, &cnt, "stream_startSyncing")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//increment the number of subscriptions we need to wait for
|
|
||||||
//by the count returned from startSyncing (SYNC subscriptions)
|
|
||||||
subscriptionCount += cnt
|
|
||||||
//now also add the number of RETRIEVAL_REQUEST subscriptions
|
|
||||||
for snid := range registries[id].peers {
|
|
||||||
subscriptionCount++
|
|
||||||
err = client.CallContext(ctx, nil, "stream_subscribeStream", snid, NewStream(swarmChunkServerStreamName, "", false), nil, Top)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//now wait until the number of expected subscriptions has been finished
|
|
||||||
//`watchSubscriptionEvents` will write with a `nil` value to errc
|
|
||||||
//every time a `SubscriptionMsg` has been received
|
|
||||||
for err := range errc {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//`nil` received, decrement count
|
|
||||||
subscriptionCount--
|
|
||||||
//all subscriptions received
|
|
||||||
if subscriptionCount == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Stream subscriptions successfully requested, action terminated")
|
|
||||||
|
|
||||||
if live {
|
|
||||||
//now upload the chunks to the selected random single node
|
|
||||||
chnks, err := uploadFileToSingleNodeStore(uploadNode.ID(), chunkCount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conf.hashes = append(conf.hashes, chnks...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
chunkSize := storage.DefaultChunkSize
|
|
||||||
|
|
||||||
//check defines what will be checked during the test
|
|
||||||
check := func(ctx context.Context, id discover.NodeID) (bool, error) {
|
|
||||||
|
|
||||||
//don't check the uploader node
|
|
||||||
if id == uploadNode.ID() {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return false, ctx.Err()
|
|
||||||
case e := <-disconnectC:
|
|
||||||
log.Error(e.Error())
|
|
||||||
return false, fmt.Errorf("Disconnect event detected, network unhealthy")
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
log.Trace(fmt.Sprintf("Checking node: %s", id))
|
|
||||||
//if there are more than one chunk, test only succeeds if all expected chunks are found
|
|
||||||
allSuccess := true
|
|
||||||
|
|
||||||
//check on the node's FileStore (netstore)
|
//check on the node's FileStore (netstore)
|
||||||
fileStore := registries[id].fileStore
|
item, ok := sim.NodeItem(id, bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No registry")
|
||||||
|
}
|
||||||
|
fileStore := item.(*storage.FileStore)
|
||||||
//check all chunks
|
//check all chunks
|
||||||
for _, chnk := range conf.hashes {
|
for i, hash := range conf.hashes {
|
||||||
reader, _ := fileStore.Retrieve(context.TODO(), chnk)
|
reader, _ := fileStore.Retrieve(context.TODO(), hash)
|
||||||
//assuming that reading the Size of the chunk is enough to know we found it
|
//check that we can read the file size and that it corresponds to the generated file size
|
||||||
if s, err := reader.Size(context.TODO(), nil); err != nil || s != chunkSize {
|
if s, err := reader.Size(ctx, nil); err != nil || s != int64(len(randomFiles[i])) {
|
||||||
allSuccess = false
|
allSuccess = false
|
||||||
log.Warn("Retrieve error", "err", err, "chunk", chnk, "nodeId", id)
|
log.Warn("Retrieve error", "err", err, "hash", hash, "nodeId", id)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(fmt.Sprintf("Chunk %x found", chnk))
|
log.Debug(fmt.Sprintf("File with root hash %x successfully retrieved", hash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allSuccess, nil
|
if err != nil {
|
||||||
}
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
|
||||||
|
localSuccess = false
|
||||||
//for each tick, run the checks on all nodes
|
} else {
|
||||||
timingTicker := time.NewTicker(5 * time.Second)
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
|
||||||
defer timingTicker.Stop()
|
|
||||||
go func() {
|
|
||||||
for range timingTicker.C {
|
|
||||||
for i := 0; i < len(ids); i++ {
|
|
||||||
log.Trace(fmt.Sprintf("triggering step %d, id %s", i, ids[i]))
|
|
||||||
trigger <- ids[i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
allSuccess = localSuccess
|
||||||
|
}
|
||||||
log.Info("Starting simulation run...")
|
}
|
||||||
|
if !allSuccess {
|
||||||
timeout := MaxTimeout * time.Second
|
return fmt.Errorf("Not all chunks succeeded!")
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
}
|
||||||
defer cancel()
|
return nil
|
||||||
|
|
||||||
//run the simulation
|
|
||||||
result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{
|
|
||||||
Action: action,
|
|
||||||
Trigger: trigger,
|
|
||||||
Expect: &simulations.Expectation{
|
|
||||||
Nodes: ids,
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
|
|
@ -743,53 +388,3 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//upload generated files to nodes
|
|
||||||
//every node gets one file uploaded
|
|
||||||
func uploadFilesToNodes(nodes []*simulations.Node) ([]storage.Address, []string, error) {
|
|
||||||
nodeCnt := len(nodes)
|
|
||||||
log.Debug(fmt.Sprintf("Uploading %d files to nodes", nodeCnt))
|
|
||||||
//array holding generated files
|
|
||||||
rfiles := make([]string, nodeCnt)
|
|
||||||
//array holding the root hashes of the files
|
|
||||||
rootAddrs := make([]storage.Address, nodeCnt)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
//for every node, generate a file and upload
|
|
||||||
for i, n := range nodes {
|
|
||||||
id := n.ID()
|
|
||||||
fileStore := registries[id].fileStore
|
|
||||||
//generate a file
|
|
||||||
rfiles[i], err = generateRandomFile()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
//store it (upload it) on the FileStore
|
|
||||||
ctx := context.TODO()
|
|
||||||
rk, wait, err := fileStore.Store(ctx, strings.NewReader(rfiles[i]), int64(len(rfiles[i])), false)
|
|
||||||
log.Debug("Uploaded random string file to node")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
err = wait(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
rootAddrs[i] = rk
|
|
||||||
}
|
|
||||||
return rootAddrs, rfiles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//generate a random file (string)
|
|
||||||
func generateRandomFile() (string, error) {
|
|
||||||
//generate a random file size between minFileSize and maxFileSize
|
|
||||||
fileSize := rand.Intn(maxFileSize-minFileSize) + minFileSize
|
|
||||||
log.Debug(fmt.Sprintf("Generated file with filesize %d kB", fileSize))
|
|
||||||
b := make([]byte, fileSize*1024)
|
|
||||||
_, err := crand.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error generating random file.", "err", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(b), nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,8 @@ package stream
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -31,82 +27,27 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
"github.com/ethereum/go-ethereum/swarm/network"
|
||||||
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing"
|
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||||
"github.com/ethereum/go-ethereum/swarm/pot"
|
"github.com/ethereum/go-ethereum/swarm/pot"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/state"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
|
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testMinProxBinSize = 2
|
const testMinProxBinSize = 2
|
||||||
const MaxTimeout = 600
|
const MaxTimeout = 600
|
||||||
|
|
||||||
var (
|
|
||||||
pof = pot.DefaultPof(256)
|
|
||||||
|
|
||||||
conf *synctestConfig
|
|
||||||
ids []discover.NodeID
|
|
||||||
datadirs map[discover.NodeID]string
|
|
||||||
ppmap map[string]*network.PeerPot
|
|
||||||
|
|
||||||
live bool
|
|
||||||
history bool
|
|
||||||
|
|
||||||
longrunning = flag.Bool("longrunning", false, "do run long-running tests")
|
|
||||||
)
|
|
||||||
|
|
||||||
type synctestConfig struct {
|
type synctestConfig struct {
|
||||||
addrs [][]byte
|
addrs [][]byte
|
||||||
hashes []storage.Address
|
hashes []storage.Address
|
||||||
idToChunksMap map[discover.NodeID][]int
|
idToChunksMap map[discover.NodeID][]int
|
||||||
chunksToNodesMap map[string][]int
|
chunksToNodesMap map[string][]int
|
||||||
addrToIdMap map[string]discover.NodeID
|
addrToIDMap map[string]discover.NodeID
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
//common_test needs to initialize the test in a init() func
|
|
||||||
//in order for adapters to register the NewStreamerService;
|
|
||||||
//this service is dependent on some global variables
|
|
||||||
//we thus need to initialize first as init() as well.
|
|
||||||
func initSyncTest() {
|
|
||||||
//assign the toAddr func so NewStreamerService can build the addr
|
|
||||||
toAddr = func(id discover.NodeID) *network.BzzAddr {
|
|
||||||
addr := network.NewAddrFromNodeID(id)
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
//global func to create local store
|
|
||||||
if *useMockStore {
|
|
||||||
createStoreFunc = createMockStore
|
|
||||||
} else {
|
|
||||||
createStoreFunc = createTestLocalStorageForId
|
|
||||||
}
|
|
||||||
//local stores
|
|
||||||
stores = make(map[discover.NodeID]storage.ChunkStore)
|
|
||||||
//data directories for each node and store
|
|
||||||
datadirs = make(map[discover.NodeID]string)
|
|
||||||
//deliveries for each node
|
|
||||||
deliveries = make(map[discover.NodeID]*Delivery)
|
|
||||||
//registries, map of discover.NodeID to its streamer
|
|
||||||
registries = make(map[discover.NodeID]*TestRegistry)
|
|
||||||
//not needed for this test but required from common_test for NewStreamService
|
|
||||||
waitPeerErrC = make(chan error)
|
|
||||||
//also not needed for this test but required for NewStreamService
|
|
||||||
peerCount = func(id discover.NodeID) int {
|
|
||||||
if ids[0] == id || ids[len(ids)-1] == id {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
if *useMockStore {
|
|
||||||
createGlobalStore()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//This test is a syncing test for nodes.
|
//This test is a syncing test for nodes.
|
||||||
|
|
@ -116,12 +57,12 @@ func initSyncTest() {
|
||||||
//to the pivot node, and we check that nodes get the chunks
|
//to the pivot node, and we check that nodes get the chunks
|
||||||
//they are expected to store based on the syncing protocol.
|
//they are expected to store based on the syncing protocol.
|
||||||
//Number of chunks and nodes can be provided via commandline too.
|
//Number of chunks and nodes can be provided via commandline too.
|
||||||
func TestSyncing(t *testing.T) {
|
func TestSyncingViaGlobalSync(t *testing.T) {
|
||||||
//if nodes/chunks have been provided via commandline,
|
//if nodes/chunks have been provided via commandline,
|
||||||
//run the tests with these values
|
//run the tests with these values
|
||||||
if *nodes != 0 && *chunks != 0 {
|
if *nodes != 0 && *chunks != 0 {
|
||||||
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes))
|
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes))
|
||||||
testSyncing(t, *chunks, *nodes)
|
testSyncingViaGlobalSync(t, *chunks, *nodes)
|
||||||
} else {
|
} else {
|
||||||
var nodeCnt []int
|
var nodeCnt []int
|
||||||
var chnkCnt []int
|
var chnkCnt []int
|
||||||
|
|
@ -138,51 +79,196 @@ func TestSyncing(t *testing.T) {
|
||||||
for _, chnk := range chnkCnt {
|
for _, chnk := range chnkCnt {
|
||||||
for _, n := range nodeCnt {
|
for _, n := range nodeCnt {
|
||||||
log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n))
|
log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n))
|
||||||
testSyncing(t, chnk, n)
|
testSyncingViaGlobalSync(t, chnk, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Do run the tests
|
func TestSyncingViaDirectSubscribe(t *testing.T) {
|
||||||
//Every test runs 3 times, a live, a history, and a live AND history
|
//if nodes/chunks have been provided via commandline,
|
||||||
func testSyncing(t *testing.T, chunkCount int, nodeCount int) {
|
//run the tests with these values
|
||||||
//test live and NO history
|
if *nodes != 0 && *chunks != 0 {
|
||||||
log.Info("Testing live and no history")
|
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes))
|
||||||
live = true
|
err := testSyncingViaDirectSubscribe(*chunks, *nodes)
|
||||||
history = false
|
|
||||||
err := runSyncTest(chunkCount, nodeCount, live, history)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
//test history only
|
} else {
|
||||||
log.Info("Testing history only")
|
var nodeCnt []int
|
||||||
live = false
|
var chnkCnt []int
|
||||||
history = true
|
//if the `longrunning` flag has been provided
|
||||||
err = runSyncTest(chunkCount, nodeCount, live, history)
|
//run more test combinations
|
||||||
|
if *longrunning {
|
||||||
|
chnkCnt = []int{1, 8, 32, 256, 1024}
|
||||||
|
nodeCnt = []int{32, 16}
|
||||||
|
} else {
|
||||||
|
//default test
|
||||||
|
chnkCnt = []int{4, 32}
|
||||||
|
nodeCnt = []int{32, 16}
|
||||||
|
}
|
||||||
|
for _, chnk := range chnkCnt {
|
||||||
|
for _, n := range nodeCnt {
|
||||||
|
log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n))
|
||||||
|
err := testSyncingViaDirectSubscribe(chnk, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
//finally test live and history
|
}
|
||||||
log.Info("Testing live and history")
|
}
|
||||||
live = true
|
}
|
||||||
err = runSyncTest(chunkCount, nodeCount, live, history)
|
}
|
||||||
|
|
||||||
|
func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) {
|
||||||
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
|
|
||||||
|
id := ctx.Config.ID
|
||||||
|
addr := network.NewAddrFromNodeID(id)
|
||||||
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
bucket.Store(bucketKeyStore, store)
|
||||||
|
cleanup = func() {
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
store.Close()
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
|
DoSync: true,
|
||||||
|
SyncUpdateDelay: 3 * time.Second,
|
||||||
|
})
|
||||||
|
bucket.Store(bucketKeyRegistry, r)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
log.Info("Initializing test config")
|
||||||
|
|
||||||
|
conf := &synctestConfig{}
|
||||||
|
//map of discover ID to indexes of chunks expected at that ID
|
||||||
|
conf.idToChunksMap = make(map[discover.NodeID][]int)
|
||||||
|
//map of overlay address to discover ID
|
||||||
|
conf.addrToIDMap = make(map[string]discover.NodeID)
|
||||||
|
//array where the generated chunk hashes will be stored
|
||||||
|
conf.hashes = make([]storage.Address, 0)
|
||||||
|
|
||||||
|
err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||||
|
defer cancelSimRun()
|
||||||
|
|
||||||
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
|
nodeIDs := sim.UpNodeIDs()
|
||||||
|
for _, n := range nodeIDs {
|
||||||
|
//get the kademlia overlay address from this ID
|
||||||
|
a := network.ToOverlayAddr(n.Bytes())
|
||||||
|
//append it to the array of all overlay addresses
|
||||||
|
conf.addrs = append(conf.addrs, a)
|
||||||
|
//the proximity calculation is on overlay addr,
|
||||||
|
//the p2p/simulations check func triggers on discover.NodeID,
|
||||||
|
//so we need to know which overlay addr maps to which nodeID
|
||||||
|
conf.addrToIDMap[string(a)] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the the node at that index
|
||||||
|
//this is the node selected for upload
|
||||||
|
node := sim.RandomUpNode()
|
||||||
|
item, ok := sim.NodeItem(node.ID, bucketKeyStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No localstore")
|
||||||
|
}
|
||||||
|
lstore := item.(*storage.LocalStore)
|
||||||
|
hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conf.hashes = append(conf.hashes, hashes...)
|
||||||
|
mapKeysToNodes(conf)
|
||||||
|
|
||||||
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File retrieval check is repeated until all uploaded files are retrieved from all nodes
|
||||||
|
// or until the timeout is reached.
|
||||||
|
allSuccess := false
|
||||||
|
var gDir string
|
||||||
|
var globalStore *mockdb.GlobalStore
|
||||||
|
if *useMockStore {
|
||||||
|
gDir, globalStore, err = createGlobalStore()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil")
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(gDir)
|
||||||
|
err := globalStore.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error closing global store! %v", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
for !allSuccess {
|
||||||
|
for _, id := range nodeIDs {
|
||||||
|
//for each expected chunk, check if it is in the local store
|
||||||
|
localChunks := conf.idToChunksMap[id]
|
||||||
|
localSuccess := true
|
||||||
|
for _, ch := range localChunks {
|
||||||
|
//get the real chunk by the index in the index array
|
||||||
|
chunk := conf.hashes[ch]
|
||||||
|
log.Trace(fmt.Sprintf("node has chunk: %s:", chunk))
|
||||||
|
//check if the expected chunk is indeed in the localstore
|
||||||
|
var err error
|
||||||
|
if *useMockStore {
|
||||||
|
//use the globalStore if the mockStore should be used; in that case,
|
||||||
|
//the complete localStore stack is bypassed for getting the chunk
|
||||||
|
_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk)
|
||||||
|
} else {
|
||||||
|
//use the actual localstore
|
||||||
|
item, ok := sim.NodeItem(id, bucketKeyStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Error accessing localstore")
|
||||||
|
}
|
||||||
|
lstore := item.(*storage.LocalStore)
|
||||||
|
_, err = lstore.Get(ctx, chunk)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
|
||||||
|
localSuccess = false
|
||||||
|
// Do not get crazy with logging the warn message
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
} else {
|
||||||
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allSuccess = localSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allSuccess {
|
||||||
|
return fmt.Errorf("Not all chunks succeeded!")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatal(result.Error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The test generates the given number of chunks
|
The test generates the given number of chunks
|
||||||
|
|
||||||
The upload is done by dependency to the global
|
|
||||||
`live` and `history` variables;
|
|
||||||
|
|
||||||
If `live` is set, first stream subscriptions are established, then
|
|
||||||
upload to a random node.
|
|
||||||
|
|
||||||
If `history` is enabled, first upload then build up subscriptions.
|
|
||||||
|
|
||||||
For every chunk generated, the nearest node addresses
|
For every chunk generated, the nearest node addresses
|
||||||
are identified, we verify that the nodes closer to the
|
are identified, we verify that the nodes closer to the
|
||||||
chunk addresses actually do have the chunks in their local stores.
|
chunk addresses actually do have the chunks in their local stores.
|
||||||
|
|
@ -190,178 +276,84 @@ chunk addresses actually do have the chunks in their local stores.
|
||||||
The test loads a snapshot file to construct the swarm network,
|
The test loads a snapshot file to construct the swarm network,
|
||||||
assuming that the snapshot file identifies a healthy
|
assuming that the snapshot file identifies a healthy
|
||||||
kademlia network. The snapshot should have 'streamer' in its service list.
|
kademlia network. The snapshot should have 'streamer' in its service list.
|
||||||
|
|
||||||
For every test run, a series of three tests will be executed:
|
|
||||||
- a LIVE test first, where first subscriptions are established,
|
|
||||||
then a file (random chunks) is uploaded
|
|
||||||
- a HISTORY test, where the file is uploaded first, and then
|
|
||||||
the subscriptions are established
|
|
||||||
- a crude LIVE AND HISTORY test last, where (different) chunks
|
|
||||||
are uploaded twice, once before and once after subscriptions
|
|
||||||
*/
|
*/
|
||||||
func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
|
func testSyncingViaDirectSubscribe(chunkCount int, nodeCount int) error {
|
||||||
initSyncTest()
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
//the ids of the snapshot nodes, initiate only now as we need nodeCount
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
ids = make([]discover.NodeID, nodeCount)
|
|
||||||
//initialize the test struct
|
id := ctx.Config.ID
|
||||||
conf = &synctestConfig{}
|
addr := network.NewAddrFromNodeID(id)
|
||||||
|
store, datadir, err := createTestLocalStorageForID(id, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
bucket.Store(bucketKeyStore, store)
|
||||||
|
cleanup = func() {
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
store.Close()
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
|
||||||
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), nil)
|
||||||
|
bucket.Store(bucketKeyRegistry, r)
|
||||||
|
|
||||||
|
fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams())
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||||
|
defer cancelSimRun()
|
||||||
|
|
||||||
|
conf := &synctestConfig{}
|
||||||
//map of discover ID to indexes of chunks expected at that ID
|
//map of discover ID to indexes of chunks expected at that ID
|
||||||
conf.idToChunksMap = make(map[discover.NodeID][]int)
|
conf.idToChunksMap = make(map[discover.NodeID][]int)
|
||||||
//map of overlay address to discover ID
|
//map of overlay address to discover ID
|
||||||
conf.addrToIdMap = make(map[string]discover.NodeID)
|
conf.addrToIDMap = make(map[string]discover.NodeID)
|
||||||
//array where the generated chunk hashes will be stored
|
//array where the generated chunk hashes will be stored
|
||||||
conf.hashes = make([]storage.Address, 0)
|
conf.hashes = make([]storage.Address, 0)
|
||||||
//channel to trigger node checks in the simulation
|
|
||||||
trigger := make(chan discover.NodeID)
|
|
||||||
//channel to check for disconnection errors
|
|
||||||
disconnectC := make(chan error)
|
|
||||||
//channel to close disconnection watcher routine
|
|
||||||
quitC := make(chan struct{})
|
|
||||||
|
|
||||||
//load nodes from the snapshot file
|
err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
|
||||||
net, err := initNetWithSnapshot(nodeCount)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
|
||||||
//do cleanup after test is terminated
|
|
||||||
defer func() {
|
|
||||||
// close quitC channel to signall all goroutines to clanup
|
|
||||||
// before calling simulation network shutdown.
|
|
||||||
close(quitC)
|
|
||||||
//wait for all rpc subscriptions to unsubscribe
|
|
||||||
rpcSubscriptionsWg.Wait()
|
|
||||||
//shutdown the snapshot network
|
|
||||||
net.Shutdown()
|
|
||||||
//after the test, clean up local stores initialized with createLocalStoreForId
|
|
||||||
localStoreCleanup()
|
|
||||||
//finally clear all data directories
|
|
||||||
datadirsCleanup()
|
|
||||||
}()
|
|
||||||
//get the nodes of the network
|
|
||||||
nodes := net.GetNodes()
|
|
||||||
//select one index at random...
|
|
||||||
idx := rand.Intn(len(nodes))
|
|
||||||
//...and get the the node at that index
|
|
||||||
//this is the node selected for upload
|
|
||||||
node := nodes[idx]
|
|
||||||
|
|
||||||
log.Info("Initializing test config")
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
//iterate over all nodes...
|
nodeIDs := sim.UpNodeIDs()
|
||||||
for c := 0; c < len(nodes); c++ {
|
for _, n := range nodeIDs {
|
||||||
//create an array of discovery node IDs
|
|
||||||
ids[c] = nodes[c].ID()
|
|
||||||
//get the kademlia overlay address from this ID
|
//get the kademlia overlay address from this ID
|
||||||
a := network.ToOverlayAddr(ids[c].Bytes())
|
a := network.ToOverlayAddr(n.Bytes())
|
||||||
//append it to the array of all overlay addresses
|
//append it to the array of all overlay addresses
|
||||||
conf.addrs = append(conf.addrs, a)
|
conf.addrs = append(conf.addrs, a)
|
||||||
//the proximity calculation is on overlay addr,
|
//the proximity calculation is on overlay addr,
|
||||||
//the p2p/simulations check func triggers on discover.NodeID,
|
//the p2p/simulations check func triggers on discover.NodeID,
|
||||||
//so we need to know which overlay addr maps to which nodeID
|
//so we need to know which overlay addr maps to which nodeID
|
||||||
conf.addrToIdMap[string(a)] = ids[c]
|
conf.addrToIDMap[string(a)] = n
|
||||||
}
|
|
||||||
log.Info("Test config successfully initialized")
|
|
||||||
|
|
||||||
//only needed for healthy call when debugging
|
|
||||||
ppmap = network.NewPeerPotMap(testMinProxBinSize, conf.addrs)
|
|
||||||
|
|
||||||
//define the action to be performed before the test checks: start syncing
|
|
||||||
action := func(ctx context.Context) error {
|
|
||||||
//first run the health check on all nodes,
|
|
||||||
//wait until nodes are all healthy
|
|
||||||
ticker := time.NewTicker(200 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
healthy := true
|
|
||||||
for _, id := range ids {
|
|
||||||
r := registries[id]
|
|
||||||
//PeerPot for this node
|
|
||||||
addr := common.Bytes2Hex(network.ToOverlayAddr(id.Bytes()))
|
|
||||||
pp := ppmap[addr]
|
|
||||||
//call Healthy RPC
|
|
||||||
h := r.delivery.overlay.Healthy(pp)
|
|
||||||
//print info
|
|
||||||
log.Debug(r.delivery.overlay.String())
|
|
||||||
log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full))
|
|
||||||
if !h.GotNN || !h.Full {
|
|
||||||
healthy = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if healthy {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if history {
|
var subscriptionCount int
|
||||||
log.Info("Uploading for history")
|
|
||||||
//If testing only history, we upload the chunk(s) first
|
|
||||||
chunks, err := uploadFileToSingleNodeStore(node.ID(), chunkCount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conf.hashes = append(conf.hashes, chunks...)
|
|
||||||
//finally map chunks to the closest addresses
|
|
||||||
mapKeysToNodes(conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
//variables needed to wait for all subscriptions established before uploading
|
filter := simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("stream").MsgCode(4)
|
||||||
errc := make(chan error)
|
eventC := sim.PeerEvents(ctx, nodeIDs, filter)
|
||||||
|
|
||||||
//now setup and start event watching in order to know when we can upload
|
for j, node := range nodeIDs {
|
||||||
ctx, watchCancel := context.WithTimeout(context.Background(), MaxTimeout*time.Second)
|
|
||||||
defer watchCancel()
|
|
||||||
|
|
||||||
log.Info("Setting up stream subscription")
|
|
||||||
|
|
||||||
//We need two iterations, one to subscribe to the subscription events
|
|
||||||
//(so we know when setup phase is finished), and one to
|
|
||||||
//actually run the stream subscriptions. We can't do it in the same iteration,
|
|
||||||
//because while the first nodes in the loop are setting up subscriptions,
|
|
||||||
//the latter ones have not subscribed to listen to peer events yet,
|
|
||||||
//and then we miss events.
|
|
||||||
|
|
||||||
//first iteration: setup disconnection watcher and subscribe to peer events
|
|
||||||
for j, id := range ids {
|
|
||||||
log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j))
|
|
||||||
client, err := net.GetNode(id).Client()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wsDoneC := watchSubscriptionEvents(ctx, id, client, errc, quitC)
|
|
||||||
// doneC is nil, the error happened which is sent to errc channel, already
|
|
||||||
if wsDoneC == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-wsDoneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
//watch for peers disconnecting
|
|
||||||
wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-wdDoneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//second iteration: start syncing
|
|
||||||
for j, id := range ids {
|
|
||||||
log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j))
|
log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j))
|
||||||
client, err := net.GetNode(id).Client()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//start syncing!
|
//start syncing!
|
||||||
|
item, ok := sim.NodeItem(node, bucketKeyRegistry)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No registry")
|
||||||
|
}
|
||||||
|
registry := item.(*Registry)
|
||||||
|
|
||||||
var cnt int
|
var cnt int
|
||||||
err = client.CallContext(ctx, &cnt, "stream_startSyncing")
|
cnt, err = startSyncing(registry, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -370,57 +362,50 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
|
||||||
subscriptionCount += cnt
|
subscriptionCount += cnt
|
||||||
}
|
}
|
||||||
|
|
||||||
//now wait until the number of expected subscriptions has been finished
|
for e := range eventC {
|
||||||
//`watchSubscriptionEvents` will write with a `nil` value to errc
|
if e.Error != nil {
|
||||||
for err := range errc {
|
return e.Error
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
//`nil` received, decrement count
|
|
||||||
subscriptionCount--
|
subscriptionCount--
|
||||||
//all subscriptions received
|
|
||||||
if subscriptionCount == 0 {
|
if subscriptionCount == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//select a random node for upload
|
||||||
log.Info("Stream subscriptions successfully requested")
|
node := sim.RandomUpNode()
|
||||||
if live {
|
item, ok := sim.NodeItem(node.ID, bucketKeyStore)
|
||||||
//now upload the chunks to the selected random single node
|
if !ok {
|
||||||
hashes, err := uploadFileToSingleNodeStore(node.ID(), chunkCount)
|
return fmt.Errorf("No localstore")
|
||||||
|
}
|
||||||
|
lstore := item.(*storage.LocalStore)
|
||||||
|
hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conf.hashes = append(conf.hashes, hashes...)
|
conf.hashes = append(conf.hashes, hashes...)
|
||||||
//finally map chunks to the closest addresses
|
|
||||||
log.Debug(fmt.Sprintf("Uploaded chunks for live syncing: %v", conf.hashes))
|
|
||||||
mapKeysToNodes(conf)
|
mapKeysToNodes(conf)
|
||||||
log.Info(fmt.Sprintf("Uploaded %d chunks to random single node", chunkCount))
|
|
||||||
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Action terminated")
|
var gDir string
|
||||||
|
var globalStore *mockdb.GlobalStore
|
||||||
return nil
|
if *useMockStore {
|
||||||
|
gDir, globalStore, err = createGlobalStore()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil")
|
||||||
}
|
}
|
||||||
|
defer os.RemoveAll(gDir)
|
||||||
//check defines what will be checked during the test
|
|
||||||
check := func(ctx context.Context, id discover.NodeID) (bool, error) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return false, ctx.Err()
|
|
||||||
case e := <-disconnectC:
|
|
||||||
log.Error(e.Error())
|
|
||||||
return false, fmt.Errorf("Disconnect event detected, network unhealthy")
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
log.Trace(fmt.Sprintf("Checking node: %s", id))
|
// File retrieval check is repeated until all uploaded files are retrieved from all nodes
|
||||||
//select the local store for the given node
|
// or until the timeout is reached.
|
||||||
//if there are more than one chunk, test only succeeds if all expected chunks are found
|
allSuccess := false
|
||||||
allSuccess := true
|
for !allSuccess {
|
||||||
|
for _, id := range nodeIDs {
|
||||||
//all the chunk indexes which are supposed to be found for this node
|
|
||||||
localChunks := conf.idToChunksMap[id]
|
|
||||||
//for each expected chunk, check if it is in the local store
|
//for each expected chunk, check if it is in the local store
|
||||||
|
localChunks := conf.idToChunksMap[id]
|
||||||
|
localSuccess := true
|
||||||
for _, ch := range localChunks {
|
for _, ch := range localChunks {
|
||||||
//get the real chunk by the index in the index array
|
//get the real chunk by the index in the index array
|
||||||
chunk := conf.hashes[ch]
|
chunk := conf.hashes[ch]
|
||||||
|
|
@ -428,59 +413,40 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
|
||||||
//check if the expected chunk is indeed in the localstore
|
//check if the expected chunk is indeed in the localstore
|
||||||
var err error
|
var err error
|
||||||
if *useMockStore {
|
if *useMockStore {
|
||||||
if globalStore == nil {
|
|
||||||
return false, fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil")
|
|
||||||
}
|
|
||||||
//use the globalStore if the mockStore should be used; in that case,
|
//use the globalStore if the mockStore should be used; in that case,
|
||||||
//the complete localStore stack is bypassed for getting the chunk
|
//the complete localStore stack is bypassed for getting the chunk
|
||||||
_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk)
|
_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk)
|
||||||
} else {
|
} else {
|
||||||
//use the actual localstore
|
//use the actual localstore
|
||||||
lstore := stores[id]
|
item, ok := sim.NodeItem(id, bucketKeyStore)
|
||||||
_, err = lstore.Get(context.TODO(), chunk)
|
if !ok {
|
||||||
|
return fmt.Errorf("Error accessing localstore")
|
||||||
|
}
|
||||||
|
lstore := item.(*storage.LocalStore)
|
||||||
|
_, err = lstore.Get(ctx, chunk)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
|
||||||
allSuccess = false
|
localSuccess = false
|
||||||
|
// Do not get crazy with logging the warn message
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
allSuccess = localSuccess
|
||||||
return allSuccess, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//for each tick, run the checks on all nodes
|
|
||||||
timingTicker := time.NewTicker(time.Second * 1)
|
|
||||||
defer timingTicker.Stop()
|
|
||||||
go func() {
|
|
||||||
for range timingTicker.C {
|
|
||||||
for i := 0; i < len(ids); i++ {
|
|
||||||
log.Trace(fmt.Sprintf("triggering step %d, id %s", i, ids[i]))
|
|
||||||
trigger <- ids[i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
if !allSuccess {
|
||||||
|
return fmt.Errorf("Not all chunks succeeded!")
|
||||||
log.Info("Starting simulation run...")
|
}
|
||||||
|
return nil
|
||||||
timeout := MaxTimeout * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
//run the simulation
|
|
||||||
result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{
|
|
||||||
Action: action,
|
|
||||||
Trigger: trigger,
|
|
||||||
Expect: &simulations.Expectation{
|
|
||||||
Nodes: ids,
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Simulation terminated")
|
log.Info("Simulation terminated")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -489,20 +455,9 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
|
||||||
//issues `RequestSubscriptionMsg` to peers, based on po, by iterating over
|
//issues `RequestSubscriptionMsg` to peers, based on po, by iterating over
|
||||||
//the kademlia's `EachBin` function.
|
//the kademlia's `EachBin` function.
|
||||||
//returns the number of subscriptions requested
|
//returns the number of subscriptions requested
|
||||||
func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) {
|
func startSyncing(r *Registry, conf *synctestConfig) (int, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if log.Lvl(*loglevel) == log.LvlDebug {
|
|
||||||
//PeerPot for this node
|
|
||||||
addr := common.Bytes2Hex(r.addr.OAddr)
|
|
||||||
pp := ppmap[addr]
|
|
||||||
//call Healthy RPC
|
|
||||||
h := r.delivery.overlay.Healthy(pp)
|
|
||||||
//print info
|
|
||||||
log.Debug(r.delivery.overlay.String())
|
|
||||||
log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full))
|
|
||||||
}
|
|
||||||
|
|
||||||
kad, ok := r.delivery.overlay.(*network.Kademlia)
|
kad, ok := r.delivery.overlay.(*network.Kademlia)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, fmt.Errorf("Not a Kademlia!")
|
return 0, fmt.Errorf("Not a Kademlia!")
|
||||||
|
|
@ -512,14 +467,10 @@ func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) {
|
||||||
//iterate over each bin and solicit needed subscription to bins
|
//iterate over each bin and solicit needed subscription to bins
|
||||||
kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool {
|
kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool {
|
||||||
//identify begin and start index of the bin(s) we want to subscribe to
|
//identify begin and start index of the bin(s) we want to subscribe to
|
||||||
log.Debug(fmt.Sprintf("Requesting subscription by: registry %s from peer %s for bin: %d", r.addr.ID(), conf.addrToIdMap[string(conn.Address())], po))
|
histRange := &Range{}
|
||||||
var histRange *Range
|
|
||||||
if history {
|
|
||||||
histRange = &Range{}
|
|
||||||
}
|
|
||||||
|
|
||||||
subCnt++
|
subCnt++
|
||||||
err = r.RequestSubscription(conf.addrToIdMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), live), histRange, Top)
|
err = r.RequestSubscription(conf.addrToIDMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), true), histRange, Top)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err))
|
log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err))
|
||||||
return false
|
return false
|
||||||
|
|
@ -552,7 +503,7 @@ func mapKeysToNodes(conf *synctestConfig) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if pl == 256 || pl == po {
|
if pl == 256 || pl == po {
|
||||||
log.Trace(fmt.Sprintf("appending %s", conf.addrToIdMap[string(a)]))
|
log.Trace(fmt.Sprintf("appending %s", conf.addrToIDMap[string(a)]))
|
||||||
nns = append(nns, indexmap[string(a)])
|
nns = append(nns, indexmap[string(a)])
|
||||||
nodemap[string(a)] = append(nodemap[string(a)], i)
|
nodemap[string(a)] = append(nodemap[string(a)], i)
|
||||||
}
|
}
|
||||||
|
|
@ -567,26 +518,24 @@ func mapKeysToNodes(conf *synctestConfig) {
|
||||||
}
|
}
|
||||||
for addr, chunks := range nodemap {
|
for addr, chunks := range nodemap {
|
||||||
//this selects which chunks are expected to be found with the given node
|
//this selects which chunks are expected to be found with the given node
|
||||||
conf.idToChunksMap[conf.addrToIdMap[addr]] = chunks
|
conf.idToChunksMap[conf.addrToIDMap[addr]] = chunks
|
||||||
}
|
}
|
||||||
log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap))
|
log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap))
|
||||||
conf.chunksToNodesMap = kmap
|
conf.chunksToNodesMap = kmap
|
||||||
}
|
}
|
||||||
|
|
||||||
//upload a file(chunks) to a single local node store
|
//upload a file(chunks) to a single local node store
|
||||||
func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int) ([]storage.Address, error) {
|
func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int, lstore *storage.LocalStore) ([]storage.Address, error) {
|
||||||
log.Debug(fmt.Sprintf("Uploading to node id: %s", id))
|
log.Debug(fmt.Sprintf("Uploading to node id: %s", id))
|
||||||
lstore := stores[id]
|
|
||||||
size := chunkSize
|
|
||||||
fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams())
|
||||||
|
size := chunkSize
|
||||||
var rootAddrs []storage.Address
|
var rootAddrs []storage.Address
|
||||||
for i := 0; i < chunkCount; i++ {
|
for i := 0; i < chunkCount; i++ {
|
||||||
ctx := context.TODO()
|
rk, wait, err := fileStore.Store(context.TODO(), io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
||||||
rk, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = wait(ctx)
|
err = wait(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -595,129 +544,3 @@ func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int) ([]storage.
|
||||||
|
|
||||||
return rootAddrs, nil
|
return rootAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//initialize a network from a snapshot
|
|
||||||
func initNetWithSnapshot(nodeCount int) (*simulations.Network, error) {
|
|
||||||
|
|
||||||
var a adapters.NodeAdapter
|
|
||||||
//add the streamer service to the node adapter
|
|
||||||
|
|
||||||
if *adapter == "exec" {
|
|
||||||
dirname, err := ioutil.TempDir(".", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a = adapters.NewExecAdapter(dirname)
|
|
||||||
} else if *adapter == "tcp" {
|
|
||||||
a = adapters.NewTCPAdapter(services)
|
|
||||||
} else if *adapter == "sim" {
|
|
||||||
a = adapters.NewSimAdapter(services)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Setting up Snapshot network")
|
|
||||||
|
|
||||||
net := simulations.NewNetwork(a, &simulations.NetworkConfig{
|
|
||||||
ID: "0",
|
|
||||||
DefaultService: "streamer",
|
|
||||||
})
|
|
||||||
|
|
||||||
f, err := os.Open(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
jsonbyte, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var snap simulations.Snapshot
|
|
||||||
err = json.Unmarshal(jsonbyte, &snap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//the snapshot probably has the property EnableMsgEvents not set
|
|
||||||
//just in case, set it to true!
|
|
||||||
//(we need this to wait for messages before uploading)
|
|
||||||
for _, n := range snap.Nodes {
|
|
||||||
n.Node.Config.EnableMsgEvents = true
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Waiting for p2p connections to be established...")
|
|
||||||
|
|
||||||
//now we can load the snapshot
|
|
||||||
err = net.Load(&snap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Info("Snapshot loaded")
|
|
||||||
return net, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//we want to wait for subscriptions to be established before uploading to test
|
|
||||||
//that live syncing is working correctly
|
|
||||||
func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) (doneC <-chan struct{}) {
|
|
||||||
events := make(chan *p2p.PeerEvent)
|
|
||||||
sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err.Error())
|
|
||||||
errc <- fmt.Errorf("error getting peer events for node %v: %s", id, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
log.Trace("watch subscription events: unsubscribe", "id", id)
|
|
||||||
sub.Unsubscribe()
|
|
||||||
close(c)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quitC:
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
select {
|
|
||||||
case errc <- ctx.Err():
|
|
||||||
case <-quitC:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case e := <-events:
|
|
||||||
//just catch SubscribeMsg
|
|
||||||
if e.Type == p2p.PeerEventTypeMsgRecv && e.Protocol == "stream" && e.MsgCode != nil && *e.MsgCode == 4 {
|
|
||||||
errc <- nil
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err):
|
|
||||||
case <-quitC:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
//create a local store for the given node
|
|
||||||
func createTestLocalStorageForId(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error) {
|
|
||||||
var datadir string
|
|
||||||
var err error
|
|
||||||
datadir, err = ioutil.TempDir("", fmt.Sprintf("syncer-test-%s", id.TerminalString()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datadirs[id] = datadir
|
|
||||||
var store storage.ChunkStore
|
|
||||||
params := storage.NewDefaultLocalStoreParams()
|
|
||||||
params.ChunkDbPath = datadir
|
|
||||||
params.BaseKey = addr.Over()
|
|
||||||
store, err = storage.NewTestLocalStoreForAddr(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return store, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,22 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
"github.com/ethereum/go-ethereum/swarm/network"
|
||||||
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing"
|
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/state"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
|
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dataChunkCount = 200
|
const dataChunkCount = 200
|
||||||
|
|
@ -46,95 +50,139 @@ func TestSyncerSimulation(t *testing.T) {
|
||||||
testSyncBetweenNodes(t, 16, 1, dataChunkCount, true, 1)
|
testSyncBetweenNodes(t, 16, 1, dataChunkCount, true, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMockStore(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error) {
|
func createMockStore(globalStore *mockdb.GlobalStore, id discover.NodeID, addr *network.BzzAddr) (lstore storage.ChunkStore, datadir string, err error) {
|
||||||
var err error
|
|
||||||
address := common.BytesToAddress(id.Bytes())
|
address := common.BytesToAddress(id.Bytes())
|
||||||
mockStore := globalStore.NewNodeStore(address)
|
mockStore := globalStore.NewNodeStore(address)
|
||||||
params := storage.NewDefaultLocalStoreParams()
|
params := storage.NewDefaultLocalStoreParams()
|
||||||
datadirs[id], err = ioutil.TempDir("", "localMockStore-"+id.TerminalString())
|
|
||||||
|
datadir, err = ioutil.TempDir("", "localMockStore-"+id.TerminalString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
params.Init(datadirs[id])
|
params.Init(datadir)
|
||||||
params.BaseKey = addr.Over()
|
params.BaseKey = addr.Over()
|
||||||
lstore, err := storage.NewLocalStore(params, mockStore)
|
lstore, err = storage.NewLocalStore(params, mockStore)
|
||||||
return lstore, nil
|
return lstore, datadir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck bool, po uint8) {
|
func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck bool, po uint8) {
|
||||||
defer setDefaultSkipCheck(defaultSkipCheck)
|
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||||
defaultSkipCheck = skipCheck
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||||
//data directories for each node and store
|
var store storage.ChunkStore
|
||||||
datadirs = make(map[discover.NodeID]string)
|
var globalStore *mockdb.GlobalStore
|
||||||
if *useMockStore {
|
var gDir, datadir string
|
||||||
createStoreFunc = createMockStore
|
|
||||||
createGlobalStore()
|
|
||||||
} else {
|
|
||||||
createStoreFunc = createTestLocalStorageFromSim
|
|
||||||
}
|
|
||||||
defer datadirsCleanup()
|
|
||||||
|
|
||||||
registries = make(map[discover.NodeID]*TestRegistry)
|
id := ctx.Config.ID
|
||||||
toAddr = func(id discover.NodeID) *network.BzzAddr {
|
|
||||||
addr := network.NewAddrFromNodeID(id)
|
addr := network.NewAddrFromNodeID(id)
|
||||||
//hack to put addresses in same space
|
//hack to put addresses in same space
|
||||||
addr.OAddr[0] = byte(0)
|
addr.OAddr[0] = byte(0)
|
||||||
return addr
|
|
||||||
|
if *useMockStore {
|
||||||
|
gDir, globalStore, err = createGlobalStore()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil")
|
||||||
}
|
}
|
||||||
conf := &streamTesting.RunConfig{
|
store, datadir, err = createMockStore(globalStore, id, addr)
|
||||||
Adapter: *adapter,
|
} else {
|
||||||
NodeCount: nodes,
|
store, datadir, err = createTestLocalStorageForID(id, addr)
|
||||||
ConnLevel: conns,
|
|
||||||
ToAddr: toAddr,
|
|
||||||
Services: services,
|
|
||||||
EnableMsgEvents: false,
|
|
||||||
}
|
}
|
||||||
// HACK: these are global variables in the test so that they are available for
|
if err != nil {
|
||||||
// the service constructor function
|
return nil, nil, err
|
||||||
// TODO: will this work with exec/docker adapter?
|
}
|
||||||
// localstore of nodes made available for action and check calls
|
bucket.Store(bucketKeyStore, store)
|
||||||
stores = make(map[discover.NodeID]storage.ChunkStore)
|
cleanup = func() {
|
||||||
deliveries = make(map[discover.NodeID]*Delivery)
|
store.Close()
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
if *useMockStore {
|
||||||
|
err := globalStore.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error closing global store! %v", "err", err)
|
||||||
|
}
|
||||||
|
os.RemoveAll(gDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStore := store.(*storage.LocalStore)
|
||||||
|
db := storage.NewDBAPI(localStore)
|
||||||
|
bucket.Store(bucketKeyDB, db)
|
||||||
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
|
delivery := NewDelivery(kad, db)
|
||||||
|
bucket.Store(bucketKeyDelivery, delivery)
|
||||||
|
|
||||||
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
|
||||||
|
SkipCheck: skipCheck,
|
||||||
|
})
|
||||||
|
|
||||||
|
fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams())
|
||||||
|
bucket.Store(bucketKeyFileStore, fileStore)
|
||||||
|
|
||||||
|
return r, cleanup, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
// create context for simulation run
|
// create context for simulation run
|
||||||
timeout := 30 * time.Second
|
timeout := 30 * time.Second
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
// defer cancel should come before defer simulation teardown
|
// defer cancel should come before defer simulation teardown
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// create simulation network with the config
|
_, err := sim.AddNodesAndConnectChain(nodes)
|
||||||
sim, teardown, err := streamTesting.NewSimulation(conf)
|
if err != nil {
|
||||||
var rpcSubscriptionsWg sync.WaitGroup
|
t.Fatal(err)
|
||||||
defer func() {
|
}
|
||||||
rpcSubscriptionsWg.Wait()
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||||
teardown()
|
nodeIDs := sim.UpNodeIDs()
|
||||||
|
|
||||||
|
nodeIndex := make(map[discover.NodeID]int)
|
||||||
|
for i, id := range nodeIDs {
|
||||||
|
nodeIndex[id] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnections := sim.PeerEvents(
|
||||||
|
context.Background(),
|
||||||
|
sim.NodeIDs(),
|
||||||
|
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for d := range disconnections {
|
||||||
|
if d.Error != nil {
|
||||||
|
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer)
|
||||||
|
t.Fatal(d.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// each node Subscribes to each other's swarmChunkServerStreamName
|
||||||
|
for j := 0; j < nodes-1; j++ {
|
||||||
|
id := nodeIDs[j]
|
||||||
|
client, err := sim.Net.GetNode(id).Client()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sid := nodeIDs[j+1]
|
||||||
|
client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream("SYNC", FormatSyncBinKey(1), false), NewRange(0, 0), Top)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if j > 0 || nodes == 2 {
|
||||||
|
item, ok := sim.NodeItem(nodeIDs[j], bucketKeyFileStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No filestore")
|
||||||
|
}
|
||||||
|
fileStore := item.(*storage.FileStore)
|
||||||
|
size := chunkCount * chunkSize
|
||||||
|
_, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
wait(ctx)
|
||||||
nodeIndex := make(map[discover.NodeID]int)
|
|
||||||
for i, id := range sim.IDs {
|
|
||||||
nodeIndex[id] = i
|
|
||||||
if !*useMockStore {
|
|
||||||
stores[id] = sim.Stores[i]
|
|
||||||
sim.Stores[i] = stores[id]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// peerCount function gives the number of peer connections for a nodeID
|
// here we distribute chunks of a random file into stores 1...nodes
|
||||||
// this is needed for the service run function to wait until
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
|
||||||
// each protocol instance runs and the streamer peers are available
|
return err
|
||||||
peerCount = func(id discover.NodeID) int {
|
|
||||||
if sim.IDs[0] == id || sim.IDs[nodes-1] == id {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
waitPeerErrC = make(chan error)
|
|
||||||
|
|
||||||
// create DBAPI-s for all nodes
|
|
||||||
dbs := make([]*storage.DBAPI, nodes)
|
|
||||||
for i := 0; i < nodes; i++ {
|
|
||||||
dbs[i] = storage.NewDBAPI(sim.Stores[i].(*storage.LocalStore))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect hashes in po 1 bin for each node
|
// collect hashes in po 1 bin for each node
|
||||||
|
|
@ -145,93 +193,31 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
||||||
if i < nodes-1 {
|
if i < nodes-1 {
|
||||||
hashCounts[i] = hashCounts[i+1]
|
hashCounts[i] = hashCounts[i+1]
|
||||||
}
|
}
|
||||||
dbs[i].Iterator(0, math.MaxUint64, po, func(addr storage.Address, index uint64) bool {
|
item, ok := sim.NodeItem(nodeIDs[i], bucketKeyDB)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No DB")
|
||||||
|
}
|
||||||
|
db := item.(*storage.DBAPI)
|
||||||
|
db.Iterator(0, math.MaxUint64, po, func(addr storage.Address, index uint64) bool {
|
||||||
hashes[i] = append(hashes[i], addr)
|
hashes[i] = append(hashes[i], addr)
|
||||||
totalHashes++
|
totalHashes++
|
||||||
hashCounts[i]++
|
hashCounts[i]++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// errc is error channel for simulation
|
|
||||||
errc := make(chan error, 1)
|
|
||||||
quitC := make(chan struct{})
|
|
||||||
defer close(quitC)
|
|
||||||
|
|
||||||
// action is subscribe
|
|
||||||
action := func(ctx context.Context) error {
|
|
||||||
// need to wait till an aynchronous process registers the peers in streamer.peers
|
|
||||||
// that is used by Subscribe
|
|
||||||
// the global peerCount function tells how many connections each node has
|
|
||||||
// TODO: this is to be reimplemented with peerEvent watcher without global var
|
|
||||||
i := 0
|
|
||||||
for err := range waitPeerErrC {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for peers: %s", err)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i == nodes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// each node Subscribes to each other's swarmChunkServerStreamName
|
|
||||||
for j := 0; j < nodes-1; j++ {
|
|
||||||
id := sim.IDs[j]
|
|
||||||
sim.Stores[j] = stores[id]
|
|
||||||
err := sim.CallClient(id, func(client *rpc.Client) error {
|
|
||||||
// report disconnect events to the error channel cos peers should not disconnect
|
|
||||||
doneC, err := streamTesting.WatchDisconnections(id, client, errc, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpcSubscriptionsWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
<-doneC
|
|
||||||
rpcSubscriptionsWg.Done()
|
|
||||||
}()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
// start syncing, i.e., subscribe to upstream peers po 1 bin
|
|
||||||
sid := sim.IDs[j+1]
|
|
||||||
return client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream("SYNC", FormatSyncBinKey(1), false), NewRange(0, 0), Top)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// here we distribute chunks of a random file into stores 1...nodes
|
|
||||||
rrFileStore := storage.NewFileStore(newRoundRobinStore(sim.Stores[1:]...), storage.NewFileStoreParams())
|
|
||||||
size := chunkCount * chunkSize
|
|
||||||
_, wait, err := rrFileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
// need to wait cos we then immediately collect the relevant bin content
|
|
||||||
wait(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// this makes sure check is not called before the previous call finishes
|
|
||||||
check := func(ctx context.Context, id discover.NodeID) (bool, error) {
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return false, err
|
|
||||||
case <-ctx.Done():
|
|
||||||
return false, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
i := nodeIndex[id]
|
|
||||||
var total, found int
|
var total, found int
|
||||||
|
for _, node := range nodeIDs {
|
||||||
|
i := nodeIndex[node]
|
||||||
|
|
||||||
for j := i; j < nodes; j++ {
|
for j := i; j < nodes; j++ {
|
||||||
total += len(hashes[j])
|
total += len(hashes[j])
|
||||||
for _, key := range hashes[j] {
|
for _, key := range hashes[j] {
|
||||||
chunk, err := dbs[i].Get(ctx, key)
|
item, ok := sim.NodeItem(nodeIDs[j], bucketKeyDB)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("No DB")
|
||||||
|
}
|
||||||
|
db := item.(*storage.DBAPI)
|
||||||
|
chunk, err := db.Get(ctx, key)
|
||||||
if err == storage.ErrFetching {
|
if err == storage.ErrFetching {
|
||||||
<-chunk.ReqC
|
<-chunk.ReqC
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|
@ -242,26 +228,15 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
||||||
found++
|
found++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug("sync check", "node", id, "index", i, "bin", po, "found", found, "total", total)
|
log.Debug("sync check", "node", node, "index", i, "bin", po, "found", found, "total", total)
|
||||||
return total == found, nil
|
|
||||||
}
|
}
|
||||||
|
if total == found && total > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Total not equallying found: total is %d", total)
|
||||||
|
})
|
||||||
|
|
||||||
conf.Step = &simulations.Step{
|
|
||||||
Action: action,
|
|
||||||
Trigger: streamTesting.Trigger(500*time.Millisecond, quitC, sim.IDs[0:nodes-1]...),
|
|
||||||
Expect: &simulations.Expectation{
|
|
||||||
Nodes: sim.IDs[0:1],
|
|
||||||
Check: check,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
startedAt := time.Now()
|
|
||||||
result, err := sim.Run(ctx, conf)
|
|
||||||
finishedAt := time.Now()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Setting up simulation failed: %v", err)
|
|
||||||
}
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
t.Fatalf("Simulation failed: %s", result.Error)
|
t.Fatal(result.Error)
|
||||||
}
|
}
|
||||||
streamTesting.CheckResult(t, result, startedAt, finishedAt)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
// 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 testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/network"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Simulation struct {
|
|
||||||
Net *simulations.Network
|
|
||||||
Stores []storage.ChunkStore
|
|
||||||
Addrs []network.Addr
|
|
||||||
IDs []discover.NodeID
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetStores(addrs ...network.Addr) ([]storage.ChunkStore, func(), error) {
|
|
||||||
var datadirs []string
|
|
||||||
stores := make([]storage.ChunkStore, len(addrs))
|
|
||||||
var err error
|
|
||||||
for i, addr := range addrs {
|
|
||||||
var datadir string
|
|
||||||
datadir, err = ioutil.TempDir("", "streamer")
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var store storage.ChunkStore
|
|
||||||
params := storage.NewDefaultLocalStoreParams()
|
|
||||||
params.Init(datadir)
|
|
||||||
params.BaseKey = addr.Over()
|
|
||||||
store, err = storage.NewTestLocalStoreForAddr(params)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
datadirs = append(datadirs, datadir)
|
|
||||||
stores[i] = store
|
|
||||||
}
|
|
||||||
teardown := func() {
|
|
||||||
for i, datadir := range datadirs {
|
|
||||||
stores[i].Close()
|
|
||||||
os.RemoveAll(datadir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stores, teardown, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "exec":
|
|
||||||
baseDir, err0 := ioutil.TempDir("", "swarm-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, exec, docker")
|
|
||||||
}
|
|
||||||
return adapter, teardown, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckResult(t *testing.T, result *simulations.StepResult, startedAt, finishedAt time.Time) {
|
|
||||||
t.Logf("Simulation passed in %s", result.FinishedAt.Sub(result.StartedAt))
|
|
||||||
if len(result.Passes) > 1 {
|
|
||||||
var min, max time.Duration
|
|
||||||
var sum int
|
|
||||||
for _, pass := range result.Passes {
|
|
||||||
duration := pass.Sub(result.StartedAt)
|
|
||||||
if sum == 0 || duration < min {
|
|
||||||
min = duration
|
|
||||||
}
|
|
||||||
if duration > max {
|
|
||||||
max = duration
|
|
||||||
}
|
|
||||||
sum += int(duration.Nanoseconds())
|
|
||||||
}
|
|
||||||
t.Logf("Min: %s, Max: %s, Average: %s", min, max, time.Duration(sum/len(result.Passes))*time.Nanosecond)
|
|
||||||
}
|
|
||||||
t.Logf("Setup: %s, Shutdown: %s", result.StartedAt.Sub(startedAt), finishedAt.Sub(result.FinishedAt))
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunConfig struct {
|
|
||||||
Adapter string
|
|
||||||
Step *simulations.Step
|
|
||||||
NodeCount int
|
|
||||||
ConnLevel int
|
|
||||||
ToAddr func(discover.NodeID) *network.BzzAddr
|
|
||||||
Services adapters.Services
|
|
||||||
DefaultService string
|
|
||||||
EnableMsgEvents bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSimulation(conf *RunConfig) (*Simulation, func(), error) {
|
|
||||||
// create network
|
|
||||||
nodes := conf.NodeCount
|
|
||||||
adapter, adapterTeardown, err := NewAdapter(conf.Adapter, conf.Services)
|
|
||||||
if err != nil {
|
|
||||||
return nil, adapterTeardown, err
|
|
||||||
}
|
|
||||||
defaultService := "streamer"
|
|
||||||
if conf.DefaultService != "" {
|
|
||||||
defaultService = conf.DefaultService
|
|
||||||
}
|
|
||||||
net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
|
|
||||||
ID: "0",
|
|
||||||
DefaultService: defaultService,
|
|
||||||
})
|
|
||||||
teardown := func() {
|
|
||||||
adapterTeardown()
|
|
||||||
net.Shutdown()
|
|
||||||
}
|
|
||||||
ids := make([]discover.NodeID, nodes)
|
|
||||||
addrs := make([]network.Addr, nodes)
|
|
||||||
// start nodes
|
|
||||||
for i := 0; i < nodes; i++ {
|
|
||||||
nodeconf := adapters.RandomNodeConfig()
|
|
||||||
nodeconf.EnableMsgEvents = conf.EnableMsgEvents
|
|
||||||
node, err := net.NewNodeWithConfig(nodeconf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, teardown, fmt.Errorf("error creating node: %s", err)
|
|
||||||
}
|
|
||||||
ids[i] = node.ID()
|
|
||||||
addrs[i] = conf.ToAddr(ids[i])
|
|
||||||
}
|
|
||||||
// set nodes number of Stores available
|
|
||||||
stores, storeTeardown, err := SetStores(addrs...)
|
|
||||||
teardown = func() {
|
|
||||||
net.Shutdown()
|
|
||||||
adapterTeardown()
|
|
||||||
storeTeardown()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, teardown, err
|
|
||||||
}
|
|
||||||
s := &Simulation{
|
|
||||||
Net: net,
|
|
||||||
Stores: stores,
|
|
||||||
IDs: ids,
|
|
||||||
Addrs: addrs,
|
|
||||||
}
|
|
||||||
return s, teardown, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Simulation) Run(ctx context.Context, conf *RunConfig) (*simulations.StepResult, error) {
|
|
||||||
// bring up nodes, launch the servive
|
|
||||||
nodes := conf.NodeCount
|
|
||||||
conns := conf.ConnLevel
|
|
||||||
for i := 0; i < nodes; i++ {
|
|
||||||
if err := s.Net.Start(s.IDs[i]); err != nil {
|
|
||||||
return nil, fmt.Errorf("error starting node %s: %s", s.IDs[i].TerminalString(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// run a simulation which connects the 10 nodes in a chain
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
for i := range s.IDs {
|
|
||||||
// collect the overlay addresses, to
|
|
||||||
for j := 0; j < conns; j++ {
|
|
||||||
var k int
|
|
||||||
if j == 0 {
|
|
||||||
k = i - 1
|
|
||||||
} else {
|
|
||||||
k = rand.Intn(len(s.IDs))
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i, k int) {
|
|
||||||
defer wg.Done()
|
|
||||||
s.Net.Connect(s.IDs[i], s.IDs[k])
|
|
||||||
}(i, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
log.Info(fmt.Sprintf("simulation with %v nodes", len(s.Addrs)))
|
|
||||||
|
|
||||||
// create an only locally retrieving FileStore for the pivot node to test
|
|
||||||
// if retriee requests have arrived
|
|
||||||
result := simulations.NewSimulation(s.Net).Run(ctx, conf.Step)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchDisconnections subscribes to admin peerEvents and sends peer event drop
|
|
||||||
// errors to the errc channel. Channel quitC signals the termination of the event loop.
|
|
||||||
// Returned doneC will be closed after the rpc subscription is unsubscribed,
|
|
||||||
// signaling that simulations network is safe to shutdown.
|
|
||||||
func WatchDisconnections(id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) (doneC <-chan struct{}, err error) {
|
|
||||||
events := make(chan *p2p.PeerEvent)
|
|
||||||
sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting peer events for node %v: %s", id, err)
|
|
||||||
}
|
|
||||||
c := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
log.Trace("watch disconnections: unsubscribe", "id", id)
|
|
||||||
sub.Unsubscribe()
|
|
||||||
close(c)
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quitC:
|
|
||||||
return
|
|
||||||
case e := <-events:
|
|
||||||
if e.Type == p2p.PeerEventTypeDrop {
|
|
||||||
select {
|
|
||||||
case errc <- fmt.Errorf("peerEvent for node %v: %v", id, e):
|
|
||||||
case <-quitC:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err):
|
|
||||||
case <-quitC:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Trigger(d time.Duration, quitC chan struct{}, ids ...discover.NodeID) chan discover.NodeID {
|
|
||||||
trigger := make(chan discover.NodeID)
|
|
||||||
go func() {
|
|
||||||
defer close(trigger)
|
|
||||||
ticker := time.NewTicker(d)
|
|
||||||
defer ticker.Stop()
|
|
||||||
// we are only testing the pivot node (net.Nodes[0])
|
|
||||||
for range ticker.C {
|
|
||||||
for _, id := range ids {
|
|
||||||
select {
|
|
||||||
case trigger <- id:
|
|
||||||
case <-quitC:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return trigger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sim *Simulation) CallClient(id discover.NodeID, f func(*rpc.Client) error) error {
|
|
||||||
node := sim.Net.GetNode(id)
|
|
||||||
if node == nil {
|
|
||||||
return fmt.Errorf("unknown node: %s", id)
|
|
||||||
}
|
|
||||||
client, err := node.Client()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error getting node client: %s", err)
|
|
||||||
}
|
|
||||||
return f(client)
|
|
||||||
}
|
|
||||||
64
swarm/version/version.go
Normal file
64
swarm/version/version.go
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
// 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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VersionMajor = 0 // Major version component of the current release
|
||||||
|
VersionMinor = 3 // Minor version component of the current release
|
||||||
|
VersionPatch = 2 // Patch version component of the current release
|
||||||
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version holds the textual version string.
|
||||||
|
var Version = func() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// VersionWithMeta holds the textual version string including the metadata.
|
||||||
|
var VersionWithMeta = func() string {
|
||||||
|
v := Version
|
||||||
|
if VersionMeta != "" {
|
||||||
|
v += "-" + VersionMeta
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
// ArchiveVersion holds the textual version string used for Swarm archives.
|
||||||
|
// e.g. "0.3.0-dea1ce05" for stable releases, or
|
||||||
|
// "0.3.1-unstable-21c059b6" for unstable releases
|
||||||
|
func ArchiveVersion(gitCommit string) string {
|
||||||
|
vsn := Version
|
||||||
|
if VersionMeta != "stable" {
|
||||||
|
vsn += "-" + VersionMeta
|
||||||
|
}
|
||||||
|
if len(gitCommit) >= 8 {
|
||||||
|
vsn += "-" + gitCommit[:8]
|
||||||
|
}
|
||||||
|
return vsn
|
||||||
|
}
|
||||||
|
|
||||||
|
func VersionWithCommit(gitCommit string) string {
|
||||||
|
vsn := Version
|
||||||
|
if len(gitCommit) >= 8 {
|
||||||
|
vsn += "-" + gitCommit[:8]
|
||||||
|
}
|
||||||
|
return vsn
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,16 @@ var Forks = map[string]*params.ChainConfig{
|
||||||
DAOForkBlock: big.NewInt(0),
|
DAOForkBlock: big.NewInt(0),
|
||||||
ByzantiumBlock: big.NewInt(0),
|
ByzantiumBlock: big.NewInt(0),
|
||||||
},
|
},
|
||||||
|
"Constantinople": {
|
||||||
|
ChainID: big.NewInt(1),
|
||||||
|
HomesteadBlock: big.NewInt(0),
|
||||||
|
EIP150Block: big.NewInt(0),
|
||||||
|
EIP155Block: big.NewInt(0),
|
||||||
|
EIP158Block: big.NewInt(0),
|
||||||
|
DAOForkBlock: big.NewInt(0),
|
||||||
|
ByzantiumBlock: big.NewInt(0),
|
||||||
|
ConstantinopleBlock: big.NewInt(0),
|
||||||
|
},
|
||||||
"FrontierToHomesteadAt5": {
|
"FrontierToHomesteadAt5": {
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
HomesteadBlock: big.NewInt(5),
|
HomesteadBlock: big.NewInt(5),
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue