Merge branch 'master' into master

This commit is contained in:
lhendre 2018-09-01 00:25:26 -04:00 committed by GitHub
commit 6f39df9e3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
399 changed files with 26885 additions and 14761 deletions

View file

@ -30,8 +30,6 @@ matrix:
go: 1.10.x go: 1.10.x
script: script:
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703 - unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
- brew update
- brew cask install osxfuse
- go run build/ci.go install - go run build/ci.go install
- go run build/ci.go test -coverage $TEST_PACKAGES - go run build/ci.go test -coverage $TEST_PACKAGES

View file

@ -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`). |
@ -69,7 +69,7 @@ This command will:
* Start up Geth's built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console), * Start up Geth's built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console),
(via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API) (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API)
as well as Geth's own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs). as well as Geth's own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs).
This too is optional and if you leave it out you can always attach to an already running Geth instance This tool is optional and if you leave it out you can always attach to an already running Geth instance
with `geth attach`. with `geth attach`.
### Full node on the Ethereum test network ### Full node on the Ethereum test network

View file

@ -1 +0,0 @@
1.8.13

View file

@ -65,9 +65,9 @@ type SimulatedBackend struct {
// NewSimulatedBackend creates a new binding backend using a simulated blockchain // NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes. // for testing purposes.
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend { func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
database := ethdb.NewMemDatabase() database := ethdb.NewMemDatabase()
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc} genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
genesis.MustCommit(database) genesis.MustCommit(database)
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}) blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})

View file

@ -207,7 +207,7 @@ func bindTypeGo(kind abi.Type) string {
// The inner function of bindTypeGo, this finds the inner type of stringKind. // The inner function of bindTypeGo, this finds the inner type of stringKind.
// (Or just the type itself if it is not an array or slice) // (Or just the type itself if it is not an array or slice)
// The length of the matched part is returned, with the the translated type. // The length of the matched part is returned, with the translated type.
func bindUnnestedTypeGo(stringKind string) (int, string) { func bindUnnestedTypeGo(stringKind string) (int, string) {
switch { switch {
@ -255,7 +255,7 @@ func bindTypeJava(kind abi.Type) string {
// The inner function of bindTypeJava, this finds the inner type of stringKind. // The inner function of bindTypeJava, this finds the inner type of stringKind.
// (Or just the type itself if it is not an array or slice) // (Or just the type itself if it is not an array or slice)
// The length of the matched part is returned, with the the translated type. // The length of the matched part is returned, with the translated type.
func bindUnnestedTypeJava(stringKind string) (int, string) { func bindUnnestedTypeJava(stringKind string) (int, string) {
switch { switch {

View file

@ -229,7 +229,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy an interaction tester contract and call a transaction on it // Deploy an interaction tester contract and call a transaction on it
_, _, interactor, err := DeployInteractor(auth, sim, "Deploy string") _, _, interactor, err := DeployInteractor(auth, sim, "Deploy string")
@ -270,7 +270,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a tuple tester contract and execute a structured call on it // Deploy a tuple tester contract and execute a structured call on it
_, _, getter, err := DeployGetter(auth, sim) _, _, getter, err := DeployGetter(auth, sim)
@ -302,7 +302,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a tuple tester contract and execute a structured call on it // Deploy a tuple tester contract and execute a structured call on it
_, _, tupler, err := DeployTupler(auth, sim) _, _, tupler, err := DeployTupler(auth, sim)
@ -344,7 +344,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a slice tester contract and execute a n array call on it // Deploy a slice tester contract and execute a n array call on it
_, _, slicer, err := DeploySlicer(auth, sim) _, _, slicer, err := DeploySlicer(auth, sim)
@ -378,7 +378,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a default method invoker contract and execute its default method // Deploy a default method invoker contract and execute its default method
_, _, defaulter, err := DeployDefaulter(auth, sim) _, _, defaulter, err := DeployDefaulter(auth, sim)
@ -411,7 +411,7 @@ var bindTests = []struct {
`[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`, `[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`,
` `
// Create a simulator and wrap a non-deployed contract // Create a simulator and wrap a non-deployed contract
sim := backends.NewSimulatedBackend(nil) sim := backends.NewSimulatedBackend(nil, uint64(10000000000))
nonexistent, err := NewNonExistent(common.Address{}, sim) nonexistent, err := NewNonExistent(common.Address{}, sim)
if err != nil { if err != nil {
@ -447,7 +447,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a funky gas pattern contract // Deploy a funky gas pattern contract
_, _, limiter, err := DeployFunkyGasPattern(auth, sim) _, _, limiter, err := DeployFunkyGasPattern(auth, sim)
@ -482,7 +482,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a sender tester contract and execute a structured call on it // Deploy a sender tester contract and execute a structured call on it
_, _, callfrom, err := DeployCallFrom(auth, sim) _, _, callfrom, err := DeployCallFrom(auth, sim)
@ -542,7 +542,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a underscorer tester contract and execute a structured call on it // Deploy a underscorer tester contract and execute a structured call on it
_, _, underscorer, err := DeployUnderscorer(auth, sim) _, _, underscorer, err := DeployUnderscorer(auth, sim)
@ -612,7 +612,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy an eventer contract // Deploy an eventer contract
_, _, eventer, err := DeployEventer(auth, sim) _, _, eventer, err := DeployEventer(auth, sim)
@ -761,7 +761,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key) auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
//deploy the test contract //deploy the test contract
_, _, testContract, err := DeployDeeplyNestedArray(auth, sim) _, _, testContract, err := DeployDeeplyNestedArray(auth, sim)
@ -820,7 +820,7 @@ func TestBindings(t *testing.T) {
t.Skip("go sdk not found for testing") t.Skip("go sdk not found for testing")
} }
// Skip the test if the go-ethereum sources are symlinked (https://github.com/golang/go/issues/14845) // Skip the test if the go-ethereum sources are symlinked (https://github.com/golang/go/issues/14845)
linkTestCode := fmt.Sprintf("package linktest\nfunc CheckSymlinks(){\nfmt.Println(backends.NewSimulatedBackend(nil))\n}") linkTestCode := fmt.Sprintf("package linktest\nfunc CheckSymlinks(){\nfmt.Println(backends.NewSimulatedBackend(nil,uint64(10000000000)))\n}")
linkTestDeps, err := imports.Process(os.TempDir(), []byte(linkTestCode), nil) linkTestDeps, err := imports.Process(os.TempDir(), []byte(linkTestCode), nil)
if err != nil { if err != nil {
t.Fatalf("failed check for goimports symlink bug: %v", err) t.Fatalf("failed check for goimports symlink bug: %v", err)

View file

@ -53,9 +53,11 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) { func TestWaitDeployed(t *testing.T) {
for name, test := range waitDeployedTests { for name, test := range waitDeployedTests {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{ backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)}, crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
}) }, 10000000,
)
// Create the transaction. // Create the transaction.
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code)) tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code))

View file

@ -106,7 +106,7 @@ type Wallet interface {
// or optionally with the aid of any location metadata from the embedded URL field. // or optionally with the aid of any location metadata from the embedded URL field.
// //
// If the wallet requires additional authentication to sign the request (e.g. // If the wallet requires additional authentication to sign the request (e.g.
// a password to decrypt the account, or a PIN code o verify the transaction), // a password to decrypt the account, or a PIN code to verify the transaction),
// an AuthNeededError instance will be returned, containing infos for the user // an AuthNeededError instance will be returned, containing infos for the user
// about which fields or actions are needed. The user may retry by providing // about which fields or actions are needed. The user may retry by providing
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock

View file

@ -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

View file

@ -28,18 +28,18 @@ package keystore
import ( import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
crand "crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/randentropy"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
@ -93,7 +93,7 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string)
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory // StoreKey generates a key, encrypts with 'auth' and stores in the given directory
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) { func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth) _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, rand.Reader, auth)
return a.Address, err return a.Address, err
} }
@ -116,7 +116,11 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
// blob that can be decrypted later on. // blob that can be decrypted later on.
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
authArray := []byte(auth) authArray := []byte(auth)
salt := randentropy.GetEntropyCSPRNG(32)
salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen) derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
if err != nil { if err != nil {
return nil, err return nil, err
@ -124,7 +128,10 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
encryptKey := derivedKey[:16] encryptKey := derivedKey[:16]
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16 iv := make([]byte, aes.BlockSize) // 16
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv) cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -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,52 +79,84 @@ 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.
// Note: yakkety is unsupported because it was officially deprecated on lanchpad. // Note: yakkety is unsupported because it was officially deprecated on lanchpad.
// Note: zesty is unsupported because it was officially deprecated on lanchpad. // Note: zesty is unsupported because it was officially deprecated on lanchpad.
debDistros = []string{"trusty", "xenial", "artful", "bionic"} // Note: artful is unsupported because it was officially deprecated on lanchpad.
debDistros = []string{"trusty", "xenial", "bionic", "cosmic"}
) )
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
@ -350,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")
@ -371,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 {
@ -382,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")
@ -400,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 {
@ -461,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`)
@ -485,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
@ -503,6 +532,7 @@ func doDebianSource(cmdline []string) {
} }
} }
} }
}
func makeWorkdir(wdflag string) string { func makeWorkdir(wdflag string) string {
var err error var err error
@ -524,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.
@ -538,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,
} }
} }
@ -560,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.
@ -589,9 +639,9 @@ 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()
} }
// ExeConflicts returns the content of the Conflicts field // ExeConflicts returns the content of the Conflicts field
@ -606,7 +656,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 ""
} }
@ -623,24 +673,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 (
@ -690,11 +739,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],
@ -746,7 +795,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 {
@ -831,7 +880,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"
} }
@ -866,7 +915,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)
} }
@ -922,7 +971,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
} }
@ -952,7 +1001,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)))
@ -1018,23 +1067,14 @@ func doPurge(cmdline []string) {
} }
for i := 0; i < len(blobs); i++ { for i := 0; i < len(blobs); i++ {
for j := i + 1; j < len(blobs); j++ { for j := i + 1; j < len(blobs); j++ {
iTime, err := time.Parse(time.RFC1123, blobs[i].Properties.LastModified) if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) {
if err != nil {
log.Fatal(err)
}
jTime, err := time.Parse(time.RFC1123, blobs[j].Properties.LastModified)
if err != nil {
log.Fatal(err)
}
if iTime.After(jTime) {
blobs[i], blobs[j] = blobs[j], blobs[i] blobs[i], blobs[j] = blobs[j], blobs[i]
} }
} }
} }
// Filter out all archives more recent that the given threshold // Filter out all archives more recent that the given threshold
for i, blob := range blobs { for i, blob := range blobs {
timestamp, _ := time.Parse(time.RFC1123, blob.Properties.LastModified) if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour {
if time.Since(timestamp) < time.Duration(*limit)*24*time.Hour {
blobs = blobs[:i] blobs = blobs[:i]
break break
} }

View file

@ -1,6 +1,15 @@
#!/bin/sh #!/bin/sh
function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; } # Cleaning the Go cache only makes sense if we actually have Go installed... or
# if Go is actually callable. This does not hold true during deb packaging, so
# we need an explicit check to avoid build failures.
if ! command -v go > /dev/null; then
exit
fi
version_gt() {
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
}
golang_version=$(go version |cut -d' ' -f3 |sed 's/go//') golang_version=$(go version |cut -d' ' -f3 |sed 's/go//')

View file

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

View 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}}

View file

@ -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

View file

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

View file

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

View file

@ -11,8 +11,8 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
Package: {{.Name}} Package: {{.Name}}
Architecture: any Architecture: any
Depends: ${misc:Depends}, {{.ExeList}} Depends: ${misc:Depends}, {{.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 .}}

View 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/>.

View file

@ -0,0 +1 @@
AUTHORS

View file

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

View file

@ -0,0 +1,13 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
override_dh_auto_build:
build/env.sh /usr/lib/go-1.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 $@

View file

@ -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)
} }

View file

@ -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(),

View file

@ -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)

View file

@ -48,7 +48,6 @@ var (
ArgsUsage: "<genesisPath>", ArgsUsage: "<genesisPath>",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.LightModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
@ -66,7 +65,7 @@ It expects the genesis file as argument.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.CacheFlag, utils.CacheFlag,
utils.LightModeFlag, utils.SyncModeFlag,
utils.GCModeFlag, utils.GCModeFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheGCFlag, utils.CacheGCFlag,
@ -87,14 +86,15 @@ processing will proceed even if an individual RLP-file import failure occurs.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.CacheFlag, utils.CacheFlag,
utils.LightModeFlag, utils.SyncModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
Requires a first argument of the file to write to. Requires a first argument of the file to write to.
Optional second and third arguments control the first and Optional second and third arguments control the first and
last block to write. In this mode, the file will be appended last block to write. In this mode, the file will be appended
if already existing.`, if already existing. If the file ends with .gz, the output will
be gzipped.`,
} }
importPreimagesCommand = cli.Command{ importPreimagesCommand = cli.Command{
Action: utils.MigrateFlags(importPreimages), Action: utils.MigrateFlags(importPreimages),
@ -104,7 +104,7 @@ if already existing.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.CacheFlag, utils.CacheFlag,
utils.LightModeFlag, utils.SyncModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
@ -118,7 +118,7 @@ if already existing.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.CacheFlag, utils.CacheFlag,
utils.LightModeFlag, utils.SyncModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
@ -148,7 +148,6 @@ The first argument must be the directory containing the blockchain to download f
ArgsUsage: " ", ArgsUsage: " ",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.LightModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
@ -162,7 +161,7 @@ Remove blockchain and state databases`,
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DataDirFlag, utils.DataDirFlag,
utils.CacheFlag, utils.CacheFlag,
utils.LightModeFlag, utils.SyncModeFlag,
}, },
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `

View file

@ -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") })

View file

@ -73,6 +73,7 @@ var (
utils.EthashDatasetDirFlag, utils.EthashDatasetDirFlag,
utils.EthashDatasetsInMemoryFlag, utils.EthashDatasetsInMemoryFlag,
utils.EthashDatasetsOnDiskFlag, utils.EthashDatasetsOnDiskFlag,
utils.TxPoolLocalsFlag,
utils.TxPoolNoLocalsFlag, utils.TxPoolNoLocalsFlag,
utils.TxPoolJournalFlag, utils.TxPoolJournalFlag,
utils.TxPoolRejournalFlag, utils.TxPoolRejournalFlag,
@ -83,6 +84,7 @@ var (
utils.TxPoolAccountQueueFlag, utils.TxPoolAccountQueueFlag,
utils.TxPoolGlobalQueueFlag, utils.TxPoolGlobalQueueFlag,
utils.TxPoolLifetimeFlag, utils.TxPoolLifetimeFlag,
utils.FastSyncFlag, utils.FastSyncFlag,
utils.ExitWhenSyncedFlag, utils.ExitWhenSyncedFlag,
utils.LightModeFlag, utils.LightModeFlag,
@ -98,11 +100,21 @@ var (
utils.ListenPortFlag, utils.ListenPortFlag,
utils.MaxPeersFlag, utils.MaxPeersFlag,
utils.MaxPendingPeersFlag, utils.MaxPendingPeersFlag,
utils.EtherbaseFlag,
utils.GasPriceFlag,
utils.MinerThreadsFlag,
utils.MiningEnabledFlag, utils.MiningEnabledFlag,
utils.TargetGasLimitFlag, utils.MinerThreadsFlag,
utils.MinerLegacyThreadsFlag,
utils.MinerNotifyFlag,
utils.MinerGasTargetFlag,
utils.MinerLegacyGasTargetFlag,
utils.MinerGasLimitFlag,
utils.MinerGasPriceFlag,
utils.MinerLegacyGasPriceFlag,
utils.MinerEtherbaseFlag,
utils.MinerLegacyEtherbaseFlag,
utils.MinerExtraDataFlag,
utils.MinerLegacyExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNoVerfiyFlag,
utils.NATFlag, utils.NATFlag,
utils.NoDiscoverFlag, utils.NoDiscoverFlag,
utils.DiscoveryV5Flag, utils.DiscoveryV5Flag,
@ -123,7 +135,6 @@ var (
utils.NoCompactionFlag, utils.NoCompactionFlag,
utils.GpoBlocksFlag, utils.GpoBlocksFlag,
utils.GpoPercentileFlag, utils.GpoPercentileFlag,
utils.ExtraDataFlag,
configFileFlag, configFileFlag,
} }
@ -231,7 +242,6 @@ func init() {
// Start system runtime metrics collection // Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second) go metrics.CollectProcessMetrics(3 * time.Second)
utils.SetupNetwork(ctx)
return nil return nil
} }
@ -253,6 +263,9 @@ func main() {
// It creates a default node based on the command line arguments and runs it in // It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down. // blocking mode, waiting for it to be shut down.
func geth(ctx *cli.Context) error { func geth(ctx *cli.Context) error {
if args := ctx.Args(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0])
}
node := makeFullNode(ctx) node := makeFullNode(ctx)
startNode(ctx, node) startNode(ctx, node)
node.Wait() node.Wait()
@ -349,25 +362,25 @@ func startNode(ctx *cli.Context, stack *node.Node) {
// Start auxiliary services if enabled // Start auxiliary services if enabled
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
// Mining only makes sense if a full Ethereum node is running // Mining only makes sense if a full Ethereum node is running
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
utils.Fatalf("Light clients do not support mining") utils.Fatalf("Light clients do not support mining")
} }
var ethereum *eth.Ethereum var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil { if err := stack.Service(&ethereum); err != nil {
utils.Fatalf("Ethereum service not running: %v", err) utils.Fatalf("Ethereum service not running: %v", err)
} }
// Use a reduced number of threads if requested
if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
type threaded interface {
SetThreads(threads int)
}
if th, ok := ethereum.Engine().(threaded); ok {
th.SetThreads(threads)
}
}
// Set the gas price to the limits from the CLI and start mining // Set the gas price to the limits from the CLI and start mining
ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name)) gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name)
if err := ethereum.StartMining(true); err != nil { if ctx.IsSet(utils.MinerGasPriceFlag.Name) {
gasprice = utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
}
ethereum.TxPool().SetGasPrice(gasprice)
threads := ctx.GlobalInt(utils.MinerLegacyThreadsFlag.Name)
if ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) {
threads = ctx.GlobalInt(utils.MinerThreadsFlag.Name)
}
if err := ethereum.StartMining(threads); err != nil {
utils.Fatalf("Failed to start mining: %v", err) utils.Fatalf("Failed to start mining: %v", err)
} }
} }

View file

@ -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)
} }

View file

@ -115,6 +115,7 @@ var AppHelpFlagGroups = []flagGroup{
{ {
Name: "TRANSACTION POOL", Name: "TRANSACTION POOL",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.TxPoolLocalsFlag,
utils.TxPoolNoLocalsFlag, utils.TxPoolNoLocalsFlag,
utils.TxPoolJournalFlag, utils.TxPoolJournalFlag,
utils.TxPoolRejournalFlag, utils.TxPoolRejournalFlag,
@ -186,10 +187,14 @@ var AppHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.MiningEnabledFlag, utils.MiningEnabledFlag,
utils.MinerThreadsFlag, utils.MinerThreadsFlag,
utils.EtherbaseFlag, utils.MinerNotifyFlag,
utils.TargetGasLimitFlag, utils.MinerGasPriceFlag,
utils.GasPriceFlag, utils.MinerGasTargetFlag,
utils.ExtraDataFlag, utils.MinerGasLimitFlag,
utils.MinerEtherbaseFlag,
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNoVerfiyFlag,
}, },
}, },
{ {
@ -231,8 +236,11 @@ var AppHelpFlagGroups = []flagGroup{
{ {
Name: "DEPRECATED", Name: "DEPRECATED",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.FastSyncFlag, utils.MinerLegacyThreadsFlag,
utils.LightModeFlag, utils.MinerLegacyGasTargetFlag,
utils.MinerLegacyGasPriceFlag,
utils.MinerLegacyEtherbaseFlag,
utils.MinerLegacyExtraDataFlag,
}, },
}, },
{ {

View file

@ -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

View file

@ -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"),
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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}}--miner.etherbase {{.Etherbase}} --mine --miner.threads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --miner.gastarget {{.GasTarget}} --miner.gaslimit {{.GasLimit}} --miner.gasprice {{.GasPrice}}' >> geth.sh
ENTRYPOINT ["/bin/sh", "geth.sh"] ENTRYPOINT ["/bin/sh", "geth.sh"]
` `
@ -68,6 +68,7 @@ services:
- STATS_NAME={{.Ethstats}} - STATS_NAME={{.Ethstats}}
- MINER_NAME={{.Etherbase}} - MINER_NAME={{.Etherbase}}
- GAS_TARGET={{.GasTarget}} - GAS_TARGET={{.GasTarget}}
- GAS_LIMIT={{.GasLimit}}
- GAS_PRICE={{.GasPrice}} - GAS_PRICE={{.GasPrice}}
logging: logging:
driver: "json-file" driver: "json-file"
@ -104,6 +105,7 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n
"Ethstats": config.ethstats, "Ethstats": config.ethstats,
"Etherbase": config.etherbase, "Etherbase": config.etherbase,
"GasTarget": uint64(1000000 * config.gasTarget), "GasTarget": uint64(1000000 * config.gasTarget),
"GasLimit": uint64(1000000 * config.gasLimit),
"GasPrice": uint64(1000000000 * config.gasPrice), "GasPrice": uint64(1000000000 * config.gasPrice),
"Unlock": config.keyJSON != "", "Unlock": config.keyJSON != "",
}) })
@ -122,6 +124,7 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n
"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")], "Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
"Etherbase": config.etherbase, "Etherbase": config.etherbase,
"GasTarget": config.gasTarget, "GasTarget": config.gasTarget,
"GasLimit": config.gasLimit,
"GasPrice": config.gasPrice, "GasPrice": config.gasPrice,
}) })
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
@ -139,9 +142,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
@ -160,6 +163,7 @@ type nodeInfos struct {
keyJSON string keyJSON string
keyPass string keyPass string
gasTarget float64 gasTarget float64
gasLimit float64
gasPrice float64 gasPrice float64
} }
@ -175,8 +179,9 @@ func (info *nodeInfos) Report() map[string]string {
} }
if info.gasTarget > 0 { if info.gasTarget > 0 {
// Miner or signer node // Miner or signer node
report["Gas limit (baseline target)"] = fmt.Sprintf("%0.3f MGas", info.gasTarget)
report["Gas price (minimum accepted)"] = fmt.Sprintf("%0.3f GWei", info.gasPrice) report["Gas price (minimum accepted)"] = fmt.Sprintf("%0.3f GWei", info.gasPrice)
report["Gas floor (baseline target)"] = fmt.Sprintf("%0.3f MGas", info.gasTarget)
report["Gas ceil (target maximum)"] = fmt.Sprintf("%0.3f MGas", info.gasLimit)
if info.etherbase != "" { if info.etherbase != "" {
// Ethash proof-of-work miner // Ethash proof-of-work miner
@ -217,11 +222,12 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
totalPeers, _ := strconv.Atoi(infos.envvars["TOTAL_PEERS"]) totalPeers, _ := strconv.Atoi(infos.envvars["TOTAL_PEERS"])
lightPeers, _ := strconv.Atoi(infos.envvars["LIGHT_PEERS"]) lightPeers, _ := strconv.Atoi(infos.envvars["LIGHT_PEERS"])
gasTarget, _ := strconv.ParseFloat(infos.envvars["GAS_TARGET"], 64) gasTarget, _ := strconv.ParseFloat(infos.envvars["GAS_TARGET"], 64)
gasLimit, _ := strconv.ParseFloat(infos.envvars["GAS_LIMIT"], 64)
gasPrice, _ := strconv.ParseFloat(infos.envvars["GAS_PRICE"], 64) gasPrice, _ := strconv.ParseFloat(infos.envvars["GAS_PRICE"], 64)
// 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), "\"")
@ -256,6 +262,7 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
keyJSON: keyJSON, keyJSON: keyJSON,
keyPass: keyPass, keyPass: keyPass,
gasTarget: gasTarget, gasTarget: gasTarget,
gasLimit: gasLimit,
gasPrice: gasPrice, gasPrice: gasPrice,
} }
stats.enode = fmt.Sprintf("enode://%s@%s:%d", id, client.address, stats.port) stats.enode = fmt.Sprintf("enode://%s@%s:%d", id, client.address, stats.port)

View file

@ -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

View file

@ -45,33 +45,44 @@ type sshClient struct {
// dial establishes an SSH connection to a remote node using the current user and // dial establishes an SSH connection to a remote node using the current user and
// the user's configured private RSA key. If that fails, password authentication // the user's configured private RSA key. If that fails, password authentication
// is fallen back to. The caller may override the login user via user@server:port. // is fallen back to. server can be a string like user:identity@server:port.
func dial(server string, pubkey []byte) (*sshClient, error) { func dial(server string, pubkey []byte) (*sshClient, error) {
// Figure out a label for the server and a logger // Figure out username, identity, hostname and port
label := server hostname := ""
if strings.Contains(label, ":") { hostport := server
label = label[:strings.Index(label, ":")] username := ""
} identity := "id_rsa" // default
login := ""
if strings.Contains(server, "@") { if strings.Contains(server, "@") {
login = label[:strings.Index(label, "@")] prefix := server[:strings.Index(server, "@")]
label = label[strings.Index(label, "@")+1:] if strings.Contains(prefix, ":") {
server = server[strings.Index(server, "@")+1:] username = prefix[:strings.Index(prefix, ":")]
identity = prefix[strings.Index(prefix, ":")+1:]
} else {
username = prefix
} }
logger := log.New("server", label) hostport = server[strings.Index(server, "@")+1:]
}
if strings.Contains(hostport, ":") {
hostname = hostport[:strings.Index(hostport, ":")]
} else {
hostname = hostport
hostport += ":22"
}
logger := log.New("server", server)
logger.Debug("Attempting to establish SSH connection") logger.Debug("Attempting to establish SSH connection")
user, err := user.Current() user, err := user.Current()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if login == "" { if username == "" {
login = user.Username username = user.Username
} }
// Configure the supported authentication methods (private key and password) // Configure the supported authentication methods (private key and password)
var auths []ssh.AuthMethod var auths []ssh.AuthMethod
path := filepath.Join(user.HomeDir, ".ssh", "id_rsa") path := filepath.Join(user.HomeDir, ".ssh", identity)
if buf, err := ioutil.ReadFile(path); err != nil { if buf, err := ioutil.ReadFile(path); err != nil {
log.Warn("No SSH key, falling back to passwords", "path", path, "err", err) log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
} else { } else {
@ -94,14 +105,14 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
} }
} }
auths = append(auths, ssh.PasswordCallback(func() (string, error) { auths = append(auths, ssh.PasswordCallback(func() (string, error) {
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server) fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server)
blob, err := terminal.ReadPassword(int(os.Stdin.Fd())) blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Println() fmt.Println()
return string(blob), err return string(blob), err
})) }))
// Resolve the IP address of the remote server // Resolve the IP address of the remote server
addr, err := net.LookupHost(label) addr, err := net.LookupHost(hostname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,10 +120,7 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
return nil, errors.New("no IPs associated with domain") return nil, errors.New("no IPs associated with domain")
} }
// Try to dial in to the remote server // Try to dial in to the remote server
logger.Trace("Dialing remote SSH server", "user", login) logger.Trace("Dialing remote SSH server", "user", username)
if !strings.Contains(server, ":") {
server += ":22"
}
keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error { keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// If no public key is known for SSH, ask the user to confirm // If no public key is known for SSH, ask the user to confirm
if pubkey == nil { if pubkey == nil {
@ -139,13 +147,13 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
// We have a mismatch, forbid connecting // We have a mismatch, forbid connecting
return errors.New("ssh key mismatch, readd the machine to update") return errors.New("ssh key mismatch, readd the machine to update")
} }
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck}) client, err := ssh.Dial("tcp", hostport, &ssh.ClientConfig{User: username, Auth: auths, HostKeyCallback: keycheck})
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Connection established, return our utility wrapper // Connection established, return our utility wrapper
c := &sshClient{ c := &sshClient{
server: label, server: hostname,
address: addr[0], address: addr[0],
pubkey: pubkey, pubkey: pubkey,
client: client, client: client,

View file

@ -82,7 +82,6 @@ func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *s
logger.Info("Starting remote server health-check") logger.Info("Starting remote server health-check")
stat := &serverStat{ stat := &serverStat{
address: client.address,
services: make(map[string]map[string]string), services: make(map[string]map[string]string),
} }
if client == nil { if client == nil {
@ -94,6 +93,8 @@ func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *s
} }
client = conn client = conn
} }
stat.address = client.address
// Client connected one way or another, run health-checks // Client connected one way or another, run health-checks
logger.Debug("Checking for nginx availability") logger.Debug("Checking for nginx availability")
if infos, err := checkNginx(client, w.network); err != nil { if infos, err := checkNginx(client, w.network); err != nil {
@ -203,7 +204,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)
@ -214,6 +215,9 @@ func (stats serverStats) render() {
if len(stat.address) > len(separator[1]) { if len(stat.address) > len(separator[1]) {
separator[1] = strings.Repeat("-", len(stat.address)) separator[1] = strings.Repeat("-", len(stat.address))
} }
if len(stat.failure) > len(separator[1]) {
separator[1] = strings.Repeat("-", len(stat.failure))
}
for service, configs := range stat.services { for service, configs := range stat.services {
if len(service) > len(separator[2]) { if len(service) > len(separator[2]) {
separator[2] = strings.Repeat("-", len(service)) separator[2] = strings.Repeat("-", len(service))
@ -222,8 +226,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))
}
} }
} }
} }
@ -248,8 +254,12 @@ func (stats serverStats) render() {
sort.Strings(services) sort.Strings(services)
if len(services) == 0 { if len(services) == 0 {
if stats[server].failure != "" {
table.Append([]string{server, stats[server].failure, "", "", ""})
} else {
table.Append([]string{server, stats[server].address, "", "", ""}) table.Append([]string{server, stats[server].address, "", "", ""})
} }
}
for j, service := range services { for j, service := range services {
// Add an empty line between all services // Add an empty line between all services
if j > 0 { if j > 0 {
@ -263,13 +273,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})
}
} }
} }
} }

View file

@ -62,14 +62,14 @@ func (w *wizard) manageServers() {
} }
} }
// makeServer reads a single line from stdin and interprets it as a hostname to // makeServer reads a single line from stdin and interprets it as
// connect to. It tries to establish a new SSH session and also executing some // username:identity@hostname to connect to. It tries to establish a
// baseline validations. // new SSH session and also executing some baseline validations.
// //
// If connection succeeds, the server is added to the wizards configs! // If connection succeeds, the server is added to the wizards configs!
func (w *wizard) makeServer() string { func (w *wizard) makeServer() string {
fmt.Println() fmt.Println()
fmt.Println("Please enter remote server's address:") fmt.Println("What is the remote server's address ([username[:identity]@]hostname[:port])?")
// Read and dial the server to ensure docker is present // Read and dial the server to ensure docker is present
input := w.readString() input := w.readString()

View file

@ -50,7 +50,7 @@ func (w *wizard) deployNode(boot bool) {
if boot { if boot {
infos = &nodeInfos{port: 30303, peersTotal: 512, peersLight: 256} infos = &nodeInfos{port: 30303, peersTotal: 512, peersLight: 256}
} else { } else {
infos = &nodeInfos{port: 30303, peersTotal: 50, peersLight: 0, gasTarget: 4.7, gasPrice: 18} infos = &nodeInfos{port: 30303, peersTotal: 50, peersLight: 0, gasTarget: 7.5, gasLimit: 10, gasPrice: 1}
} }
} }
existed := err == nil existed := err == nil
@ -152,6 +152,10 @@ func (w *wizard) deployNode(boot bool) {
fmt.Printf("What gas limit should empty blocks target (MGas)? (default = %0.3f)\n", infos.gasTarget) fmt.Printf("What gas limit should empty blocks target (MGas)? (default = %0.3f)\n", infos.gasTarget)
infos.gasTarget = w.readDefaultFloat(infos.gasTarget) infos.gasTarget = w.readDefaultFloat(infos.gasTarget)
fmt.Println()
fmt.Printf("What gas limit should full blocks target (MGas)? (default = %0.3f)\n", infos.gasLimit)
infos.gasLimit = w.readDefaultFloat(infos.gasLimit)
fmt.Println() fmt.Println()
fmt.Printf("What gas price should the signer require (GWei)? (default = %0.3f)\n", infos.gasPrice) fmt.Printf("What gas price should the signer require (GWei)? (default = %0.3f)\n", infos.gasPrice)
infos.gasPrice = w.readDefaultFloat(infos.gasPrice) infos.gasPrice = w.readDefaultFloat(infos.gasPrice)

219
cmd/swarm/access.go Normal file
View file

@ -0,0 +1,219 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/api/client"
"gopkg.in/urfave/cli.v1"
)
var salt = make([]byte, 32)
func init() {
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
panic("reading from crypto/rand failed: " + err.Error())
}
}
func accessNewPass(ctx *cli.Context) {
args := ctx.Args()
if len(args) != 1 {
utils.Fatalf("Expected 1 argument - the ref")
}
var (
ae *api.AccessEntry
accessKey []byte
err error
ref = args[0]
password = getPassPhrase("", 0, makePasswordList(ctx))
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
accessKey, ae, err = api.DoPasswordNew(ctx, password, salt)
if err != nil {
utils.Fatalf("error getting session key: %v", err)
}
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
if dryRun {
err = printManifests(m, nil)
if err != nil {
utils.Fatalf("had an error printing the manifests: %v", err)
}
} else {
utils.Fatalf("uploading manifests")
err = uploadManifests(ctx, m, nil)
if err != nil {
utils.Fatalf("had an error uploading the manifests: %v", err)
}
}
}
func accessNewPK(ctx *cli.Context) {
args := ctx.Args()
if len(args) != 1 {
utils.Fatalf("Expected 1 argument - the ref")
}
var (
ae *api.AccessEntry
sessionKey []byte
err error
ref = args[0]
privateKey = getPrivKey(ctx)
granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name)
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
sessionKey, ae, err = api.DoPKNew(ctx, privateKey, granteePublicKey, salt)
if err != nil {
utils.Fatalf("error getting session key: %v", err)
}
m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
if dryRun {
err = printManifests(m, nil)
if err != nil {
utils.Fatalf("had an error printing the manifests: %v", err)
}
} else {
err = uploadManifests(ctx, m, nil)
if err != nil {
utils.Fatalf("had an error uploading the manifests: %v", err)
}
}
}
func accessNewACT(ctx *cli.Context) {
args := ctx.Args()
if len(args) != 1 {
utils.Fatalf("Expected 1 argument - the ref")
}
var (
ae *api.AccessEntry
actManifest *api.Manifest
accessKey []byte
err error
ref = args[0]
grantees = []string{}
actFilename = ctx.String(SwarmAccessGrantKeysFlag.Name)
privateKey = getPrivKey(ctx)
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
bytes, err := ioutil.ReadFile(actFilename)
if err != nil {
utils.Fatalf("had an error reading the grantee public key list")
}
grantees = strings.Split(string(bytes), "\n")
accessKey, ae, actManifest, err = api.DoACTNew(ctx, privateKey, salt, grantees)
if err != nil {
utils.Fatalf("error generating ACT manifest: %v", err)
}
if err != nil {
utils.Fatalf("error getting session key: %v", err)
}
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
if err != nil {
utils.Fatalf("error generating root access manifest: %v", err)
}
if dryRun {
err = printManifests(m, actManifest)
if err != nil {
utils.Fatalf("had an error printing the manifests: %v", err)
}
} else {
err = uploadManifests(ctx, m, actManifest)
if err != nil {
utils.Fatalf("had an error uploading the manifests: %v", err)
}
}
}
func printManifests(rootAccessManifest, actManifest *api.Manifest) error {
js, err := json.Marshal(rootAccessManifest)
if err != nil {
return err
}
fmt.Println(string(js))
if actManifest != nil {
js, err := json.Marshal(actManifest)
if err != nil {
return err
}
fmt.Println(string(js))
}
return nil
}
func uploadManifests(ctx *cli.Context, rootAccessManifest, actManifest *api.Manifest) error {
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client := client.NewClient(bzzapi)
var (
key string
err error
)
if actManifest != nil {
key, err = client.UploadManifest(actManifest, false)
if err != nil {
return err
}
rootAccessManifest.Entries[0].Access.Act = key
}
key, err = client.UploadManifest(rootAccessManifest, false)
if err != nil {
return err
}
fmt.Println(key)
return nil
}
// makePasswordList reads password lines from the file specified by the global --password flag
// and also by the same subcommand --password flag.
// This function ia a fork of utils.MakePasswordList to lookup cli context for subcommand.
// Function ctx.SetGlobal is not setting the global flag value that can be accessed
// by ctx.GlobalString using the current version of cli package.
func makePasswordList(ctx *cli.Context) []string {
path := ctx.GlobalString(utils.PasswordFileFlag.Name)
if path == "" {
path = ctx.String(utils.PasswordFileFlag.Name)
if path == "" {
return nil
}
}
text, err := ioutil.ReadFile(path)
if err != nil {
utils.Fatalf("Failed to read password file: %v", err)
}
lines := strings.Split(string(text), "\n")
// Sanitise DOS line endings.
for i := range lines {
lines[i] = strings.TrimRight(lines[i], "\r")
}
return lines
}

584
cmd/swarm/access_test.go Normal file
View file

@ -0,0 +1,584 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// +build !windows
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"io"
"io/ioutil"
gorand "math/rand"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/api"
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
)
// TestAccessPassword tests for the correct creation of an ACT manifest protected by a password.
// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
// The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded
// is then fetched through 2nd node. since the tested code is not key-aware - we can just
// fetch from the 2nd node using HTTP BasicAuth
func TestAccessPassword(t *testing.T) {
cluster := newTestCluster(t, 1)
defer cluster.Shutdown()
proxyNode := cluster.Nodes[0]
// create a tmp file
tmp, err := ioutil.TempDir("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
// write data to file
data := "notsorandomdata"
dataFilename := filepath.Join(tmp, "data.txt")
err = ioutil.WriteFile(dataFilename, []byte(data), 0666)
if err != nil {
t.Fatal(err)
}
hashRegexp := `[a-f\d]{128}`
// upload the file with 'swarm up' and expect a hash
up := runSwarm(t,
"--bzzapi",
proxyNode.URL, //it doesn't matter through which node we upload content
"up",
"--encrypt",
dataFilename)
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
if len(matches) < 1 {
t.Fatal("no matches found")
}
ref := matches[0]
password := "smth"
passwordFilename := filepath.Join(tmp, "password.txt")
err = ioutil.WriteFile(passwordFilename, []byte(password), 0666)
if err != nil {
t.Fatal(err)
}
up = runSwarm(t,
"access",
"new",
"pass",
"--dry-run",
"--password",
passwordFilename,
ref,
)
_, matches = up.ExpectRegexp(".+")
up.ExpectExit()
if len(matches) == 0 {
t.Fatalf("stdout not matched")
}
var m api.Manifest
err = json.Unmarshal([]byte(matches[0]), &m)
if err != nil {
t.Fatalf("unmarshal manifest: %v", err)
}
if len(m.Entries) != 1 {
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
}
e := m.Entries[0]
ct := "application/bzz-manifest+json"
if e.ContentType != ct {
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
}
if e.Access == nil {
t.Fatal("manifest access is nil")
}
a := e.Access
if a.Type != "pass" {
t.Errorf(`got access type %q, expected "pass"`, a.Type)
}
if len(a.Salt) < 32 {
t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
}
if a.KdfParams == nil {
t.Fatal("manifest access kdf params is nil")
}
client := swarm.NewClient(cluster.Nodes[0].URL)
hash, err := client.UploadManifest(&m, false)
if err != nil {
t.Fatal(err)
}
httpClient := &http.Client{}
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
response, err := httpClient.Get(url)
if err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusUnauthorized {
t.Fatal("should be a 401")
}
authHeader := response.Header.Get("WWW-Authenticate")
if authHeader == "" {
t.Fatal("should be something here")
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("", password)
response, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode)
}
d, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
if string(d) != data {
t.Errorf("expected decrypted data %q, got %q", data, string(d))
}
wrongPasswordFilename := filepath.Join(tmp, "password-wrong.txt")
err = ioutil.WriteFile(wrongPasswordFilename, []byte("just wr0ng"), 0666)
if err != nil {
t.Fatal(err)
}
//download file with 'swarm down' with wrong password
up = runSwarm(t,
"--bzzapi",
proxyNode.URL,
"down",
"bzz:/"+hash,
tmp,
"--password",
wrongPasswordFilename)
_, matches = up.ExpectRegexp("unauthorized")
if len(matches) != 1 && matches[0] != "unauthorized" {
t.Fatal(`"unauthorized" not found in output"`)
}
up.ExpectExit()
}
// TestAccessPK tests for the correct creation of an ACT manifest between two parties (publisher and grantee).
// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
// The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears.
// Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware,
// the test will fail if the proxy's given private key is not granted on the ACT.
func TestAccessPK(t *testing.T) {
// Setup Swarm and upload a test file to it
cluster := newTestCluster(t, 1)
defer cluster.Shutdown()
// create a tmp file
tmp, err := ioutil.TempFile("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer tmp.Close()
defer os.Remove(tmp.Name())
// write data to file
data := "notsorandomdata"
_, err = io.WriteString(tmp, data)
if err != nil {
t.Fatal(err)
}
hashRegexp := `[a-f\d]{128}`
// upload the file with 'swarm up' and expect a hash
up := runSwarm(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
"--encrypt",
tmp.Name())
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
if len(matches) < 1 {
t.Fatal("no matches found")
}
ref := matches[0]
pk := cluster.Nodes[0].PrivateKey
granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
if err != nil {
t.Fatal(err)
}
passFile, err := ioutil.TempFile("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer passFile.Close()
defer os.Remove(passFile.Name())
_, err = io.WriteString(passFile, testPassphrase)
if err != nil {
t.Fatal(err)
}
_, publisherAccount := getTestAccount(t, publisherDir)
up = runSwarm(t,
"--bzzaccount",
publisherAccount.Address.String(),
"--password",
passFile.Name(),
"--datadir",
publisherDir,
"--bzzapi",
cluster.Nodes[0].URL,
"access",
"new",
"pk",
"--dry-run",
"--grant-key",
hex.EncodeToString(granteePubKey),
ref,
)
_, matches = up.ExpectRegexp(".+")
up.ExpectExit()
if len(matches) == 0 {
t.Fatalf("stdout not matched")
}
var m api.Manifest
err = json.Unmarshal([]byte(matches[0]), &m)
if err != nil {
t.Fatalf("unmarshal manifest: %v", err)
}
if len(m.Entries) != 1 {
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
}
e := m.Entries[0]
ct := "application/bzz-manifest+json"
if e.ContentType != ct {
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
}
if e.Access == nil {
t.Fatal("manifest access is nil")
}
a := e.Access
if a.Type != "pk" {
t.Errorf(`got access type %q, expected "pk"`, a.Type)
}
if len(a.Salt) < 32 {
t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
}
if a.KdfParams != nil {
t.Fatal("manifest access kdf params should be nil")
}
client := swarm.NewClient(cluster.Nodes[0].URL)
hash, err := client.UploadManifest(&m, false)
if err != nil {
t.Fatal(err)
}
httpClient := &http.Client{}
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
response, err := httpClient.Get(url)
if err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusOK {
t.Fatal("should be a 200")
}
d, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
if string(d) != data {
t.Errorf("expected decrypted data %q, got %q", data, string(d))
}
}
// TestAccessACT tests the e2e creation, uploading and downloading of an ACT type access control
// the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data
// set. the third node should fail decoding the reference as it will not be granted access. the publisher uploads through
// one of the nodes then disappears.
func TestAccessACT(t *testing.T) {
// Setup Swarm and upload a test file to it
cluster := newTestCluster(t, 3)
defer cluster.Shutdown()
var uploadThroughNode = cluster.Nodes[0]
client := swarm.NewClient(uploadThroughNode.URL)
r1 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
nodeToSkip := r1.Intn(3) // a number between 0 and 2 (node indices in `cluster`)
// create a tmp file
tmp, err := ioutil.TempFile("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer tmp.Close()
defer os.Remove(tmp.Name())
// write data to file
data := "notsorandomdata"
_, err = io.WriteString(tmp, data)
if err != nil {
t.Fatal(err)
}
hashRegexp := `[a-f\d]{128}`
// upload the file with 'swarm up' and expect a hash
up := runSwarm(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
"--encrypt",
tmp.Name())
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
if len(matches) < 1 {
t.Fatal("no matches found")
}
ref := matches[0]
grantees := []string{}
for i, v := range cluster.Nodes {
if i == nodeToSkip {
continue
}
pk := v.PrivateKey
granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
grantees = append(grantees, hex.EncodeToString(granteePubKey))
}
granteesPubkeyListFile, err := ioutil.TempFile("", "grantees-pubkey-list.csv")
if err != nil {
t.Fatal(err)
}
_, err = granteesPubkeyListFile.WriteString(strings.Join(grantees, "\n"))
if err != nil {
t.Fatal(err)
}
defer granteesPubkeyListFile.Close()
defer os.Remove(granteesPubkeyListFile.Name())
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
if err != nil {
t.Fatal(err)
}
passFile, err := ioutil.TempFile("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer passFile.Close()
defer os.Remove(passFile.Name())
_, err = io.WriteString(passFile, testPassphrase)
if err != nil {
t.Fatal(err)
}
_, publisherAccount := getTestAccount(t, publisherDir)
up = runSwarm(t,
"--bzzaccount",
publisherAccount.Address.String(),
"--password",
passFile.Name(),
"--datadir",
publisherDir,
"--bzzapi",
cluster.Nodes[0].URL,
"access",
"new",
"act",
"--grant-keys",
granteesPubkeyListFile.Name(),
ref,
)
_, matches = up.ExpectRegexp(`[a-f\d]{64}`)
up.ExpectExit()
if len(matches) == 0 {
t.Fatalf("stdout not matched")
}
hash := matches[0]
m, _, err := client.DownloadManifest(hash)
if err != nil {
t.Fatalf("unmarshal manifest: %v", err)
}
if len(m.Entries) != 1 {
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
}
e := m.Entries[0]
ct := "application/bzz-manifest+json"
if e.ContentType != ct {
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
}
if e.Access == nil {
t.Fatal("manifest access is nil")
}
a := e.Access
if a.Type != "act" {
t.Fatalf(`got access type %q, expected "act"`, a.Type)
}
if len(a.Salt) < 32 {
t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
}
if a.KdfParams != nil {
t.Fatal("manifest access kdf params should be nil")
}
httpClient := &http.Client{}
// all nodes except the skipped node should be able to decrypt the content
for i, node := range cluster.Nodes {
log.Debug("trying to fetch from node", "node index", i)
url := node.URL + "/" + "bzz:/" + hash
response, err := httpClient.Get(url)
if err != nil {
t.Fatal(err)
}
log.Debug("got response from node", "response code", response.StatusCode)
if i == nodeToSkip {
log.Debug("reached node to skip", "status code", response.StatusCode)
if response.StatusCode != http.StatusUnauthorized {
t.Fatalf("should be a 401")
}
continue
}
if response.StatusCode != http.StatusOK {
t.Fatal("should be a 200")
}
d, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
if string(d) != data {
t.Errorf("expected decrypted data %q, got %q", data, string(d))
}
}
}
// TestKeypairSanity is a sanity test for the crypto scheme for ACT. it asserts the correct shared secret according to
// the specs at https://github.com/ethersphere/swarm-docs/blob/eb857afda906c6e7bb90d37f3f334ccce5eef230/act.md
func TestKeypairSanity(t *testing.T) {
salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
t.Fatalf("reading from crypto/rand failed: %v", err.Error())
}
sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d"
for i, v := range []struct {
publisherPriv string
granteePub string
}{
{
publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459",
granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a",
},
{
publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d",
granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db",
},
} {
b, _ := hex.DecodeString(v.granteePub)
granteePub, _ := crypto.DecompressPubkey(b)
publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv)
ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt)
if err != nil {
t.Fatal(err)
}
hasher := sha3.NewKeccak256()
hasher.Write(salt)
shared, err := hex.DecodeString(sharedSecret)
if err != nil {
t.Fatal(err)
}
hasher.Write(shared)
sum := hasher.Sum(nil)
if !bytes.Equal(ssKey, sum) {
t.Fatalf("%d: got a session key mismatch", i)
}
}
}

77
cmd/swarm/bootnodes.go Normal file
View file

@ -0,0 +1,77 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
var SwarmBootnodes = []string{
// Foundation Swarm Gateway Cluster
"enode://e5c6f9215c919a5450a7b8c14c22535607b69f2c8e1e7f6f430cb25d7a2c27cd1df4c4f18ad7c1d7e5162e271ffcd3f20b1a1467fb6e790e7d727f3b2193de97@52.232.7.187:30399",
"enode://9b2fe07e69ccc7db5fef15793dab7d7d2e697ed92132d6e9548218e68a34613a8671ad03a6658d862b468ed693cae8a0f8f8d37274e4a657ffb59ca84676e45b@52.232.7.187:30400",
"enode://76c1059162c93ef9df0f01097c824d17c492634df211ef4c806935b349082233b63b90c23970254b3b7138d630400f7cf9b71e80355a446a8b733296cb04169a@52.232.7.187:30401",
"enode://ce46bbe2a8263145d65252d52da06e000ad350ed09c876a71ea9544efa42f63c1e1b6cc56307373aaad8f9dd069c90d0ed2dd1530106200e16f4ca681dd8ae2d@52.232.7.187:30402",
"enode://f431e0d6008a6c35c6e670373d828390c8323e53da8158e7bfc43cf07e632cc9e472188be8df01decadea2d4a068f1428caba769b632554a8fb0607bc296988f@52.232.7.187:30403",
"enode://174720abfff83d7392f121108ae50ea54e04889afe020df883655c0f6cb95414db945a0228d8982fe000d86fc9f4b7669161adc89cd7cd56f78f01489ab2b99b@52.232.7.187:30404",
"enode://2ae89be4be61a689b6f9ecee4360a59e185e010ab750f14b63b4ae43d4180e872e18e3437d4386ce44875dc7cc6eb761acba06412fe3178f3dac1dab3b65703e@52.232.7.187:30405",
"enode://24abebe1c0e6d75d6052ce3219a87be8573fd6397b4cb51f0773b83abba9b3d872bfb273cdc07389715b87adfac02f5235f5241442c5089802cbd8d42e310fce@52.232.7.187:30406",
"enode://d08dfa46bfbbdbcaafbb6e34abee4786610f6c91e0b76d7881f0334ac10dda41d8c1f2b6eedffb4493293c335c0ad46776443b2208d1fbbb9e1a90b25ee4eef2@52.232.7.187:30407",
"enode://8d95eb0f837d27581a43668ed3b8783d69dc4e84aa3edd7a0897e026155c8f59c8702fdc0375ee7bac15757c9c78e1315d9b73e4ce59c936db52ea4ae2f501c7@52.232.7.187:30408",
"enode://a5967cc804aebd422baaaba9f06f27c9e695ccab335b61088130f8cbe64e3cdf78793868c7051dfc06eecfe844fad54bc7f6dfaed9db3c7ecef279cb829c25fb@52.232.7.187:30409",
"enode://5f00134d81a8f2ebcc46f8766f627f492893eda48138f811b7de2168308171968f01710bca6da05764e74f14bae41652f554e6321f1aed85fa3461e89d075dbf@52.232.7.187:30410",
"enode://b2142b79b01a5aa66a5e23cc35e78219a8e97bc2412a6698cee24ae02e87078b725d71730711bd62e25ff1aa8658c6633778af8ac14c63814a337c3dd0ebda9f@52.232.7.187:30411",
"enode://1ffa7651094867d6486ce3ef46d27a052c2cb968b618346c6df7040322c7efc3337547ba85d4cbba32e8b31c42c867202554735c06d4c664b9afada2ed0c4b3c@52.232.7.187:30412",
"enode://129e0c3d5f5df12273754f6f703d2424409fa4baa599e0b758c55600169313887855e75b082028d2302ec034b303898cd697cc7ae8256ba924ce927510da2c8d@52.232.7.187:30413",
"enode://419e2dc0d2f5b022cf16b0e28842658284909fa027a0fbbb5e2b755e7f846ea02a8f0b66a7534981edf6a7bcf8a14855344c6668e2cd4476ccd35a11537c9144@52.232.7.187:30414",
"enode://23d55ad900583231b91f2f62e3f72eb498b342afd58b682be3af052eed62b5651094471065981de33d8786f075f05e3cca499503b0ac8ae84b2a06e99f5b0723@52.232.7.187:30415",
"enode://bc56e4158c00e9f616d7ea533def20a89bef959df4e62a768ff238ff4e1e9223f57ecff969941c20921bad98749baae311c0fbebce53bf7bbb9d3dc903640990@52.232.7.187:30416",
"enode://433ce15199c409875e7e72fffd69fdafe746f17b20f0d5555281722a65fde6c80328fab600d37d8624509adc072c445ce0dad4a1c01cff6acf3132c11d429d4d@52.232.7.187:30417",
"enode://632ee95b8f0eac51ef89ceb29313fef3a60050181d66a6b125583b1a225a7694b252edc016efb58aa3b251da756cb73280842a022c658ed405223b2f58626343@52.232.7.187:30418",
"enode://4a0f9bcff7a4b9ee453fb298d0fb222592efe121512e30cd72fef631beb8c6a15153a1456eb073ee18551c0e003c569651a101892dc4124e90b933733a498bb5@52.232.7.187:30419",
"enode://f0d80fbc72d16df30e19aac3051eb56a7aff0c8367686702e01ea132d8b0b3ee00cadd6a859d2cca98ec68d3d574f8a8a87dba2347ec1e2818dc84bc3fa34fae@52.232.7.187:30420",
"enode://a199146906e4f9f2b94b195a8308d9a59a3564b92efaab898a4243fe4c2ad918b7a8e4853d9d901d94fad878270a2669d644591299c3d43de1b298c00b92b4a7@52.232.7.187:30421",
"enode://052036ea8736b37adbfb684d90ce43e11b3591b51f31489d7c726b03618dea4f73b1e659deb928e6bf40564edcdcf08351643f42db3d4ca1c2b5db95dad59e94@52.232.7.187:30422",
"enode://460e2b8c6da8f12fac96c836e7d108f4b7ec55a1c64631bb8992339e117e1c28328fee83af863196e20af1487a655d13e5ceba90e980e92502d5bac5834c1f71@52.232.7.187:30423",
"enode://6d2cdd13741b2e72e9031e1b93c6d9a4e68de2844aa4e939f6a8a8498a7c1d7e2ee4c64217e92a6df08c9a32c6764d173552810ef1bd2ecb356532d389dd2136@52.232.7.187:30424",
"enode://62105fc25ce2cd5b299647f47eaa9211502dc76f0e9f461df915782df7242ac3223e3db04356ae6ed2977ccac20f0b16864406e9ca514a40a004cb6a5d0402aa@52.232.7.187:30425",
"enode://e0e388fc520fd493c33f0ce16685e6f98fb6aec28f2edc14ee6b179594ee519a896425b0025bb6f0e182dd3e468443f19c70885fbc66560d000093a668a86aa8@52.232.7.187:30426",
"enode://63f3353a72521ea10022127a4fe6b4acbef197c3fe668fd9f4805542d8a6fcf79f6335fbab62d180a35e19b739483e740858b113fdd7c13a26ad7b4e318a5aef@52.232.7.187:30427",
"enode://33a42b927085678d4aefd4e70b861cfca6ef5f6c143696c4f755973fd29e64c9e658cad57a66a687a7a156da1e3688b1fbdd17bececff2ee009fff038fa5666b@52.232.7.187:30428",
"enode://259ab5ab5c1daee3eab7e3819ab3177b82d25c29e6c2444fdd3f956e356afae79a72840ccf2d0665fe82c81ebc3b3734da1178ac9fd5d62c67e674b69f86b6be@52.232.7.187:30429",
"enode://558bccad7445ce3fd8db116ed6ab4aed1324fdbdac2348417340c1764dc46d46bffe0728e5b7d5c36f12e794c289f18f57f08f085d2c65c9910a5c7a65b6a66a@52.232.7.187:30430",
"enode://abe60937a0657ffded718e3f84a32987286983be257bdd6004775c4b525747c2b598f4fac49c8de324de5ce75b22673fa541a7ce2d555fb7f8ca325744ae3577@52.232.7.187:30431",
"enode://bce6f0aaa5b230742680084df71d4f026b3eff7f564265599216a1b06b765303fdc9325de30ffd5dfdaf302ce4b14322891d2faea50ce2ca298d7409f5858339@52.232.7.187:30432",
"enode://21b957c4e03277d42be6660730ec1b93f540764f26c6abdb54d006611139c7081248486206dfbf64fcaffd62589e9c6b8ea77a5297e4b21a605f1bcf49483ed0@52.232.7.187:30433",
"enode://ff104e30e64f24c3d7328acee8b13354e5551bc8d60bb25ecbd9632d955c7e34bb2d969482d173355baad91c8282f8b592624eb3929151090da3b4448d4d58fb@52.232.7.187:30434",
"enode://c76e2b5f81a521bceaec1518926a21380a345df9cf463461562c6845795512497fb67679e155fc96a74350f8b78de8f4c135dd52b106dbbb9795452021d09ea5@52.232.7.187:30435",
"enode://3288fd860105164f3e9b69934c4eb18f7146cfab31b5a671f994e21a36e9287766e5f9f075aefbc404538c77f7c2eb2a4495020a7633a1c3970d94e9fa770aeb@52.232.7.187:30436",
"enode://6cea859c7396d46b20cfcaa80f9a11cd112f8684f2f782f7b4c0e1e0af9212113429522075101923b9b957603e6c32095a6a07b5e5e35183c521952ee108dfaf@52.232.7.187:30437",
"enode://f628ec56e4ca8317cc24cc4ac9b27b95edcce7b96e1c7f3b53e30de4a8580fe44f2f0694a513bdb0a431acaf2824074d6ace4690247bbc34c14f426af8c056ea@52.232.7.187:30438",
"enode://055ec8b26fc105c4f97970a1cce9773a5e34c03f511b839db742198a1c571e292c54aa799e9afb991cc8a560529b8cdf3e0c344bc6c282aff2f68eec59361ddf@52.232.7.187:30439",
"enode://48cb0d430c328974226aa33a931d8446cd5a8d40f3ead8f4ce7ad60faa1278192eb6d58bed91258d63e81f255fc107eec2425ce2ae8b22350dd556076e160610@52.232.7.187:30440",
"enode://3fadb7af7f770d5ffc6b073b8d42834bebb18ce1fe8a4fe270d2b799e7051327093960dc61d9a18870db288f7746a0e6ea2a013cd6ab0e5f97ca08199473aace@52.232.7.187:30441",
"enode://a5d7168024c9992769cf380ffa559a64b4f39a29d468f579559863814eb0ae0ed689ac0871a3a2b4c78b03297485ec322d578281131ef5d5c09a4beb6200a97a@52.232.7.187:30442",
"enode://9c57744c5b2c2d71abcbe80512652f9234d4ab041b768a2a886ab390fe6f184860f40e113290698652d7e20a8ac74d27ac8671db23eb475b6c5e6253e4693bf8@52.232.7.187:30443",
"enode://daca9ff0c3176045a0e0ed228dee00ec86bc0939b135dc6b1caa23745d20fd0332e1ee74ad04020e89df56c7146d831a91b89d15ca3df05ba7618769fefab376@52.232.7.187:30444",
"enode://a3f6af59428cb4b9acb198db15ef5554fa43c2b0c18e468a269722d64a27218963a2975eaf82750b6262e42192b5e3669ea51337b4cda62b33987981bc5e0c1a@52.232.7.187:30445",
"enode://fe571422fa4651c3354c85dac61911a6a6520dd3c0332967a49d4133ca30e16a8a4946fa73ca2cb5de77917ea701a905e1c3015b2f4defcd53132b61cc84127a@52.232.7.187:30446",
// Mainframe
"enode://ee9a5a571ea6c8a59f9a8bb2c569c865e922b41c91d09b942e8c1d4dd2e1725bd2c26149da14de1f6321a2c6fdf1e07c503c3e093fb61696daebf74d6acd916b@54.186.219.160:30399",
"enode://a03f0562ecb8a992ad5242345535e73483cdc18ab934d36bf24b567d43447c2cea68f89f1d51d504dd13acc30f24ebce5a150bea2ccb1b722122ce4271dc199d@52.67.248.147:30399",
"enode://e2cbf9eafd85903d3b1c56743035284320695e0072bc8d7396e0542aa5e1c321b236f67eab66b79c2f15d4447fa4bbe74dd67d0467da23e7eb829f60ec8a812b@13.58.169.1:30399",
"enode://8b8c6bda6047f1cad9fab2db4d3d02b7aa26279902c32879f7bcd4a7d189fee77fdc36ee151ce6b84279b4792e72578fd529d2274d014132465758fbfee51cee@13.209.13.15:30399",
"enode://63f6a8818927e429585287cf2ca0cb9b11fa990b7b9b331c2962cdc6f21807a2473b26e8256225c26caff70d7218e59586d704d49061452c6852e382c885d03c@35.154.106.174:30399",
"enode://ed4bd3b794ed73f18e6dcc70c6624dfec63b5654f6ab54e8f40b16eff8afbd342d4230e099ddea40e84423f81b2d2ea79799dc345257b1fec6f6c422c9d008f7@52.213.20.99:30399",
}

View file

@ -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{
@ -70,6 +68,7 @@ const (
SWARM_ENV_SWAP_API = "SWARM_SWAP_API" SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE" SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE"
SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY" SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY"
SWARM_ENV_LIGHT_NODE_ENABLE = "SWARM_LIGHT_NODE_ENABLE"
SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK" SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK"
SWARM_ENV_ENS_API = "SWARM_ENS_API" SWARM_ENV_ENS_API = "SWARM_ENS_API"
SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR" SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR"
@ -79,6 +78,7 @@ const (
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH" SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY" SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY" SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
GETH_ENV_DATADIR = "GETH_DATADIR" GETH_ENV_DATADIR = "GETH_DATADIR"
) )
@ -133,7 +133,7 @@ func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context) {
log.Debug(printConfig(config)) log.Debug(printConfig(config))
} }
//override the current config with whatever is in the config file, if a config file has been provided //configFileOverride overrides the current config with the config file, if a config file has been provided
func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) { func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) {
var err error var err error
@ -143,7 +143,8 @@ func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config
if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" { if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" {
utils.Fatalf("Config file flag provided with invalid file path") utils.Fatalf("Config file flag provided with invalid file path")
} }
f, err := os.Open(filepath) var f *os.File
f, err = os.Open(filepath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -206,6 +207,10 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
currentConfig.SyncUpdateDelay = d currentConfig.SyncUpdateDelay = d
} }
if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) {
currentConfig.LightNodeEnabled = true
}
if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) { if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) {
currentConfig.DeliverySkipCheck = true currentConfig.DeliverySkipCheck = true
} }
@ -228,10 +233,6 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
currentConfig.Cors = cors currentConfig.Cors = cors
} }
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name)
}
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" { if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
currentConfig.LocalStoreParams.ChunkDbPath = storePath currentConfig.LocalStoreParams.ChunkDbPath = storePath
} }
@ -303,6 +304,12 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
} }
} }
if lne := os.Getenv(SWARM_ENV_LIGHT_NODE_ENABLE); lne != "" {
if lightnode, err := strconv.ParseBool(lne); err != nil {
currentConfig.LightNodeEnabled = lightnode
}
}
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" { if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
currentConfig.SwapAPI = swapapi currentConfig.SwapAPI = swapapi
} }
@ -323,10 +330,6 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
currentConfig.Cors = cors currentConfig.Cors = cors
} }
if bootnodes := os.Getenv(SWARM_ENV_BOOTNODES); bootnodes != "" {
currentConfig.BootNodes = bootnodes
}
return currentConfig return currentConfig
} }

View file

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
@ -559,3 +560,16 @@ func TestValidateConfig(t *testing.T) {
} }
} }
} }
func assignTCPPort() (string, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", err
}
l.Close()
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
return "", err
}
return port, nil
}

View file

@ -68,18 +68,36 @@ func download(ctx *cli.Context) {
utils.Fatalf("could not parse uri argument: %v", err) utils.Fatalf("could not parse uri argument: %v", err)
} }
dl := func(credentials string) error {
// assume behaviour according to --recursive switch // assume behaviour according to --recursive switch
if isRecursive { if isRecursive {
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest); err != nil { if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil {
utils.Fatalf("encoutered an error while downloading directory: %v", err) if err == swarm.ErrUnauthorized {
return err
}
return fmt.Errorf("directory %s: %v", uri.Path, err)
} }
} else { } else {
// we are downloading a file // we are downloading a file
log.Debug(fmt.Sprintf("downloading file/path from a manifest. hash: %s, path:%s", uri.Addr, uri.Path)) log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path)
err := client.DownloadFile(uri.Addr, uri.Path, dest) err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials)
if err != nil { if err != nil {
utils.Fatalf("could not download %s from given address: %s. error: %v", uri.Path, uri.Addr, err) if err == swarm.ErrUnauthorized {
return err
}
return fmt.Errorf("file %s from address: %s: %v", uri.Path, uri.Addr, err)
} }
} }
return nil
}
if passwords := makePasswordList(ctx); passwords != nil {
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
err = dl(password)
} else {
err = dl("")
}
if err != nil {
utils.Fatalf("download: %v", err)
}
} }

View file

@ -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")

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// +build linux darwin freebsd // +build linux freebsd
package main package main
@ -43,6 +43,11 @@ type testFile struct {
} }
// TestCLISwarmFs is a high-level test of swarmfs // TestCLISwarmFs is a high-level test of swarmfs
//
// This test fails on travis for macOS as this executable exits with code 1
// and without any log messages in the log:
// /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse.
// This is the reason for this file not being built on darwin architecture.
func TestCLISwarmFs(t *testing.T) { func TestCLISwarmFs(t *testing.T) {
cluster := newTestCluster(t, 3) cluster := newTestCluster(t, 3)
defer cluster.Shutdown() defer cluster.Shutdown()

View file

@ -44,7 +44,7 @@ func list(ctx *cli.Context) {
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client := swarm.NewClient(bzzapi) client := swarm.NewClient(bzzapi)
list, err := client.List(manifest, prefix) list, err := client.List(manifest, prefix, "")
if err != nil { if err != nil {
utils.Fatalf("Failed to generate file and directory list: %s", err) utils.Fatalf("Failed to generate file and directory list: %s", err)
} }

View file

@ -37,13 +37,12 @@ import (
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/log" "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/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"
) )
@ -68,13 +67,6 @@ OPTIONS:
var ( var (
gitCommit string // Git SHA1 commit hash of the release (set via linker flags) gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
testbetBootNodes = []string{
"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
}
) )
var ( var (
@ -123,6 +115,11 @@ var (
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)", Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY, EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY,
} }
SwarmLightNodeEnabled = cli.BoolFlag{
Name: "lightnode",
Usage: "Enable Swarm LightNode (default false)",
EnvVar: SWARM_ENV_LIGHT_NODE_ENABLE,
}
SwarmDeliverySkipCheckFlag = cli.BoolFlag{ SwarmDeliverySkipCheckFlag = cli.BoolFlag{
Name: "delivery-skip-check", Name: "delivery-skip-check",
Usage: "Skip chunk delivery check (default false)", Usage: "Skip chunk delivery check (default false)",
@ -150,6 +147,14 @@ var (
Name: "defaultpath", Name: "defaultpath",
Usage: "path to file served for empty url path (none)", Usage: "path to file served for empty url path (none)",
} }
SwarmAccessGrantKeyFlag = cli.StringFlag{
Name: "grant-key",
Usage: "grants a given public key access to an ACT",
}
SwarmAccessGrantKeysFlag = cli.StringFlag{
Name: "grant-keys",
Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
}
SwarmUpFromStdinFlag = cli.BoolFlag{ SwarmUpFromStdinFlag = cli.BoolFlag{
Name: "stdin", Name: "stdin",
Usage: "reads data to be uploaded from stdin", Usage: "reads data to be uploaded from stdin",
@ -162,6 +167,15 @@ var (
Name: "encrypt", Name: "encrypt",
Usage: "use encrypted upload", Usage: "use encrypted upload",
} }
SwarmAccessPasswordFlag = cli.StringFlag{
Name: "password",
Usage: "Password",
EnvVar: SWARM_ACCESS_PASSWORD,
}
SwarmDryRunFlag = cli.BoolFlag{
Name: "dry-run",
Usage: "dry-run",
}
CorsStringFlag = cli.StringFlag{ CorsStringFlag = cli.StringFlag{
Name: "corsdomain", Name: "corsdomain",
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')", Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
@ -216,7 +230,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.
@ -247,6 +261,61 @@ func init() {
Flags: []cli.Flag{SwarmEncryptedFlag}, Flags: []cli.Flag{SwarmEncryptedFlag},
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash", Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
}, },
{
CustomHelpTemplate: helpTemplate,
Name: "access",
Usage: "encrypts a reference and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root manifest",
Subcommands: []cli.Command{
{
CustomHelpTemplate: helpTemplate,
Name: "new",
Usage: "encrypts a reference and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
Subcommands: []cli.Command{
{
Action: accessNewPass,
CustomHelpTemplate: helpTemplate,
Flags: []cli.Flag{
utils.PasswordFileFlag,
SwarmDryRunFlag,
},
Name: "pass",
Usage: "encrypts a reference with a password and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
},
{
Action: accessNewPK,
CustomHelpTemplate: helpTemplate,
Flags: []cli.Flag{
utils.PasswordFileFlag,
SwarmDryRunFlag,
SwarmAccessGrantKeyFlag,
},
Name: "pk",
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
},
{
Action: accessNewACT,
CustomHelpTemplate: helpTemplate,
Flags: []cli.Flag{
SwarmAccessGrantKeysFlag,
SwarmDryRunFlag,
},
Name: "act",
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
ArgsUsage: "<ref>",
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
},
},
},
},
},
{ {
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
Name: "resource", Name: "resource",
@ -301,14 +370,11 @@ func init() {
{ {
Action: download, Action: download,
Name: "down", Name: "down",
Flags: []cli.Flag{SwarmRecursiveFlag}, Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
Usage: "downloads a swarm manifest or a file inside a manifest", Usage: "downloads a swarm manifest or a file inside a manifest",
ArgsUsage: " <uri> [<dir>]", ArgsUsage: " <uri> [<dir>]",
Description: ` Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.
`,
}, },
{ {
Name: "manifest", Name: "manifest",
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
@ -317,23 +383,23 @@ Downloads a swarm bzz uri to the given dir. When no dir is provided, working dir
Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove", Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Action: add, Action: manifestAdd,
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
Name: "add", Name: "add",
Usage: "add a new path to the manifest", Usage: "add a new path to the manifest",
ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]", ArgsUsage: "<MANIFEST> <path> <hash>",
Description: "Adds a new path to the manifest", Description: "Adds a new path to the manifest",
}, },
{ {
Action: update, Action: manifestUpdate,
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
Name: "update", Name: "update",
Usage: "update the hash for an already existing path in the manifest", Usage: "update the hash for an already existing path in the manifest",
ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]", ArgsUsage: "<MANIFEST> <path> <newhash>",
Description: "Update the hash for an already existing path in the manifest", Description: "Update the hash for an already existing path in the manifest",
}, },
{ {
Action: remove, Action: manifestRemove,
CustomHelpTemplate: helpTemplate, CustomHelpTemplate: helpTemplate,
Name: "remove", Name: "remove",
Usage: "removes a path from the manifest", Usage: "removes a path from the manifest",
@ -408,16 +474,14 @@ pv(1) tool to get a progress bar:
Name: "import", Name: "import",
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)", Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
ArgsUsage: "<chunkdb> <file>", ArgsUsage: "<chunkdb> <file>",
Description: ` Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
Import chunks from a tar archive into a local chunk database (use - to read from stdin).
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
The import may be quite large, consider piping the input through the Unix The import may be quite large, consider piping the input through the Unix
pv(1) tool to get a progress bar: pv(1) tool to get a progress bar:
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks - pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
`,
}, },
{ {
Action: dbClean, Action: dbClean,
@ -464,6 +528,7 @@ pv(1) tool to get a progress bar:
SwarmSwapAPIFlag, SwarmSwapAPIFlag,
SwarmSyncDisabledFlag, SwarmSyncDisabledFlag,
SwarmSyncUpdateDelay, SwarmSyncUpdateDelay,
SwarmLightNodeEnabled,
SwarmDeliverySkipCheckFlag, SwarmDeliverySkipCheckFlag,
SwarmListenAddrFlag, SwarmListenAddrFlag,
SwarmPortFlag, SwarmPortFlag,
@ -516,7 +581,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)
} }
@ -528,6 +594,7 @@ func version(ctx *cli.Context) error {
func bzzd(ctx *cli.Context) error { func bzzd(ctx *cli.Context) error {
//build a valid bzzapi.Config from all available sources: //build a valid bzzapi.Config from all available sources:
//default config, file config, command line and env vars //default config, file config, command line and env vars
bzzconfig, err := buildConfig(ctx) bzzconfig, err := buildConfig(ctx)
if err != nil { if err != nil {
utils.Fatalf("unable to configure swarm: %v", err) utils.Fatalf("unable to configure swarm: %v", err)
@ -544,12 +611,16 @@ func bzzd(ctx *cli.Context) error {
if _, err := os.Stat(bzzconfig.Path); err == nil { if _, err := os.Stat(bzzconfig.Path); err == nil {
cfg.DataDir = bzzconfig.Path cfg.DataDir = bzzconfig.Path
} }
//optionally set the bootnodes before configuring the node
setSwarmBootstrapNodes(ctx, &cfg)
//setup the ethereum node //setup the ethereum node
utils.SetNodeConfig(ctx, &cfg) utils.SetNodeConfig(ctx, &cfg)
stack, err := node.New(&cfg) stack, err := node.New(&cfg)
if err != nil { if err != nil {
utils.Fatalf("can't create node: %v", err) utils.Fatalf("can't create node: %v", err)
} }
//a few steps need to be done after the config phase is completed, //a few steps need to be done after the config phase is completed,
//due to overriding behavior //due to overriding behavior
initSwarmNode(bzzconfig, stack, ctx) initSwarmNode(bzzconfig, stack, ctx)
@ -567,16 +638,6 @@ func bzzd(ctx *cli.Context) error {
stack.Stop() stack.Stop()
}() }()
// Add bootnodes as initial peers.
if bzzconfig.BootNodes != "" {
bootnodes := strings.Split(bzzconfig.BootNodes, ",")
injectBootnodes(stack.Server(), bootnodes)
} else {
if bzzconfig.NetworkID == 3 {
injectBootnodes(stack.Server(), testbetBootNodes)
}
}
stack.Wait() stack.Wait()
return nil return nil
} }
@ -684,17 +745,6 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
return password return password
} }
func injectBootnodes(srv *p2p.Server, nodes []string) {
for _, url := range nodes {
n, err := discover.ParseNode(url)
if err != nil {
log.Error("Invalid swarm bootnode", "err", err)
continue
}
srv.AddPeer(n)
}
}
// addDefaultHelpSubcommand scans through defined CLI commands and adds // addDefaultHelpSubcommand scans through defined CLI commands and adds
// a basic help subcommand to each // a basic help subcommand to each
// if a help command is already defined, it will take precedence over the default. // if a help command is already defined, it will take precedence over the default.
@ -707,3 +757,20 @@ func addDefaultHelpSubcommands(commands []cli.Command) {
} }
} }
} }
func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) || ctx.GlobalIsSet(utils.BootnodesV4Flag.Name) {
return
}
cfg.P2P.BootstrapNodes = []*discover.Node{}
for _, url := range SwarmBootnodes {
node, err := discover.ParseNode(url)
if err != nil {
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
}
cfg.P2P.BootstrapNodes = append(cfg.P2P.BootstrapNodes, node)
}
log.Debug("added default swarm bootnodes", "length", len(cfg.P2P.BootstrapNodes))
}

View file

@ -18,10 +18,8 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"mime" "os"
"path/filepath"
"strings" "strings"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
@ -30,127 +28,118 @@ import (
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
const bzzManifestJSON = "application/bzz-manifest+json" // manifestAdd adds a new entry to the manifest at the given path.
// New entry hash, the last argument, must be the hash of a manifest
func add(ctx *cli.Context) { // with only one entry, which meta-data will be added to the original manifest.
// On success, this function will print new (updated) manifest's hash.
func manifestAdd(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
if len(args) < 3 { if len(args) != 3 {
utils.Fatalf("Need at least three arguments <MHASH> <path> <HASH> [<content-type>]") utils.Fatalf("Need exactly three arguments <MHASH> <path> <HASH>")
} }
var ( var (
mhash = args[0] mhash = args[0]
path = args[1] path = args[1]
hash = args[2] hash = args[2]
ctype string
wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
mroot api.Manifest
) )
if len(args) > 3 { bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
ctype = args[3] client := swarm.NewClient(bzzapi)
} else {
ctype = mime.TypeByExtension(filepath.Ext(path)) m, _, err := client.DownloadManifest(hash)
if err != nil {
utils.Fatalf("Error downloading manifest to add: %v", err)
}
l := len(m.Entries)
if l == 0 {
utils.Fatalf("No entries in manifest %s", hash)
} else if l > 1 {
utils.Fatalf("Too many entries in manifest %s", hash)
} }
newManifest := addEntryToManifest(ctx, mhash, path, hash, ctype) newManifest := addEntryToManifest(client, mhash, path, m.Entries[0])
fmt.Println(newManifest) fmt.Println(newManifest)
if !wantManifest {
// Print the manifest. This is the only output to stdout.
mrootJSON, _ := json.MarshalIndent(mroot, "", " ")
fmt.Println(string(mrootJSON))
return
}
} }
func update(ctx *cli.Context) { // manifestUpdate replaces an existing entry of the manifest at the given path.
// New entry hash, the last argument, must be the hash of a manifest
// with only one entry, which meta-data will be added to the original manifest.
// On success, this function will print hash of the updated manifest.
func manifestUpdate(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
if len(args) < 3 { if len(args) != 3 {
utils.Fatalf("Need at least three arguments <MHASH> <path> <HASH>") utils.Fatalf("Need exactly three arguments <MHASH> <path> <HASH>")
} }
var ( var (
mhash = args[0] mhash = args[0]
path = args[1] path = args[1]
hash = args[2] hash = args[2]
ctype string
wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
mroot api.Manifest
) )
if len(args) > 3 {
ctype = args[3] bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
} else { client := swarm.NewClient(bzzapi)
ctype = mime.TypeByExtension(filepath.Ext(path))
m, _, err := client.DownloadManifest(hash)
if err != nil {
utils.Fatalf("Error downloading manifest to update: %v", err)
}
l := len(m.Entries)
if l == 0 {
utils.Fatalf("No entries in manifest %s", hash)
} else if l > 1 {
utils.Fatalf("Too many entries in manifest %s", hash)
} }
newManifest := updateEntryInManifest(ctx, mhash, path, hash, ctype) newManifest, _, defaultEntryUpdated := updateEntryInManifest(client, mhash, path, m.Entries[0], true)
if defaultEntryUpdated {
// Print informational message to stderr
// allowing the user to get the new manifest hash from stdout
// without the need to parse the complete output.
fmt.Fprintln(os.Stderr, "Manifest default entry is updated, too")
}
fmt.Println(newManifest) fmt.Println(newManifest)
if !wantManifest {
// Print the manifest. This is the only output to stdout.
mrootJSON, _ := json.MarshalIndent(mroot, "", " ")
fmt.Println(string(mrootJSON))
return
}
} }
func remove(ctx *cli.Context) { // manifestRemove removes an existing entry of the manifest at the given path.
// On success, this function will print hash of the manifest which does not
// contain the path.
func manifestRemove(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
if len(args) < 2 { if len(args) != 2 {
utils.Fatalf("Need at least two arguments <MHASH> <path>") utils.Fatalf("Need exactly two arguments <MHASH> <path>")
} }
var ( var (
mhash = args[0] mhash = args[0]
path = args[1] path = args[1]
wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
mroot api.Manifest
) )
newManifest := removeEntryFromManifest(ctx, mhash, path) bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client := swarm.NewClient(bzzapi)
newManifest := removeEntryFromManifest(client, mhash, path)
fmt.Println(newManifest) fmt.Println(newManifest)
if !wantManifest {
// Print the manifest. This is the only output to stdout.
mrootJSON, _ := json.MarshalIndent(mroot, "", " ")
fmt.Println(string(mrootJSON))
return
}
} }
func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) string { func addEntryToManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry) string {
var longestPathEntry = api.ManifestEntry{}
var (
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client = swarm.NewClient(bzzapi)
longestPathEntry = api.ManifestEntry{}
)
mroot, isEncrypted, err := client.DownloadManifest(mhash) mroot, isEncrypted, err := client.DownloadManifest(mhash)
if err != nil { if err != nil {
utils.Fatalf("Manifest download failed: %v", err) utils.Fatalf("Manifest download failed: %v", err)
} }
//TODO: check if the "hash" to add is valid and present in swarm
_, _, err = client.DownloadManifest(hash)
if err != nil {
utils.Fatalf("Hash to add is not present: %v", err)
}
// See if we path is in this Manifest or do we have to dig deeper // See if we path is in this Manifest or do we have to dig deeper
for _, entry := range mroot.Entries { for _, e := range mroot.Entries {
if path == entry.Path { if path == e.Path {
utils.Fatalf("Path %s already present, not adding anything", path) utils.Fatalf("Path %s already present, not adding anything", path)
} else { } else {
if entry.ContentType == bzzManifestJSON { if e.ContentType == api.ManifestType {
prfxlen := strings.HasPrefix(path, entry.Path) prfxlen := strings.HasPrefix(path, e.Path)
if prfxlen && len(path) > len(longestPathEntry.Path) { if prfxlen && len(path) > len(longestPathEntry.Path) {
longestPathEntry = entry longestPathEntry = e
} }
} }
} }
@ -159,25 +148,21 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
if longestPathEntry.Path != "" { if longestPathEntry.Path != "" {
// Load the child Manifest add the entry there // Load the child Manifest add the entry there
newPath := path[len(longestPathEntry.Path):] newPath := path[len(longestPathEntry.Path):]
newHash := addEntryToManifest(ctx, longestPathEntry.Hash, newPath, hash, ctype) newHash := addEntryToManifest(client, longestPathEntry.Hash, newPath, entry)
// Replace the hash for parent Manifests // Replace the hash for parent Manifests
newMRoot := &api.Manifest{} newMRoot := &api.Manifest{}
for _, entry := range mroot.Entries { for _, e := range mroot.Entries {
if longestPathEntry.Path == entry.Path { if longestPathEntry.Path == e.Path {
entry.Hash = newHash e.Hash = newHash
} }
newMRoot.Entries = append(newMRoot.Entries, entry) newMRoot.Entries = append(newMRoot.Entries, e)
} }
mroot = newMRoot mroot = newMRoot
} else { } else {
// Add the entry in the leaf Manifest // Add the entry in the leaf Manifest
newEntry := api.ManifestEntry{ entry.Path = path
Hash: hash, mroot.Entries = append(mroot.Entries, entry)
Path: path,
ContentType: ctype,
}
mroot.Entries = append(mroot.Entries, newEntry)
} }
newManifestHash, err := client.UploadManifest(mroot, isEncrypted) newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
@ -185,14 +170,16 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
utils.Fatalf("Manifest upload failed: %v", err) utils.Fatalf("Manifest upload failed: %v", err)
} }
return newManifestHash return newManifestHash
} }
func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) string { // updateEntryInManifest updates an existing entry o path with a new one in the manifest with provided mhash
// finding the path recursively through all nested manifests. Argument isRoot is used for default
// entry update detection. If the updated entry has the same hash as the default entry, then the
// default entry in root manifest will be updated too.
// Returned values are the new manifest hash, hash of the entry that was replaced by the new entry and
// a a bool that is true if default entry is updated.
func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry, isRoot bool) (newManifestHash, oldHash string, defaultEntryUpdated bool) {
var ( var (
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client = swarm.NewClient(bzzapi)
newEntry = api.ManifestEntry{} newEntry = api.ManifestEntry{}
longestPathEntry = api.ManifestEntry{} longestPathEntry = api.ManifestEntry{}
) )
@ -202,17 +189,18 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
utils.Fatalf("Manifest download failed: %v", err) utils.Fatalf("Manifest download failed: %v", err)
} }
//TODO: check if the "hash" with which to update is valid and present in swarm
// See if we path is in this Manifest or do we have to dig deeper // See if we path is in this Manifest or do we have to dig deeper
for _, entry := range mroot.Entries { for _, e := range mroot.Entries {
if path == entry.Path { if path == e.Path {
newEntry = entry newEntry = e
// keep the reference of the hash of the entry that should be replaced
// for default entry detection
oldHash = e.Hash
} else { } else {
if entry.ContentType == bzzManifestJSON { if e.ContentType == api.ManifestType {
prfxlen := strings.HasPrefix(path, entry.Path) prfxlen := strings.HasPrefix(path, e.Path)
if prfxlen && len(path) > len(longestPathEntry.Path) { if prfxlen && len(path) > len(longestPathEntry.Path) {
longestPathEntry = entry longestPathEntry = e
} }
} }
} }
@ -225,50 +213,50 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
if longestPathEntry.Path != "" { if longestPathEntry.Path != "" {
// Load the child Manifest add the entry there // Load the child Manifest add the entry there
newPath := path[len(longestPathEntry.Path):] newPath := path[len(longestPathEntry.Path):]
newHash := updateEntryInManifest(ctx, longestPathEntry.Hash, newPath, hash, ctype) var newHash string
newHash, oldHash, _ = updateEntryInManifest(client, longestPathEntry.Hash, newPath, entry, false)
// Replace the hash for parent Manifests // Replace the hash for parent Manifests
newMRoot := &api.Manifest{} newMRoot := &api.Manifest{}
for _, entry := range mroot.Entries { for _, e := range mroot.Entries {
if longestPathEntry.Path == entry.Path { if longestPathEntry.Path == e.Path {
entry.Hash = newHash e.Hash = newHash
} }
newMRoot.Entries = append(newMRoot.Entries, entry) newMRoot.Entries = append(newMRoot.Entries, e)
} }
mroot = newMRoot mroot = newMRoot
} }
if newEntry.Path != "" { // update the manifest if the new entry is found and
// check if default entry should be updated
if newEntry.Path != "" || isRoot {
// Replace the hash for leaf Manifest // Replace the hash for leaf Manifest
newMRoot := &api.Manifest{} newMRoot := &api.Manifest{}
for _, entry := range mroot.Entries { for _, e := range mroot.Entries {
if newEntry.Path == entry.Path { if newEntry.Path == e.Path {
myEntry := api.ManifestEntry{ entry.Path = e.Path
Hash: hash,
Path: entry.Path,
ContentType: ctype,
}
newMRoot.Entries = append(newMRoot.Entries, myEntry)
} else {
newMRoot.Entries = append(newMRoot.Entries, entry) newMRoot.Entries = append(newMRoot.Entries, entry)
} else if isRoot && e.Path == "" && e.Hash == oldHash {
entry.Path = e.Path
newMRoot.Entries = append(newMRoot.Entries, entry)
defaultEntryUpdated = true
} else {
newMRoot.Entries = append(newMRoot.Entries, e)
} }
} }
mroot = newMRoot mroot = newMRoot
} }
newManifestHash, err := client.UploadManifest(mroot, isEncrypted) newManifestHash, err = client.UploadManifest(mroot, isEncrypted)
if err != nil { if err != nil {
utils.Fatalf("Manifest upload failed: %v", err) utils.Fatalf("Manifest upload failed: %v", err)
} }
return newManifestHash return newManifestHash, oldHash, defaultEntryUpdated
} }
func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string { func removeEntryFromManifest(client *swarm.Client, mhash, path string) string {
var ( var (
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
client = swarm.NewClient(bzzapi)
entryToRemove = api.ManifestEntry{} entryToRemove = api.ManifestEntry{}
longestPathEntry = api.ManifestEntry{} longestPathEntry = api.ManifestEntry{}
) )
@ -283,7 +271,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
if path == entry.Path { if path == entry.Path {
entryToRemove = entry entryToRemove = entry
} else { } else {
if entry.ContentType == bzzManifestJSON { if entry.ContentType == api.ManifestType {
prfxlen := strings.HasPrefix(path, entry.Path) prfxlen := strings.HasPrefix(path, entry.Path)
if prfxlen && len(path) > len(longestPathEntry.Path) { if prfxlen && len(path) > len(longestPathEntry.Path) {
longestPathEntry = entry longestPathEntry = entry
@ -299,7 +287,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
if longestPathEntry.Path != "" { if longestPathEntry.Path != "" {
// Load the child Manifest remove the entry there // Load the child Manifest remove the entry there
newPath := path[len(longestPathEntry.Path):] newPath := path[len(longestPathEntry.Path):]
newHash := removeEntryFromManifest(ctx, longestPathEntry.Hash, newPath) newHash := removeEntryFromManifest(client, longestPathEntry.Hash, newPath)
// Replace the hash for parent Manifests // Replace the hash for parent Manifests
newMRoot := &api.Manifest{} newMRoot := &api.Manifest{}

579
cmd/swarm/manifest_test.go Normal file
View file

@ -0,0 +1,579 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/swarm/api"
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
)
// TestManifestChange tests manifest add, update and remove
// cli commands without encryption.
func TestManifestChange(t *testing.T) {
testManifestChange(t, false)
}
// TestManifestChange tests manifest add, update and remove
// cli commands with encryption enabled.
func TestManifestChangeEncrypted(t *testing.T) {
testManifestChange(t, true)
}
// testManifestChange performs cli commands:
// - manifest add
// - manifest update
// - manifest remove
// on a manifest, testing the functionality of this
// comands on paths that are in root manifest or a nested one.
// Argument encrypt controls whether to use encryption or not.
func testManifestChange(t *testing.T, encrypt bool) {
t.Parallel()
cluster := newTestCluster(t, 1)
defer cluster.Shutdown()
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
origDir := filepath.Join(tmp, "orig")
if err := os.Mkdir(origDir, 0777); err != nil {
t.Fatal(err)
}
indexDataFilename := filepath.Join(origDir, "index.html")
err = ioutil.WriteFile(indexDataFilename, []byte("<h1>Test</h1>"), 0666)
if err != nil {
t.Fatal(err)
}
// Files paths robots.txt and robots.html share the same prefix "robots."
// which will result a manifest with a nested manifest under path "robots.".
// This will allow testing manifest changes on both root and nested manifest.
err = ioutil.WriteFile(filepath.Join(origDir, "robots.txt"), []byte("Disallow: /"), 0666)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(filepath.Join(origDir, "robots.html"), []byte("<strong>No Robots Allowed</strong>"), 0666)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(filepath.Join(origDir, "mutants.txt"), []byte("Frank\nMarcus"), 0666)
if err != nil {
t.Fatal(err)
}
args := []string{
"--bzzapi",
cluster.Nodes[0].URL,
"--recursive",
"--defaultpath",
indexDataFilename,
"up",
origDir,
}
if encrypt {
args = append(args, "--encrypt")
}
origManifestHash := runSwarmExpectHash(t, args...)
checkHashLength(t, origManifestHash, encrypt)
client := swarm.NewClient(cluster.Nodes[0].URL)
// upload a new file and use its manifest to add it the original manifest.
t.Run("add", func(t *testing.T) {
humansData := []byte("Ann\nBob")
humansDataFilename := filepath.Join(tmp, "humans.txt")
err = ioutil.WriteFile(humansDataFilename, humansData, 0666)
if err != nil {
t.Fatal(err)
}
humansManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
humansDataFilename,
)
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"add",
origManifestHash,
"humans.txt",
humansManifestHash,
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
for _, e := range newManifest.Entries {
if e.Path == "humans.txt" {
found = true
if e.Size != int64(len(humansData)) {
t.Errorf("expected humans.txt size %v, got %v", len(humansData), e.Size)
}
if e.ModTime.IsZero() {
t.Errorf("got zero mod time for humans.txt")
}
ct := "text/plain; charset=utf-8"
if e.ContentType != ct {
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
}
break
}
}
if !found {
t.Fatal("no humans.txt in new manifest")
}
checkFile(t, client, newManifestHash, "humans.txt", humansData)
})
// upload a new file and use its manifest to add it the original manifest,
// but ensure that the file will be in the nested manifest of the original one.
t.Run("add nested", func(t *testing.T) {
robotsData := []byte(`{"disallow": "/"}`)
robotsDataFilename := filepath.Join(tmp, "robots.json")
err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
if err != nil {
t.Fatal(err)
}
robotsManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
robotsDataFilename,
)
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"add",
origManifestHash,
"robots.json",
robotsManifestHash,
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
loop:
for _, e := range newManifest.Entries {
if e.Path == "robots." {
nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
for _, e := range nestedManifest.Entries {
if e.Path == "json" {
found = true
if e.Size != int64(len(robotsData)) {
t.Errorf("expected robots.json size %v, got %v", len(robotsData), e.Size)
}
if e.ModTime.IsZero() {
t.Errorf("got zero mod time for robots.json")
}
ct := "application/json"
if e.ContentType != ct {
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
}
break loop
}
}
}
}
if !found {
t.Fatal("no robots.json in new manifest")
}
checkFile(t, client, newManifestHash, "robots.json", robotsData)
})
// upload a new file and use its manifest to change the file it the original manifest.
t.Run("update", func(t *testing.T) {
indexData := []byte("<h1>Ethereum Swarm</h1>")
indexDataFilename := filepath.Join(tmp, "index.html")
err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
if err != nil {
t.Fatal(err)
}
indexManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
indexDataFilename,
)
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"update",
origManifestHash,
"index.html",
indexManifestHash,
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
for _, e := range newManifest.Entries {
if e.Path == "index.html" {
found = true
if e.Size != int64(len(indexData)) {
t.Errorf("expected index.html size %v, got %v", len(indexData), e.Size)
}
if e.ModTime.IsZero() {
t.Errorf("got zero mod time for index.html")
}
ct := "text/html; charset=utf-8"
if e.ContentType != ct {
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
}
break
}
}
if !found {
t.Fatal("no index.html in new manifest")
}
checkFile(t, client, newManifestHash, "index.html", indexData)
// check default entry change
checkFile(t, client, newManifestHash, "", indexData)
})
// upload a new file and use its manifest to change the file it the original manifest,
// but ensure that the file is in the nested manifest of the original one.
t.Run("update nested", func(t *testing.T) {
robotsData := []byte(`<string>Only humans allowed!!!</strong>`)
robotsDataFilename := filepath.Join(tmp, "robots.html")
err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
if err != nil {
t.Fatal(err)
}
humansManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
robotsDataFilename,
)
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"update",
origManifestHash,
"robots.html",
humansManifestHash,
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
loop:
for _, e := range newManifest.Entries {
if e.Path == "robots." {
nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
for _, e := range nestedManifest.Entries {
if e.Path == "html" {
found = true
if e.Size != int64(len(robotsData)) {
t.Errorf("expected robots.html size %v, got %v", len(robotsData), e.Size)
}
if e.ModTime.IsZero() {
t.Errorf("got zero mod time for robots.html")
}
ct := "text/html; charset=utf-8"
if e.ContentType != ct {
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
}
break loop
}
}
}
}
if !found {
t.Fatal("no robots.html in new manifest")
}
checkFile(t, client, newManifestHash, "robots.html", robotsData)
})
// remove a file from the manifest.
t.Run("remove", func(t *testing.T) {
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"remove",
origManifestHash,
"mutants.txt",
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
for _, e := range newManifest.Entries {
if e.Path == "mutants.txt" {
found = true
break
}
}
if found {
t.Fatal("mutants.txt is not removed")
}
})
// remove a file from the manifest, but ensure that the file is in
// the nested manifest of the original one.
t.Run("remove nested", func(t *testing.T) {
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"remove",
origManifestHash,
"robots.html",
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
loop:
for _, e := range newManifest.Entries {
if e.Path == "robots." {
nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
for _, e := range nestedManifest.Entries {
if e.Path == "html" {
found = true
break loop
}
}
}
}
if found {
t.Fatal("robots.html in not removed")
}
})
}
// TestNestedDefaultEntryUpdate tests if the default entry is updated
// if the file in nested manifest used for it is also updated.
func TestNestedDefaultEntryUpdate(t *testing.T) {
testNestedDefaultEntryUpdate(t, false)
}
// TestNestedDefaultEntryUpdateEncrypted tests if the default entry
// of encrypted upload is updated if the file in nested manifest
// used for it is also updated.
func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
testNestedDefaultEntryUpdate(t, true)
}
func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
t.Parallel()
cluster := newTestCluster(t, 1)
defer cluster.Shutdown()
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
origDir := filepath.Join(tmp, "orig")
if err := os.Mkdir(origDir, 0777); err != nil {
t.Fatal(err)
}
indexData := []byte("<h1>Test</h1>")
indexDataFilename := filepath.Join(origDir, "index.html")
err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
if err != nil {
t.Fatal(err)
}
// Add another file with common prefix as the default entry to test updates of
// default entry with nested manifests.
err = ioutil.WriteFile(filepath.Join(origDir, "index.txt"), []byte("Test"), 0666)
if err != nil {
t.Fatal(err)
}
args := []string{
"--bzzapi",
cluster.Nodes[0].URL,
"--recursive",
"--defaultpath",
indexDataFilename,
"up",
origDir,
}
if encrypt {
args = append(args, "--encrypt")
}
origManifestHash := runSwarmExpectHash(t, args...)
checkHashLength(t, origManifestHash, encrypt)
client := swarm.NewClient(cluster.Nodes[0].URL)
newIndexData := []byte("<h1>Ethereum Swarm</h1>")
newIndexDataFilename := filepath.Join(tmp, "index.html")
err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666)
if err != nil {
t.Fatal(err)
}
newIndexManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"up",
newIndexDataFilename,
)
newManifestHash := runSwarmExpectHash(t,
"--bzzapi",
cluster.Nodes[0].URL,
"manifest",
"update",
origManifestHash,
"index.html",
newIndexManifestHash,
)
checkHashLength(t, newManifestHash, encrypt)
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
var found bool
for _, e := range newManifest.Entries {
if e.Path == "index." {
found = true
newManifest = downloadManifest(t, client, e.Hash, encrypt)
break
}
}
if !found {
t.Fatal("no index. path in new manifest")
}
found = false
for _, e := range newManifest.Entries {
if e.Path == "html" {
found = true
if e.Size != int64(len(newIndexData)) {
t.Errorf("expected index.html size %v, got %v", len(newIndexData), e.Size)
}
if e.ModTime.IsZero() {
t.Errorf("got zero mod time for index.html")
}
ct := "text/html; charset=utf-8"
if e.ContentType != ct {
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
}
break
}
}
if !found {
t.Fatal("no html in new manifest")
}
checkFile(t, client, newManifestHash, "index.html", newIndexData)
// check default entry change
checkFile(t, client, newManifestHash, "", newIndexData)
}
func runSwarmExpectHash(t *testing.T, args ...string) (hash string) {
t.Helper()
hashRegexp := `[a-f\d]{64,128}`
up := runSwarm(t, args...)
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
if len(matches) < 1 {
t.Fatal("no matches found")
}
return matches[0]
}
func checkHashLength(t *testing.T, hash string, encrypted bool) {
t.Helper()
l := len(hash)
if encrypted && l != 128 {
t.Errorf("expected hash length 128, got %v", l)
}
if !encrypted && l != 64 {
t.Errorf("expected hash length 64, got %v", l)
}
}
func downloadManifest(t *testing.T, client *swarm.Client, hash string, encrypted bool) (manifest *api.Manifest) {
t.Helper()
m, isEncrypted, err := client.DownloadManifest(hash)
if err != nil {
t.Fatal(err)
}
if encrypted != isEncrypted {
t.Error("new manifest encryption flag is not correct")
}
return m
}
func checkFile(t *testing.T, client *swarm.Client, hash, path string, expected []byte) {
t.Helper()
f, err := client.Download(hash, path)
if err != nil {
t.Fatal(err)
}
got, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, expected) {
t.Errorf("expected file content %q, got %q", expected, got)
}
}

View file

@ -17,12 +17,17 @@
package main package main
import ( import (
"context"
"crypto/ecdsa"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"syscall"
"testing" "testing"
"time" "time"
@ -178,6 +183,7 @@ type testNode struct {
Enode string Enode string
Dir string Dir string
IpcPath string IpcPath string
PrivateKey *ecdsa.PrivateKey
Client *rpc.Client Client *rpc.Client
Cmd *cmdtest.TestCmd Cmd *cmdtest.TestCmd
} }
@ -218,14 +224,12 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
} }
// assign ports // assign ports
httpPort, err := assignTCPPort() ports, err := getAvailableTCPPorts(2)
if err != nil {
t.Fatal(err)
}
p2pPort, err := assignTCPPort()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p2pPort := ports[0]
httpPort := ports[1]
// start the node // start the node
node.Cmd = runSwarm(t, node.Cmd = runSwarm(t,
@ -246,6 +250,17 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// ensure that all ports have active listeners
// so that the next node will not get the same
// when calling getAvailableTCPPorts
err = waitTCPPorts(ctx, ports...)
if err != nil {
t.Fatal(err)
}
// wait for the node to start // wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint()) node.Client, err = rpc.Dial(conf.IPCEndpoint())
@ -277,17 +292,19 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
func newTestNode(t *testing.T, dir string) *testNode { func newTestNode(t *testing.T, dir string) *testNode {
conf, account := getTestAccount(t, dir) conf, account := getTestAccount(t, dir)
node := &testNode{Dir: dir} ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
node := &testNode{Dir: dir, PrivateKey: pk}
// assign ports // assign ports
httpPort, err := assignTCPPort() ports, err := getAvailableTCPPorts(2)
if err != nil {
t.Fatal(err)
}
p2pPort, err := assignTCPPort()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p2pPort := ports[0]
httpPort := ports[1]
// start the node // start the node
node.Cmd = runSwarm(t, node.Cmd = runSwarm(t,
@ -308,6 +325,17 @@ func newTestNode(t *testing.T, dir string) *testNode {
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// ensure that all ports have active listeners
// so that the next node will not get the same
// when calling getAvailableTCPPorts
err = waitTCPPorts(ctx, ports...)
if err != nil {
t.Fatal(err)
}
// wait for the node to start // wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint()) node.Client, err = rpc.Dial(conf.IPCEndpoint())
@ -343,15 +371,92 @@ func (n *testNode) Shutdown() {
} }
} }
func assignTCPPort() (string, error) { // getAvailableTCPPorts returns a set of ports that
// nothing is listening on at the time.
//
// Function assignTCPPort cannot be called in sequence
// and guardantee that the same port will be returned in
// different calls as the listener is closed within the function,
// not after all listeners are started and selected unique
// available ports.
func getAvailableTCPPorts(count int) (ports []string, err error) {
for i := 0; i < count; i++ {
l, err := net.Listen("tcp", "127.0.0.1:0") l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
return "", err return nil, err
} }
l.Close() // defer close in the loop to be sure the same port will not
// be selected in the next iteration
defer l.Close()
_, port, err := net.SplitHostPort(l.Addr().String()) _, port, err := net.SplitHostPort(l.Addr().String())
if err != nil { if err != nil {
return "", err return nil, err
}
ports = append(ports, port)
}
return ports, nil
}
// waitTCPPorts blocks until tcp connections can be
// established on all provided ports. It runs all
// ports dialers in parallel, and returns the first
// encountered error.
// See waitTCPPort also.
func waitTCPPorts(ctx context.Context, ports ...string) error {
var err error
// mu locks err variable that is assigned in
// other goroutines
var mu sync.Mutex
// cancel is canceling all goroutines
// when the firs error is returned
// to prevent unnecessary waiting
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var wg sync.WaitGroup
for _, port := range ports {
wg.Add(1)
go func(port string) {
defer wg.Done()
e := waitTCPPort(ctx, port)
mu.Lock()
defer mu.Unlock()
if e != nil && err == nil {
err = e
cancel()
}
}(port)
}
wg.Wait()
return err
}
// waitTCPPort blocks until tcp connection can be established
// ona provided port. It has a 3 minute timeout as maximum,
// to prevent long waiting, but it can be shortened with
// a provided context instance. Dialer has a 10 second timeout
// in every iteration, and connection refused error will be
// retried in 100 milliseconds periods.
func waitTCPPort(ctx context.Context, port string) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
for {
c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port)
if err != nil {
if operr, ok := err.(*net.OpError); ok {
if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED {
time.Sleep(100 * time.Millisecond)
continue
}
}
return err
}
return c.Close()
} }
return port, nil
} }

View file

@ -98,6 +98,17 @@ func upload(ctx *cli.Context) {
if !recursive { if !recursive {
return "", errors.New("Argument is a directory and recursive upload is disabled") return "", errors.New("Argument is a directory and recursive upload is disabled")
} }
if defaultPath != "" {
// construct absolute default path
absDefaultPath, _ := filepath.Abs(defaultPath)
absFile, _ := filepath.Abs(file)
// make sure absolute directory ends with only one "/"
// to trim it from absolute default path and get relative default path
absFile = strings.TrimRight(absFile, "/") + "/"
if absDefaultPath != "" && absFile != "" && strings.HasPrefix(absDefaultPath, absFile) {
defaultPath = strings.TrimPrefix(absDefaultPath, absFile)
}
}
return client.UploadDirectory(file, defaultPath, "", toEncrypt) return client.UploadDirectory(file, defaultPath, "", toEncrypt)
} }
} else { } else {

View file

@ -273,3 +273,84 @@ func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
} }
} }
} }
// TestCLISwarmUpDefaultPath tests swarm recursive upload with relative and absolute
// default paths and with encryption.
func TestCLISwarmUpDefaultPath(t *testing.T) {
testCLISwarmUpDefaultPath(false, false, t)
testCLISwarmUpDefaultPath(false, true, t)
testCLISwarmUpDefaultPath(true, false, t)
testCLISwarmUpDefaultPath(true, true, t)
}
func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) {
cluster := newTestCluster(t, 1)
defer cluster.Shutdown()
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
err = ioutil.WriteFile(filepath.Join(tmp, "index.html"), []byte("<h1>Test</h1>"), 0666)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(filepath.Join(tmp, "robots.txt"), []byte("Disallow: /"), 0666)
if err != nil {
t.Fatal(err)
}
defaultPath := "index.html"
if absDefaultPath {
defaultPath = filepath.Join(tmp, defaultPath)
}
args := []string{
"--bzzapi",
cluster.Nodes[0].URL,
"--recursive",
"--defaultpath",
defaultPath,
"up",
tmp,
}
if toEncrypt {
args = append(args, "--encrypt")
}
up := runSwarm(t, args...)
hashRegexp := `[a-f\d]{64,128}`
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
hash := matches[0]
client := swarm.NewClient(cluster.Nodes[0].URL)
m, isEncrypted, err := client.DownloadManifest(hash)
if err != nil {
t.Fatal(err)
}
if toEncrypt != isEncrypted {
t.Error("downloaded manifest is not encrypted")
}
var found bool
var entriesCount int
for _, e := range m.Entries {
entriesCount++
if e.Path == "" {
found = true
}
}
if !found {
t.Error("manifest default entry was not found")
}
if entriesCount != 3 {
t.Errorf("manifest contains %v entries, expected %v", entriesCount, 3)
}
}

View file

@ -24,7 +24,6 @@ import (
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -98,7 +97,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]
} }
@ -171,6 +170,7 @@ var (
Name: "light", Name: "light",
Usage: "Enable light client mode (replaced by --syncmode)", Usage: "Enable light client mode (replaced by --syncmode)",
} }
defaultSyncMode = eth.DefaultConfig.SyncMode defaultSyncMode = eth.DefaultConfig.SyncMode
SyncModeFlag = TextMarshalerFlag{ SyncModeFlag = TextMarshalerFlag{
Name: "syncmode", Name: "syncmode",
@ -247,6 +247,10 @@ var (
Value: eth.DefaultConfig.Ethash.DatasetsOnDisk, Value: eth.DefaultConfig.Ethash.DatasetsOnDisk,
} }
// Transaction pool settings // Transaction pool settings
TxPoolLocalsFlag = cli.StringFlag{
Name: "txpool.locals",
Usage: "Comma separated accounts to treat as locals (no flush, priority inclusion)",
}
TxPoolNoLocalsFlag = cli.BoolFlag{ TxPoolNoLocalsFlag = cli.BoolFlag{
Name: "txpool.nolocals", Name: "txpool.nolocals",
Usage: "Disables price exemptions for locally submitted transactions", Usage: "Disables price exemptions for locally submitted transactions",
@ -323,29 +327,71 @@ var (
Usage: "Enable mining", Usage: "Enable mining",
} }
MinerThreadsFlag = cli.IntFlag{ MinerThreadsFlag = cli.IntFlag{
Name: "minerthreads", Name: "miner.threads",
Usage: "Number of CPU threads to use for mining", Usage: "Number of CPU threads to use for mining",
Value: runtime.NumCPU(), Value: 0,
} }
TargetGasLimitFlag = cli.Uint64Flag{ MinerLegacyThreadsFlag = cli.IntFlag{
Name: "minerthreads",
Usage: "Number of CPU threads to use for mining (deprecated, use --miner.threads)",
Value: 0,
}
MinerNotifyFlag = cli.StringFlag{
Name: "miner.notify",
Usage: "Comma separated HTTP URL list to notify of new work packages",
}
MinerGasTargetFlag = cli.Uint64Flag{
Name: "miner.gastarget",
Usage: "Target gas floor for mined blocks",
Value: eth.DefaultConfig.MinerGasFloor,
}
MinerLegacyGasTargetFlag = cli.Uint64Flag{
Name: "targetgaslimit", Name: "targetgaslimit",
Usage: "Target gas limit sets the artificial target gas floor for the blocks to mine", Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)",
Value: params.GenesisGasLimit, Value: eth.DefaultConfig.MinerGasFloor,
} }
EtherbaseFlag = cli.StringFlag{ MinerGasLimitFlag = cli.Uint64Flag{
Name: "etherbase", Name: "miner.gaslimit",
Usage: "Public address for block mining rewards (default = first account created)", Usage: "Target gas ceiling for mined blocks",
Value: eth.DefaultConfig.MinerGasCeil,
}
MinerGasPriceFlag = BigFlag{
Name: "miner.gasprice",
Usage: "Minimal gas price for mining a transactions",
Value: eth.DefaultConfig.MinerGasPrice,
}
MinerLegacyGasPriceFlag = BigFlag{
Name: "gasprice",
Usage: "Minimal gas price for mining a transactions (deprecated, use --miner.gasprice)",
Value: eth.DefaultConfig.MinerGasPrice,
}
MinerEtherbaseFlag = cli.StringFlag{
Name: "miner.etherbase",
Usage: "Public address for block mining rewards (default = first account)",
Value: "0", Value: "0",
} }
GasPriceFlag = BigFlag{ MinerLegacyEtherbaseFlag = cli.StringFlag{
Name: "gasprice", Name: "etherbase",
Usage: "Minimal gas price to accept for mining a transactions", Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)",
Value: eth.DefaultConfig.GasPrice, Value: "0",
} }
ExtraDataFlag = cli.StringFlag{ MinerExtraDataFlag = cli.StringFlag{
Name: "extradata", Name: "miner.extradata",
Usage: "Block extra data set by the miner (default = client version)", Usage: "Block extra data set by the miner (default = client version)",
} }
MinerLegacyExtraDataFlag = cli.StringFlag{
Name: "extradata",
Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)",
}
MinerRecommitIntervalFlag = cli.DurationFlag{
Name: "miner.recommit",
Usage: "Time interval to recreate the block being mined",
Value: eth.DefaultConfig.MinerRecommit,
}
MinerNoVerfiyFlag = cli.BoolFlag{
Name: "miner.noverify",
Usage: "Disable remote sealing verification",
}
// Account settings // Account settings
UnlockedAccountFlag = cli.StringFlag{ UnlockedAccountFlag = cli.StringFlag{
Name: "unlock", Name: "unlock",
@ -609,7 +655,7 @@ func setNodeKey(ctx *cli.Context, cfg *p2p.Config) {
case file != "": case file != "":
if key, err = crypto.LoadECDSA(file); err != nil { if key, err = crypto.LoadECDSA(file); err != nil {
Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err) Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err)
} }]
cfg.PrivateKey = key cfg.PrivateKey = key
case hex != "": case hex != "":
if key, err = crypto.HexToECDSA(hex); err != nil { if key, err = crypto.HexToECDSA(hex); err != nil {
@ -649,8 +695,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
for _, url := range urls { for _, url := range urls {
node, err := discover.ParseNode(url) node, err := discover.ParseNode(url)
if err != nil { if err != nil {
log.Error("Bootstrap URL invalid", "enode", url, "err", err) log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
continue
} }
cfg.BootstrapNodes = append(cfg.BootstrapNodes, node) cfg.BootstrapNodes = append(cfg.BootstrapNodes, node)
} }
@ -816,10 +861,19 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
// setEtherbase retrieves the etherbase either from the directly specified // setEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed. // command line flags or from the keystore if CLI indexed.
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
if ctx.GlobalIsSet(EtherbaseFlag.Name) { // Extract the current etherbase, new flag overriding legacy one
account, err := MakeAddress(ks, ctx.GlobalString(EtherbaseFlag.Name)) var etherbase string
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) {
etherbase = ctx.GlobalString(MinerLegacyEtherbaseFlag.Name)
}
if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) {
etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name)
}
// Convert the etherbase into an address and configure it
if etherbase != "" {
account, err := MakeAddress(ks, etherbase)
if err != nil { if err != nil {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err) Fatalf("Invalid miner etherbase: %v", err)
} }
cfg.Etherbase = account.Address cfg.Etherbase = account.Address
} }
@ -850,7 +904,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setBootstrapNodes(ctx, cfg) setBootstrapNodes(ctx, cfg)
setBootstrapNodesV5(ctx, cfg) setBootstrapNodesV5(ctx, cfg)
lightClient := ctx.GlobalBool(LightModeFlag.Name) || ctx.GlobalString(SyncModeFlag.Name) == "light" lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light"
lightServer := ctx.GlobalInt(LightServFlag.Name) != 0 lightServer := ctx.GlobalInt(LightServFlag.Name) != 0
lightPeers := ctx.GlobalInt(LightPeersFlag.Name) lightPeers := ctx.GlobalInt(LightPeersFlag.Name)
@ -950,6 +1004,16 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
} }
func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
if ctx.GlobalIsSet(TxPoolLocalsFlag.Name) {
locals := strings.Split(ctx.GlobalString(TxPoolLocalsFlag.Name), ",")
for _, account := range locals {
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
Fatalf("Invalid account in --txpool.locals: %s", trimmed)
} else {
cfg.Locals = append(cfg.Locals, common.HexToAddress(account))
}
}
}
if ctx.GlobalIsSet(TxPoolNoLocalsFlag.Name) { if ctx.GlobalIsSet(TxPoolNoLocalsFlag.Name) {
cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name) cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name)
} }
@ -1055,8 +1119,6 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
// Avoid conflicting network flags // Avoid conflicting network flags
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag) checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
checkExclusive(ctx, LightServFlag, LightModeFlag)
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light") checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
@ -1065,13 +1127,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
setTxPool(ctx, &cfg.TxPool) setTxPool(ctx, &cfg.TxPool)
setEthash(ctx, cfg) setEthash(ctx, cfg)
switch { if ctx.GlobalIsSet(SyncModeFlag.Name) {
case ctx.GlobalIsSet(SyncModeFlag.Name):
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode) cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
case ctx.GlobalBool(FastSyncFlag.Name):
cfg.SyncMode = downloader.FastSync
case ctx.GlobalBool(LightModeFlag.Name):
cfg.SyncMode = downloader.LightSync
} }
if ctx.GlobalIsSet(LightServFlag.Name) { if ctx.GlobalIsSet(LightServFlag.Name) {
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name) cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
@ -1096,17 +1153,38 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
} }
if ctx.GlobalIsSet(MinerThreadsFlag.Name) { if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
cfg.MinerThreads = ctx.GlobalInt(MinerThreadsFlag.Name) cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
} }
if ctx.GlobalIsSet(DocRootFlag.Name) { if ctx.GlobalIsSet(DocRootFlag.Name) {
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
} }
if ctx.GlobalIsSet(ExtraDataFlag.Name) { if ctx.GlobalIsSet(MinerLegacyExtraDataFlag.Name) {
cfg.ExtraData = []byte(ctx.GlobalString(ExtraDataFlag.Name)) cfg.MinerExtraData = []byte(ctx.GlobalString(MinerLegacyExtraDataFlag.Name))
} }
if ctx.GlobalIsSet(GasPriceFlag.Name) { if ctx.GlobalIsSet(MinerExtraDataFlag.Name) {
cfg.GasPrice = GlobalBig(ctx, GasPriceFlag.Name) cfg.MinerExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name))
}
if ctx.GlobalIsSet(MinerLegacyGasTargetFlag.Name) {
cfg.MinerGasFloor = ctx.GlobalUint64(MinerLegacyGasTargetFlag.Name)
}
if ctx.GlobalIsSet(MinerGasTargetFlag.Name) {
cfg.MinerGasFloor = ctx.GlobalUint64(MinerGasTargetFlag.Name)
}
if ctx.GlobalIsSet(MinerGasLimitFlag.Name) {
cfg.MinerGasCeil = ctx.GlobalUint64(MinerGasLimitFlag.Name)
}
if ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
cfg.MinerGasPrice = GlobalBig(ctx, MinerLegacyGasPriceFlag.Name)
}
if ctx.GlobalIsSet(MinerGasPriceFlag.Name) {
cfg.MinerGasPrice = GlobalBig(ctx, MinerGasPriceFlag.Name)
}
if ctx.GlobalIsSet(MinerRecommitIntervalFlag.Name) {
cfg.MinerRecommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
}
if ctx.GlobalIsSet(MinerNoVerfiyFlag.Name) {
cfg.MinerNoverify = ctx.Bool(MinerNoVerfiyFlag.Name)
} }
if ctx.GlobalIsSet(VMEnableDebugFlag.Name) { if ctx.GlobalIsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode // TODO(fjl): force-enable this in --dev mode
@ -1148,8 +1226,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
log.Info("Using developer account", "address", developer.Address) log.Info("Using developer account", "address", developer.Address)
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address) cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
if !ctx.GlobalIsSet(GasPriceFlag.Name) { if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
cfg.GasPrice = big.NewInt(1) cfg.MinerGasPrice = big.NewInt(1)
} }
} }
// TODO(fjl): move trie cache generations into config // TODO(fjl): move trie cache generations into config
@ -1220,12 +1298,6 @@ func RegisterEthStatsService(stack *node.Node, url string) {
} }
} }
// SetupNetwork configures the system for either the main net or some test network.
func SetupNetwork(ctx *cli.Context) {
// TODO(fjl): move target gas limit into config
params.TargetGasLimit = ctx.GlobalUint64(TargetGasLimitFlag.Name)
}
func SetupMetrics(ctx *cli.Context) { func SetupMetrics(ctx *cli.Context) {
if metrics.Enabled { if metrics.Enabled {
log.Info("Enabling metrics collection") log.Info("Enabling metrics collection")
@ -1254,7 +1326,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
handles = makeDatabaseHandles() handles = makeDatabaseHandles()
) )
name := "chaindata" name := "chaindata"
if ctx.GlobalBool(LightModeFlag.Name) { if ctx.GlobalString(SyncModeFlag.Name) == "light" {
name = "lightchaindata" name = "lightchaindata"
} }
chainDb, err := stack.OpenDatabase(name, cache, handles) chainDb, err := stack.OpenDatabase(name, cache, handles)
@ -1299,7 +1371,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir), DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir),
DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem, DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem,
DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk, DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk,
}) }, nil, false)
} }
} }
if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {

View file

@ -754,7 +754,7 @@ func extractIDFromEnode(s string) []byte {
return n.ID[:] return n.ID[:]
} }
// obfuscateBloom adds 16 random bits to the the bloom // obfuscateBloom adds 16 random bits to the bloom
// filter, in order to obfuscate the containing topics. // filter, in order to obfuscate the containing topics.
// it does so deterministically within every session. // it does so deterministically within every session.
// despite additional bits, it will match on average // despite additional bits, it will match on average

View file

@ -117,7 +117,7 @@ func TestDecodingCycle(t *testing.T) {
// TestCompression tests that compression works by returning either the bitset // TestCompression tests that compression works by returning either the bitset
// encoded input, or the actual input if the bitset version is longer. // encoded input, or the actual input if the bitset version is longer.
func TestCompression(t *testing.T) { func TestCompression(t *testing.T) {
// Check the the compression returns the bitset encoding is shorter // Check the compression returns the bitset encoding is shorter
in := hexutil.MustDecode("0x4912385c0e7b64000000") in := hexutil.MustDecode("0x4912385c0e7b64000000")
out := hexutil.MustDecode("0x80fe4912385c0e7b64") out := hexutil.MustDecode("0x80fe4912385c0e7b64")
@ -127,7 +127,7 @@ func TestCompression(t *testing.T) {
if data, err := DecompressBytes(out, len(in)); err != nil || !bytes.Equal(data, in) { if data, err := DecompressBytes(out, len(in)); err != nil || !bytes.Equal(data, in) {
t.Errorf("decoding mismatch for sparse data: have %x, want %x, error %v", data, in, err) t.Errorf("decoding mismatch for sparse data: have %x, want %x, error %v", data, in, err)
} }
// Check the the compression returns the input if the bitset encoding is longer // Check the compression returns the input if the bitset encoding is longer
in = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb") in = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb")
out = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb") out = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb")

View file

@ -30,3 +30,34 @@ type AbsTime time.Duration
func Now() AbsTime { func Now() AbsTime {
return AbsTime(monotime.Now()) return AbsTime(monotime.Now())
} }
// Add returns t + d.
func (t AbsTime) Add(d time.Duration) AbsTime {
return t + AbsTime(d)
}
// Clock interface makes it possible to replace the monotonic system clock with
// a simulated clock.
type Clock interface {
Now() AbsTime
Sleep(time.Duration)
After(time.Duration) <-chan time.Time
}
// System implements Clock using the system clock.
type System struct{}
// Now implements Clock.
func (System) Now() AbsTime {
return AbsTime(monotime.Now())
}
// Sleep implements Clock.
func (System) Sleep(d time.Duration) {
time.Sleep(d)
}
// After implements Clock.
func (System) After(d time.Duration) <-chan time.Time {
return time.After(d)
}

129
common/mclock/simclock.go Normal file
View file

@ -0,0 +1,129 @@
// 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 mclock
import (
"sync"
"time"
)
// Simulated implements a virtual Clock for reproducible time-sensitive tests. It
// simulates a scheduler on a virtual timescale where actual processing takes zero time.
//
// The virtual clock doesn't advance on its own, call Run to advance it and execute timers.
// Since there is no way to influence the Go scheduler, testing timeout behaviour involving
// goroutines needs special care. A good way to test such timeouts is as follows: First
// perform the action that is supposed to time out. Ensure that the timer you want to test
// is created. Then run the clock until after the timeout. Finally observe the effect of
// the timeout using a channel or semaphore.
type Simulated struct {
now AbsTime
scheduled []event
mu sync.RWMutex
cond *sync.Cond
}
type event struct {
do func()
at AbsTime
}
// Run moves the clock by the given duration, executing all timers before that duration.
func (s *Simulated) Run(d time.Duration) {
s.mu.Lock()
defer s.mu.Unlock()
s.init()
end := s.now + AbsTime(d)
for len(s.scheduled) > 0 {
ev := s.scheduled[0]
if ev.at > end {
break
}
s.now = ev.at
ev.do()
s.scheduled = s.scheduled[1:]
}
s.now = end
}
func (s *Simulated) ActiveTimers() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.scheduled)
}
func (s *Simulated) WaitForTimers(n int) {
s.mu.Lock()
defer s.mu.Unlock()
s.init()
for len(s.scheduled) < n {
s.cond.Wait()
}
}
// Now implements Clock.
func (s *Simulated) Now() AbsTime {
s.mu.RLock()
defer s.mu.RUnlock()
return s.now
}
// Sleep implements Clock.
func (s *Simulated) Sleep(d time.Duration) {
<-s.After(d)
}
// After implements Clock.
func (s *Simulated) After(d time.Duration) <-chan time.Time {
after := make(chan time.Time, 1)
s.insert(d, func() {
after <- (time.Time{}).Add(time.Duration(s.now))
})
return after
}
func (s *Simulated) insert(d time.Duration, do func()) {
s.mu.Lock()
defer s.mu.Unlock()
s.init()
at := s.now + AbsTime(d)
l, h := 0, len(s.scheduled)
ll := h
for l != h {
m := (l + h) / 2
if at < s.scheduled[m].at {
h = m
} else {
l = m + 1
}
}
s.scheduled = append(s.scheduled, event{})
copy(s.scheduled[l+1:], s.scheduled[l:ll])
s.scheduled[l] = event{do: do, at: at}
s.cond.Broadcast()
}
func (s *Simulated) init() {
if s.cond == nil {
s.cond = sync.NewCond(&s.mu)
}
}

57
common/prque/prque.go Executable file
View file

@ -0,0 +1,57 @@
// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque".
package prque
import (
"container/heap"
)
// Priority queue data structure.
type Prque struct {
cont *sstack
}
// Creates a new priority queue.
func New(setIndex setIndexCallback) *Prque {
return &Prque{newSstack(setIndex)}
}
// Pushes a value with a given priority into the queue, expanding if necessary.
func (p *Prque) Push(data interface{}, priority int64) {
heap.Push(p.cont, &item{data, priority})
}
// Pops the value with the greates priority off the stack and returns it.
// Currently no shrinking is done.
func (p *Prque) Pop() (interface{}, int64) {
item := heap.Pop(p.cont).(*item)
return item.value, item.priority
}
// Pops only the item from the queue, dropping the associated priority value.
func (p *Prque) PopItem() interface{} {
return heap.Pop(p.cont).(*item).value
}
// Remove removes the element with the given index.
func (p *Prque) Remove(i int) interface{} {
if i < 0 {
return nil
}
return heap.Remove(p.cont, i)
}
// Checks whether the priority queue is empty.
func (p *Prque) Empty() bool {
return p.cont.Len() == 0
}
// Returns the number of element in the priority queue.
func (p *Prque) Size() int {
return p.cont.Len()
}
// Clears the contents of the priority queue.
func (p *Prque) Reset() {
*p = *New(p.cont.setIndex)
}

106
common/prque/sstack.go Executable file
View file

@ -0,0 +1,106 @@
// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque".
package prque
// The size of a block of data
const blockSize = 4096
// A prioritized item in the sorted stack.
//
// Note: priorities can "wrap around" the int64 range, a comes before b if (a.priority - b.priority) > 0.
// The difference between the lowest and highest priorities in the queue at any point should be less than 2^63.
type item struct {
value interface{}
priority int64
}
// setIndexCallback is called when the element is moved to a new index.
// Providing setIndexCallback is optional, it is needed only if the application needs
// to delete elements other than the top one.
type setIndexCallback func(a interface{}, i int)
// Internal sortable stack data structure. Implements the Push and Pop ops for
// the stack (heap) functionality and the Len, Less and Swap methods for the
// sortability requirements of the heaps.
type sstack struct {
setIndex setIndexCallback
size int
capacity int
offset int
blocks [][]*item
active []*item
}
// Creates a new, empty stack.
func newSstack(setIndex setIndexCallback) *sstack {
result := new(sstack)
result.setIndex = setIndex
result.active = make([]*item, blockSize)
result.blocks = [][]*item{result.active}
result.capacity = blockSize
return result
}
// Pushes a value onto the stack, expanding it if necessary. Required by
// heap.Interface.
func (s *sstack) Push(data interface{}) {
if s.size == s.capacity {
s.active = make([]*item, blockSize)
s.blocks = append(s.blocks, s.active)
s.capacity += blockSize
s.offset = 0
} else if s.offset == blockSize {
s.active = s.blocks[s.size/blockSize]
s.offset = 0
}
if s.setIndex != nil {
s.setIndex(data.(*item).value, s.size)
}
s.active[s.offset] = data.(*item)
s.offset++
s.size++
}
// Pops a value off the stack and returns it. Currently no shrinking is done.
// Required by heap.Interface.
func (s *sstack) Pop() (res interface{}) {
s.size--
s.offset--
if s.offset < 0 {
s.offset = blockSize - 1
s.active = s.blocks[s.size/blockSize]
}
res, s.active[s.offset] = s.active[s.offset], nil
if s.setIndex != nil {
s.setIndex(res.(*item).value, -1)
}
return
}
// Returns the length of the stack. Required by sort.Interface.
func (s *sstack) Len() int {
return s.size
}
// Compares the priority of two elements of the stack (higher is first).
// Required by sort.Interface.
func (s *sstack) Less(i, j int) bool {
return (s.blocks[i/blockSize][i%blockSize].priority - s.blocks[j/blockSize][j%blockSize].priority) > 0
}
// Swaps two elements in the stack. Required by sort.Interface.
func (s *sstack) Swap(i, j int) {
ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize
a, b := s.blocks[jb][jo], s.blocks[ib][io]
if s.setIndex != nil {
s.setIndex(a.value, i)
s.setIndex(b.value, j)
}
s.blocks[ib][io], s.blocks[jb][jo] = a, b
}
// Resets the stack, effectively clearing its contents.
func (s *sstack) Reset() {
*s = *newSstack(s.setIndex)
}

View file

@ -387,23 +387,24 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
break break
} }
} }
// If we're at block zero, make a snapshot // If we're at an checkpoint block, make a snapshot if it's known
if number == 0 { if number%c.config.Epoch == 0 {
genesis := chain.GetHeaderByNumber(0) checkpoint := chain.GetHeaderByNumber(number)
if err := c.VerifyHeader(chain, genesis, false); err != nil { if checkpoint != nil {
return nil, err hash := checkpoint.Hash()
}
signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength) signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength)
for i := 0; i < len(signers); i++ { for i := 0; i < len(signers); i++ {
copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:]) copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:])
} }
snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers) snap = newSnapshot(c.config, c.signatures, number, hash, signers)
if err := snap.store(c.db); err != nil { if err := snap.store(c.db); err != nil {
return nil, err return nil, err
} }
log.Trace("Stored genesis voting snapshot to disk") log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)
break break
} }
}
// No snapshot for this header, gather the header and move backward // No snapshot for this header, gather the header and move backward
var header *types.Header var header *types.Header
if len(parents) > 0 { if len(parents) > 0 {
@ -589,17 +590,17 @@ func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
// Seal implements consensus.Engine, attempting to create a sealed block using // Seal implements consensus.Engine, attempting to create a sealed block using
// the local signing credentials. // the local signing credentials.
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
header := block.Header() header := block.Header()
// Sealing the genesis block is not supported // Sealing the genesis block is not supported
number := header.Number.Uint64() number := header.Number.Uint64()
if number == 0 { if number == 0 {
return nil, errUnknownBlock return errUnknownBlock
} }
// For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
if c.config.Period == 0 && len(block.Transactions()) == 0 { if c.config.Period == 0 && len(block.Transactions()) == 0 {
return nil, errWaitTransactions return errWaitTransactions
} }
// Don't hold the signer fields for the entire sealing procedure // Don't hold the signer fields for the entire sealing procedure
c.lock.RLock() c.lock.RLock()
@ -609,10 +610,10 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
// Bail out if we're unauthorized to sign a block // Bail out if we're unauthorized to sign a block
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil { if err != nil {
return nil, err return err
} }
if _, authorized := snap.Signers[signer]; !authorized { if _, authorized := snap.Signers[signer]; !authorized {
return nil, errUnauthorized return errUnauthorized
} }
// If we're amongst the recent signers, wait for the next block // If we're amongst the recent signers, wait for the next block
for seen, recent := range snap.Recents { for seen, recent := range snap.Recents {
@ -620,8 +621,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
// Signer is among recents, only wait if the current block doesn't shift it out // Signer is among recents, only wait if the current block doesn't shift it out
if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit { if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
log.Info("Signed recently, must wait for others") log.Info("Signed recently, must wait for others")
<-stop return nil
return nil, nil
} }
} }
} }
@ -634,21 +634,29 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
} }
log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
select {
case <-stop:
return nil, nil
case <-time.After(delay):
}
// Sign all the things! // Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
if err != nil { if err != nil {
return nil, err return err
} }
copy(header.Extra[len(header.Extra)-extraSeal:], sighash) copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
// Wait until sealing is terminated or delay timeout.
log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
go func() {
select {
case <-stop:
return
case <-time.After(delay):
}
return block.WithSeal(header), nil select {
case results <- block.WithSeal(header):
default:
log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header))
}
}()
return nil
} }
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
@ -672,6 +680,16 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
return new(big.Int).Set(diffNoTurn) return new(big.Int).Set(diffNoTurn)
} }
// SealHash returns the hash of a block prior to it being sealed.
func (c *Clique) SealHash(header *types.Header) common.Hash {
return sigHash(header)
}
// 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 {

View file

@ -84,7 +84,7 @@ func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header {
if number == 0 { if number == 0 {
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0) return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0)
} }
panic("not supported") return nil
} }
// Tests that voting is evaluated correctly for various simple and complex scenarios. // Tests that voting is evaluated correctly for various simple and complex scenarios.

View file

@ -86,9 +86,15 @@ type Engine interface {
Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
// Seal generates a new block for the given input block with the local miner's // Seal generates a new sealing request for the given input block and pushes
// seal place on top. // the result into the given channel.
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) //
// Note, the method returns immediately and will send the result async. More
// than one result may also be returned depending on the consensus algorothm.
Seal(chain ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
// SealHash returns the hash of a block prior to it being sealed.
SealHash(header *types.Header) common.Hash
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have. // that a new block should have.
@ -96,6 +102,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.

View file

@ -729,7 +729,8 @@ 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}, nil, false)
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
View 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())
}

View file

@ -31,13 +31,15 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
) )
// Ethash proof-of-work protocol constants. // Ethash proof-of-work protocol constants.
var ( var (
FrontierBlockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
ByzantiumBlockReward *big.Int = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium ByzantiumBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
maxUncles = 2 // Maximum number of uncles allowed in a single block maxUncles = 2 // Maximum number of uncles allowed in a single block
allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks
) )
@ -461,6 +463,13 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
// VerifySeal implements consensus.Engine, checking whether the given block satisfies // VerifySeal implements consensus.Engine, checking whether the given block satisfies
// the PoW difficulty requirements. // the PoW difficulty requirements.
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error { func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
return ethash.verifySeal(chain, header, false)
}
// verifySeal checks whether a block satisfies the PoW difficulty requirements,
// either using the usual ethash cache for it, or alternatively using a full DAG
// to make remote mining fast.
func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error {
// If we're running a fake PoW, accept any seal as valid // If we're running a fake PoW, accept any seal as valid
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
time.Sleep(ethash.fakeDelay) time.Sleep(ethash.fakeDelay)
@ -471,29 +480,52 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head
} }
// If we're running a shared PoW, delegate verification to it // If we're running a shared PoW, delegate verification to it
if ethash.shared != nil { if ethash.shared != nil {
return ethash.shared.VerifySeal(chain, header) return ethash.shared.verifySeal(chain, header, fulldag)
} }
// Ensure that we have a valid difficulty for the block // Ensure that we have a valid difficulty for the block
if header.Difficulty.Sign() <= 0 { if header.Difficulty.Sign() <= 0 {
return errInvalidDifficulty return errInvalidDifficulty
} }
// Recompute the digest and PoW value and verify against the header // Recompute the digest and PoW values
number := header.Number.Uint64() number := header.Number.Uint64()
var (
digest []byte
result []byte
)
// If fast-but-heavy PoW verification was requested, use an ethash dataset
if fulldag {
dataset := ethash.dataset(number, true)
if dataset.generated() {
digest, result = hashimotoFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
// Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
// until after the call to hashimotoFull so it's not unmapped while being used.
runtime.KeepAlive(dataset)
} else {
// Dataset not yet generated, don't hang, use a cache instead
fulldag = false
}
}
// If slow-but-light PoW verification was requested (or DAG not yet ready), use an ethash cache
if !fulldag {
cache := ethash.cache(number) cache := ethash.cache(number)
size := datasetSize(number) size := datasetSize(number)
if ethash.config.PowMode == ModeTest { if ethash.config.PowMode == ModeTest {
size = 32 * 1024 size = 32 * 1024
} }
digest, result := hashimotoLight(size, cache.cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64()) digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
// Caches are unmapped in a finalizer. Ensure that the cache stays live
// Caches are unmapped in a finalizer. Ensure that the cache stays alive
// until after the call to hashimotoLight so it's not unmapped while being used. // until after the call to hashimotoLight so it's not unmapped while being used.
runtime.KeepAlive(cache) runtime.KeepAlive(cache)
}
// Verify the calculated values against the ones provided in the header
if !bytes.Equal(header.MixDigest[:], digest) { if !bytes.Equal(header.MixDigest[:], digest) {
return errInvalidMixDigest return errInvalidMixDigest
} }
target := new(big.Int).Div(maxUint256, header.Difficulty) target := new(big.Int).Div(two256, header.Difficulty)
if new(big.Int).SetBytes(result).Cmp(target) > 0 { if new(big.Int).SetBytes(result).Cmp(target) > 0 {
return errInvalidPoW return errInvalidPoW
} }
@ -522,6 +554,29 @@ func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header
return types.NewBlock(header, txs, uncles, receipts), nil return types.NewBlock(header, txs, uncles, receipts), nil
} }
// SealHash returns the hash of a block prior to it being sealed.
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
hasher.Sum(hash[:0])
return hash
}
// Some weird constants to avoid constant memory allocs for them. // Some weird constants to avoid constant memory allocs for them.
var ( var (
big8 = big.NewInt(8) big8 = big.NewInt(8)

View file

@ -29,11 +29,14 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"sync" "sync"
"sync/atomic"
"time" "time"
"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"
@ -43,11 +46,11 @@ import (
var ErrInvalidDumpMagic = errors.New("invalid dump magic") var ErrInvalidDumpMagic = errors.New("invalid dump magic")
var ( var (
// maxUint256 is a big integer representing 2^256-1 // two256 is a big integer representing 2^256
maxUint256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
// sharedEthash is a full instance that can be shared between multiple users. // sharedEthash is a full instance that can be shared between multiple users.
sharedEthash = New(Config{"", 3, 0, "", 1, 0, ModeNormal}) sharedEthash = New(Config{"", 3, 0, "", 1, 0, ModeNormal}, nil, false)
// algorithmRevision is the data structure version used for file naming. // algorithmRevision is the data structure version used for file naming.
algorithmRevision = 23 algorithmRevision = 23
@ -279,6 +282,7 @@ type dataset struct {
mmap mmap.MMap // Memory map itself to unmap before releasing mmap mmap.MMap // Memory map itself to unmap before releasing
dataset []uint32 // The actual cache data content dataset []uint32 // The actual cache data content
once sync.Once // Ensures the cache is generated only once once sync.Once // Ensures the cache is generated only once
done uint32 // Atomic flag to determine generation status
} }
// newDataset creates a new ethash mining dataset and returns it as a plain Go // newDataset creates a new ethash mining dataset and returns it as a plain Go
@ -290,6 +294,9 @@ func newDataset(epoch uint64) interface{} {
// generate ensures that the dataset content is generated before use. // generate ensures that the dataset content is generated before use.
func (d *dataset) generate(dir string, limit int, test bool) { func (d *dataset) generate(dir string, limit int, test bool) {
d.once.Do(func() { d.once.Do(func() {
// Mark the dataset generated after we're done. This is needed for remote
defer atomic.StoreUint32(&d.done, 1)
csize := cacheSize(d.epoch*epochLength + 1) csize := cacheSize(d.epoch*epochLength + 1)
dsize := datasetSize(d.epoch*epochLength + 1) dsize := datasetSize(d.epoch*epochLength + 1)
seed := seedHash(d.epoch*epochLength + 1) seed := seedHash(d.epoch*epochLength + 1)
@ -304,6 +311,8 @@ func (d *dataset) generate(dir string, limit int, test bool) {
d.dataset = make([]uint32, dsize/4) d.dataset = make([]uint32, dsize/4)
generateDataset(d.dataset, d.epoch, cache) generateDataset(d.dataset, d.epoch, cache)
return
} }
// Disk storage is needed, this will get fancy // Disk storage is needed, this will get fancy
var endian string var endian string
@ -346,6 +355,13 @@ func (d *dataset) generate(dir string, limit int, test bool) {
}) })
} }
// generated returns whether this particular dataset finished generating already
// or not (it may not have been started at all). This is useful for remote miners
// to default to verification caches instead of blocking on DAG generations.
func (d *dataset) generated() bool {
return atomic.LoadUint32(&d.done) == 1
}
// finalizer closes any file handlers and memory maps open. // finalizer closes any file handlers and memory maps open.
func (d *dataset) finalizer() { func (d *dataset) finalizer() {
if d.mmap != nil { if d.mmap != nil {
@ -389,6 +405,36 @@ type Config struct {
PowMode Mode PowMode Mode
} }
// sealTask wraps a seal block with relative result channel for remote sealer thread.
type sealTask struct {
block *types.Block
results chan<- *types.Block
}
// 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,16 +449,27 @@ 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 *sealTask // Notification channel to push new work and relative result channel to remote sealer
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
func New(config Config) *Ethash { // remote mining, also optionally notifying a batch of remote services of new work
// packages.
func New(config Config, notify []string, noverify bool) *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)
config.CachesInMem = 1 config.CachesInMem = 1
@ -423,19 +480,41 @@ 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 *sealTask),
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(notify, noverify)
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(notify []string, noverify bool) *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 *sealTask),
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(notify, noverify)
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 +568,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.
@ -511,20 +606,34 @@ func (ethash *Ethash) cache(block uint64) *cache {
// dataset tries to retrieve a mining dataset for the specified block number // dataset tries to retrieve a mining dataset for the specified block number
// by first checking against a list of in-memory datasets, then against DAGs // by first checking against a list of in-memory datasets, then against DAGs
// stored on disk, and finally generating one if none can be found. // stored on disk, and finally generating one if none can be found.
func (ethash *Ethash) dataset(block uint64) *dataset { //
// If async is specified, not only the future but the current DAG is also
// generates on a background thread.
func (ethash *Ethash) dataset(block uint64, async bool) *dataset {
// Retrieve the requested ethash dataset
epoch := block / epochLength epoch := block / epochLength
currentI, futureI := ethash.datasets.get(epoch) currentI, futureI := ethash.datasets.get(epoch)
current := currentI.(*dataset) current := currentI.(*dataset)
// Wait for generation finish. // If async is specified, generate everything in a background thread
if async && !current.generated() {
go func() {
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
if futureI != nil {
future := futureI.(*dataset)
future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
}
}()
} else {
// Either blocking generation was requested, or already done
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
// If we need a new future dataset, now's a good time to regenerate it.
if futureI != nil { if futureI != nil {
future := futureI.(*dataset) future := futureI.(*dataset)
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
} }
}
return current return current
} }
@ -561,14 +670,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

View file

@ -23,24 +23,35 @@ 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"
) )
// Tests that ethash works correctly in test mode. // Tests that ethash works correctly in test mode.
func TestTestMode(t *testing.T) { func TestTestMode(t *testing.T) {
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
ethash := NewTester() ethash := NewTester(nil, false)
block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil) defer ethash.Close()
results := make(chan *types.Block)
err := ethash.Seal(nil, types.NewBlockWithHeader(header), results, nil)
if err != nil { if err != nil {
t.Fatalf("failed to seal block: %v", err) t.Fatalf("failed to seal block: %v", err)
} }
head.Nonce = types.EncodeNonce(block.Nonce()) select {
head.MixDigest = block.MixDigest() case block := <-results:
if err := ethash.VerifySeal(nil, head); err != nil { header.Nonce = types.EncodeNonce(block.Nonce())
header.MixDigest = block.MixDigest()
if err := ethash.VerifySeal(nil, header); err != nil {
t.Fatalf("unexpected verification error: %v", err) t.Fatalf("unexpected verification error: %v", err)
} }
case <-time.NewTimer(time.Second).C:
t.Error("sealing result timeout")
}
} }
// This test checks that cache lru logic doesn't crash under load. // This test checks that cache lru logic doesn't crash under load.
@ -51,7 +62,8 @@ func TestCacheFileEvict(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
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}, nil, false)
defer e.Close()
workers := 8 workers := 8
epochs := 100 epochs := 100
@ -73,7 +85,85 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) {
if block < 0 { if block < 0 {
block = 0 block = 0
} }
head := &types.Header{Number: big.NewInt(block), Difficulty: big.NewInt(100)} header := &types.Header{Number: big.NewInt(block), Difficulty: big.NewInt(100)}
e.VerifySeal(nil, head) e.VerifySeal(nil, header)
}
}
func TestRemoteSealer(t *testing.T) {
ethash := NewTester(nil, false)
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")
}
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
sealhash := ethash.SealHash(header)
// Push new work.
results := make(chan *types.Block)
ethash.Seal(nil, block, results, nil)
var (
work [3]string
err error
)
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
t.Error("expect to return a mining work has same hash")
}
if res := api.SubmitWork(types.BlockNonce{}, sealhash, 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.
header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
block = types.NewBlockWithHeader(header)
sealhash = ethash.SealHash(header)
ethash.Seal(nil, block, results, nil)
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
t.Error("expect to return the latest pushed work")
}
}
func TestHashRate(t *testing.T) {
var (
hashrate = []hexutil.Uint64{100, 200, 300}
expect uint64
ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")}
)
ethash := NewTester(nil, false)
defer ethash.Close()
if tot := ethash.Hashrate(); tot != 0 {
t.Error("expect the result should be zero")
}
api := &API{ethash}
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(nil, false)
time.Sleep(1 * time.Second) // ensure exit channel is listening
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")
} }
} }

View file

@ -17,12 +17,17 @@
package ethash package ethash
import ( import (
"bytes"
crand "crypto/rand" crand "crypto/rand"
"encoding/json"
"errors"
"math" "math"
"math/big" "math/big"
"math/rand" "math/rand"
"net/http"
"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,22 +35,36 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
const (
// staleThreshold is the maximum depth of the acceptable stale but valid ethash solution.
staleThreshold = 7
)
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, results chan<- *types.Block, stop <-chan struct{}) error {
// If we're running a fake PoW, simply return a 0 nonce immediately // If we're running a fake PoW, simply return a 0 nonce immediately
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
header := block.Header() header := block.Header()
header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
return block.WithSeal(header), nil select {
case results <- block.WithSeal(header):
default:
log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header()))
}
return nil
} }
// If we're running a shared PoW, delegate sealing to it // If we're running a shared PoW, delegate sealing to it
if ethash.shared != nil { if ethash.shared != nil {
return ethash.shared.Seal(chain, block, stop) return ethash.shared.Seal(chain, block, results, 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
@ -53,7 +72,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil { if err != nil {
ethash.lock.Unlock() ethash.lock.Unlock()
return nil, err return err
} }
ethash.rand = rand.New(rand.NewSource(seed.Int64())) ethash.rand = rand.New(rand.NewSource(seed.Int64()))
} }
@ -64,32 +83,47 @@ 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
} }
var pend sync.WaitGroup // Push new work to remote sealer
if ethash.workCh != nil {
ethash.workCh <- &sealTask{block: block, results: results}
}
var (
pend sync.WaitGroup
locals = make(chan *types.Block)
)
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, locals)
}(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
go func() {
var result *types.Block var result *types.Block
select { select {
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 = <-locals:
// One of the threads found a block, abort all others // One of the threads found a block, abort all others
select {
case results <- result:
default:
log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", ethash.SealHash(block.Header()))
}
close(abort) close(abort)
case <-ethash.update: case <-ethash.update:
// Thread count was changed on user request, restart // Thread count was changed on user request, restart
close(abort) close(abort)
pend.Wait() if err := ethash.Seal(chain, block, results, stop); err != nil {
return ethash.Seal(chain, block, stop) log.Error("Failed to restart sealing after update", "err", err)
}
} }
// Wait for all miners to terminate and return the block // Wait for all miners to terminate and return the block
pend.Wait() pend.Wait()
return result, nil }()
return nil
} }
// mine is the actual proof-of-work miner that searches for a nonce starting from // mine is the actual proof-of-work miner that searches for a nonce starting from
@ -98,10 +132,10 @@ func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan s
// Extract some data from the header // Extract some data from the header
var ( var (
header = block.Header() header = block.Header()
hash = header.HashNoNonce().Bytes() hash = ethash.SealHash(header).Bytes()
target = new(big.Int).Div(maxUint256, header.Difficulty) target = new(big.Int).Div(two256, header.Difficulty)
number = header.Number.Uint64() number = header.Number.Uint64()
dataset = ethash.dataset(number) dataset = ethash.dataset(number, false)
) )
// Start generating random nonces until we abort or find a good one // Start generating random nonces until we abort or find a good one
var ( var (
@ -150,3 +184,185 @@ 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 is a standalone goroutine to handle remote mining related stuff.
func (ethash *Ethash) remote(notify []string, noverify bool) {
var (
works = make(map[common.Hash]*types.Block)
rates = make(map[common.Hash]hashrate)
results chan<- *types.Block
currentBlock *types.Block
currentWork [3]string
notifyTransport = &http.Transport{}
notifyClient = &http.Client{
Transport: notifyTransport,
Timeout: time.Second,
}
notifyReqs = make([]*http.Request, len(notify))
)
// notifyWork notifies all the specified mining endpoints of the availability of
// new work to be processed.
notifyWork := func() {
work := currentWork
blob, _ := json.Marshal(work)
for i, url := range notify {
// Terminate any previously pending request and create the new work
if notifyReqs[i] != nil {
notifyTransport.CancelRequest(notifyReqs[i])
}
notifyReqs[i], _ = http.NewRequest("POST", url, bytes.NewReader(blob))
notifyReqs[i].Header.Set("Content-Type", "application/json")
// Push the new work concurrently to all the remote nodes
go func(req *http.Request, url string) {
res, err := notifyClient.Do(req)
if err != nil {
log.Warn("Failed to notify remote miner", "err", err)
} else {
log.Trace("Notified remote miner", "miner", url, "hash", log.Lazy{Fn: func() common.Hash { return common.HexToHash(work[0]) }}, "target", work[2])
res.Body.Close()
}
}(notifyReqs[i], url)
}
}
// makeWork creates 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
makeWork := func(block *types.Block) {
hash := ethash.SealHash(block.Header())
currentWork[0] = hash.Hex()
currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex()
currentWork[2] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex()
// Trace the seal work fetched by remote sealer.
currentBlock = block
works[hash] = block
}
// 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, sealhash common.Hash) bool {
if currentBlock == nil {
log.Error("Pending work without block", "sealhash", sealhash)
return false
}
// Make sure the work submitted is present
block := works[sealhash]
if block == nil {
log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64())
return false
}
// Verify the correctness of submitted result.
header := block.Header()
header.Nonce = nonce
header.MixDigest = mixDigest
start := time.Now()
if !noverify {
if err := ethash.verifySeal(nil, header, true); err != nil {
log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", time.Since(start), "err", err)
return false
}
}
// Make sure the result channel is assigned.
if results == nil {
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
return false
}
log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", time.Since(start))
// Solutions seems to be valid, return to the miner and notify acceptance.
solution := block.WithSeal(header)
// The submitted solution is within the scope of acceptance.
if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() {
select {
case results <- solution:
log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
return true
default:
log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash)
return false
}
}
// The submitted block is too old to accept, drop it.
log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
return false
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case work := <-ethash.workCh:
// Update current work with new received block.
// Note same work can be past twice, happens when changing CPU threads.
results = work.results
makeWork(work.block)
// Notify and requested URLs of the new work availability
notifyWork()
case work := <-ethash.fetchWorkCh:
// Return current mining work to remote miner.
if currentBlock == nil {
work.errc <- errNoMiningWork
} else {
work.res <- currentWork
}
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)
}
}
// Clear stale pending blocks
if currentBlock != nil {
for hash, block := range works {
if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() {
delete(works, hash)
}
}
}
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
}
}
}

View file

@ -0,0 +1,199 @@
package ethash
import (
"encoding/json"
"io/ioutil"
"math/big"
"net"
"net/http"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// Tests whether remote HTTP servers are correctly notified of new work.
func TestRemoteNotify(t *testing.T) {
// Start a simple webserver to capture notifications
sink := make(chan [3]string)
server := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
blob, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("failed to read miner notification: %v", err)
}
var work [3]string
if err := json.Unmarshal(blob, &work); err != nil {
t.Fatalf("failed to unmarshal miner notification: %v", err)
}
sink <- work
}),
}
// Open a custom listener to extract its local address
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("failed to open notification server: %v", err)
}
defer listener.Close()
go server.Serve(listener)
// Create the custom ethash engine
ethash := NewTester([]string{"http://" + listener.Addr().String()}, false)
defer ethash.Close()
// Stream a work task and ensure the notification bubbles out
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
ethash.Seal(nil, block, nil, nil)
select {
case work := <-sink:
if want := ethash.SealHash(header).Hex(); work[0] != want {
t.Errorf("work packet hash mismatch: have %s, want %s", work[0], want)
}
if want := common.BytesToHash(SeedHash(header.Number.Uint64())).Hex(); work[1] != want {
t.Errorf("work packet seed mismatch: have %s, want %s", work[1], want)
}
target := new(big.Int).Div(new(big.Int).Lsh(big.NewInt(1), 256), header.Difficulty)
if want := common.BytesToHash(target.Bytes()).Hex(); work[2] != want {
t.Errorf("work packet target mismatch: have %s, want %s", work[2], want)
}
case <-time.After(time.Second):
t.Fatalf("notification timed out")
}
}
// Tests that pushing work packages fast to the miner doesn't cause any data race
// issues in the notifications.
func TestRemoteMultiNotify(t *testing.T) {
// Start a simple webserver to capture notifications
sink := make(chan [3]string, 64)
server := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
blob, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("failed to read miner notification: %v", err)
}
var work [3]string
if err := json.Unmarshal(blob, &work); err != nil {
t.Fatalf("failed to unmarshal miner notification: %v", err)
}
sink <- work
}),
}
// Open a custom listener to extract its local address
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("failed to open notification server: %v", err)
}
defer listener.Close()
go server.Serve(listener)
// Create the custom ethash engine
ethash := NewTester([]string{"http://" + listener.Addr().String()}, false)
defer ethash.Close()
// Stream a lot of work task and ensure all the notifications bubble out
for i := 0; i < cap(sink); i++ {
header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
ethash.Seal(nil, block, nil, nil)
}
for i := 0; i < cap(sink); i++ {
select {
case <-sink:
case <-time.After(250 * time.Millisecond):
t.Fatalf("notification %d timed out", i)
}
}
}
// Tests whether stale solutions are correctly processed.
func TestStaleSubmission(t *testing.T) {
ethash := NewTester(nil, true)
defer ethash.Close()
api := &API{ethash}
fakeNonce, fakeDigest := types.BlockNonce{0x01, 0x02, 0x03}, common.HexToHash("deadbeef")
testcases := []struct {
headers []*types.Header
submitIndex int
submitRes bool
}{
// Case1: submit solution for the latest mining package
{
[]*types.Header{
{ParentHash: common.BytesToHash([]byte{0xa}), Number: big.NewInt(1), Difficulty: big.NewInt(100000000)},
},
0,
true,
},
// Case2: submit solution for the previous package but have same parent.
{
[]*types.Header{
{ParentHash: common.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000000)},
{ParentHash: common.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000001)},
},
0,
true,
},
// Case3: submit stale but acceptable solution
{
[]*types.Header{
{ParentHash: common.BytesToHash([]byte{0xc}), Number: big.NewInt(3), Difficulty: big.NewInt(100000000)},
{ParentHash: common.BytesToHash([]byte{0xd}), Number: big.NewInt(9), Difficulty: big.NewInt(100000000)},
},
0,
true,
},
// Case4: submit very old solution
{
[]*types.Header{
{ParentHash: common.BytesToHash([]byte{0xe}), Number: big.NewInt(10), Difficulty: big.NewInt(100000000)},
{ParentHash: common.BytesToHash([]byte{0xf}), Number: big.NewInt(17), Difficulty: big.NewInt(100000000)},
},
0,
false,
},
}
results := make(chan *types.Block, 16)
for id, c := range testcases {
for _, h := range c.headers {
ethash.Seal(nil, types.NewBlockWithHeader(h), results, nil)
}
if res := api.SubmitWork(fakeNonce, ethash.SealHash(c.headers[c.submitIndex]), fakeDigest); res != c.submitRes {
t.Errorf("case %d submit result mismatch, want %t, get %t", id+1, c.submitRes, res)
}
if !c.submitRes {
continue
}
select {
case res := <-results:
if res.Header().Nonce != fakeNonce {
t.Errorf("case %d block nonce mismatch, want %s, get %s", id+1, fakeNonce, res.Header().Nonce)
}
if res.Header().MixDigest != fakeDigest {
t.Errorf("case %d block digest mismatch, want %s, get %s", id+1, fakeDigest, res.Header().MixDigest)
}
if res.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() {
t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Header().Difficulty)
}
if res.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() {
t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Header().Number.Uint64())
}
if res.Header().ParentHash != c.headers[c.submitIndex].ParentHash {
t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Header().ParentHash.Hex())
}
case <-time.NewTimer(time.Second).C:
t.Errorf("case %d fetch ethash result timeout", id+1)
}
}
}

View file

@ -314,7 +314,7 @@ func (c *Console) Interactive() {
input = "" // Current user input input = "" // Current user input
scheduler = make(chan string) // Channel to send the next prompt on and receive the input scheduler = make(chan string) // Channel to send the next prompt on and receive the input
) )
// Start a goroutine to listen for promt requests and send back inputs // Start a goroutine to listen for prompt requests and send back inputs
go func() { go func() {
for { for {
// Read the next user input // Read the next user input

View file

@ -201,7 +201,7 @@ func TestInteractive(t *testing.T) {
go tester.console.Interactive() go tester.console.Interactive()
// Wait for a promt and send a statement back // Wait for a prompt and send a statement back
select { select {
case <-tester.input.scheduler: case <-tester.input.scheduler:
case <-time.After(time.Second): case <-time.After(time.Second):
@ -212,7 +212,7 @@ func TestInteractive(t *testing.T) {
case <-time.After(time.Second): case <-time.After(time.Second):
t.Fatalf("input feedback timeout") t.Fatalf("input feedback timeout")
} }
// Wait for the second promt and ensure first statement was evaluated // Wait for the second prompt and ensure first statement was evaluated
select { select {
case <-tester.input.scheduler: case <-tester.input.scheduler:
case <-time.After(time.Second): case <-time.After(time.Second):
@ -249,7 +249,7 @@ func TestExecute(t *testing.T) {
} }
// Tests that the JavaScript objects returned by statement executions are properly // Tests that the JavaScript objects returned by statement executions are properly
// pretty printed instead of just displaing "[object]". // pretty printed instead of just displaying "[object]".
func TestPrettyPrint(t *testing.T) { func TestPrettyPrint(t *testing.T) {
tester := newTester(t, nil) tester := newTester(t, nil)
defer tester.Close(t) defer tester.Close(t)
@ -300,7 +300,7 @@ func TestIndenting(t *testing.T) {
}{ }{
{`var a = 1;`, 0}, {`var a = 1;`, 0},
{`"some string"`, 0}, {`"some string"`, 0},
{`"some string with (parentesis`, 0}, {`"some string with (parenthesis`, 0},
{`"some string with newline {`"some string with newline
("`, 0}, ("`, 0},
{`function v(a,b) {}`, 0}, {`function v(a,b) {}`, 0},

View file

@ -27,7 +27,7 @@ import (
// Only this reader may be used for input because it keeps an internal buffer. // Only this reader may be used for input because it keeps an internal buffer.
var Stdin = newTerminalPrompter() var Stdin = newTerminalPrompter()
// UserPrompter defines the methods needed by the console to promt the user for // UserPrompter defines the methods needed by the console to prompt the user for
// various types of inputs. // various types of inputs.
type UserPrompter interface { type UserPrompter interface {
// PromptInput displays the given prompt to the user and requests some textual // PromptInput displays the given prompt to the user and requests some textual
@ -43,7 +43,7 @@ type UserPrompter interface {
// choice to be made, returning that choice. // choice to be made, returning that choice.
PromptConfirm(prompt string) (bool, error) PromptConfirm(prompt string) (bool, error)
// SetHistory sets the the input scrollback history that the prompter will allow // SetHistory sets the input scrollback history that the prompter will allow
// the user to scroll back to. // the user to scroll back to.
SetHistory(history []string) SetHistory(history []string)
@ -149,7 +149,7 @@ func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) {
return false, err return false, err
} }
// SetHistory sets the the input scrollback history that the prompter will allow // SetHistory sets the input scrollback history that the prompter will allow
// the user to scroll back to. // the user to scroll back to.
func (p *terminalPrompter) SetHistory(history []string) { func (p *terminalPrompter) SetHistory(history []string) {
p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n"))) p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n")))

View file

@ -46,7 +46,7 @@ func newTestBackend() *backends.SimulatedBackend {
addr0: {Balance: big.NewInt(1000000000)}, addr0: {Balance: big.NewInt(1000000000)},
addr1: {Balance: big.NewInt(1000000000)}, addr1: {Balance: big.NewInt(1000000000)},
addr2: {Balance: big.NewInt(1000000000)}, addr2: {Balance: big.NewInt(1000000000)},
}) }, 10000000)
} }
func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) { func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) {

View file

@ -40,7 +40,7 @@ var (
) )
func main() { func main() {
backend := backends.NewSimulatedBackend(testAlloc) backend := backends.NewSimulatedBackend(testAlloc, uint64(100000000))
auth := bind.NewKeyedTransactor(testKey) auth := bind.NewKeyedTransactor(testKey)
// Deploy the contract, get the code. // Deploy the contract, get the code.

View file

@ -35,7 +35,7 @@ var (
) )
func TestENS(t *testing.T) { func TestENS(t *testing.T) {
contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}) contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000)
transactOpts := bind.NewKeyedTransactor(key) transactOpts := bind.NewKeyedTransactor(key)
ensAddr, ens, err := DeployENS(transactOpts, contractBackend) ensAddr, ens, err := DeployENS(transactOpts, contractBackend)

View file

@ -111,7 +111,8 @@ func init() {
func genTxRing(naccounts int) func(int, *BlockGen) { func genTxRing(naccounts int) func(int, *BlockGen) {
from := 0 from := 0
return func(i int, gen *BlockGen) { return func(i int, gen *BlockGen) {
gas := CalcGasLimit(gen.PrevBlock(i - 1)) block := gen.PrevBlock(i - 1)
gas := CalcGasLimit(block, block.GasLimit(), block.GasLimit())
for { for {
gas -= params.TxGas gas -= params.TxGas
if gas < params.TxGas { if gas < params.TxGas {

View file

@ -45,7 +45,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
return validator return validator
} }
// ValidateBody validates the given block's uncles and verifies the the block // ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already // header's transaction and uncle roots. The headers are assumed to be already
// validated at this point. // validated at this point.
func (v *BlockValidator) ValidateBody(block *types.Block) error { func (v *BlockValidator) ValidateBody(block *types.Block) error {
@ -101,9 +101,11 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
return nil return nil
} }
// CalcGasLimit computes the gas limit of the next block after parent. // CalcGasLimit computes the gas limit of the next block after parent. It aims
// This is miner strategy, not consensus protocol. // to keep the baseline gas above the provided floor, and increase it towards the
func CalcGasLimit(parent *types.Block) uint64 { // ceil if the blocks are full. If the ceil is exceeded, it will always decrease
// the gas allowance.
func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 {
// contrib = (parentGasUsed * 3 / 2) / 1024 // contrib = (parentGasUsed * 3 / 2) / 1024
contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor
@ -121,12 +123,16 @@ func CalcGasLimit(parent *types.Block) uint64 {
if limit < params.MinGasLimit { if limit < params.MinGasLimit {
limit = params.MinGasLimit limit = params.MinGasLimit
} }
// however, if we're now below the target (TargetGasLimit) we increase the // If we're outside our allowed gas range, we try to hone towards them
// limit as much as we can (parentGasLimit / 1024 -1) if limit < gasFloor {
if limit < params.TargetGasLimit {
limit = parent.GasLimit() + decay limit = parent.GasLimit() + decay
if limit > params.TargetGasLimit { if limit > gasFloor {
limit = params.TargetGasLimit limit = gasFloor
}
} else if limit > gasCeil {
limit = parent.GasLimit() - decay
if limit < gasCeil {
limit = gasCeil
} }
} }
return limit return limit

View file

@ -450,15 +450,19 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
} }
log.Info("Exporting batch of blocks", "count", last-first+1) log.Info("Exporting batch of blocks", "count", last-first+1)
start, reported := time.Now(), time.Now()
for nr := first; nr <= last; nr++ { for nr := first; nr <= last; nr++ {
block := bc.GetBlockByNumber(nr) block := bc.GetBlockByNumber(nr)
if block == nil { if block == nil {
return fmt.Errorf("export failed on #%d: not found", nr) return fmt.Errorf("export failed on #%d: not found", nr)
} }
if err := block.EncodeRLP(w); err != nil { if err := block.EncodeRLP(w); err != nil {
return err return err
} }
if time.Since(reported) >= statsReportLimit {
log.Info("Exporting blocks", "exported", block.NumberU64()-first, "elapsed", common.PrettyDuration(time.Since(start)))
reported = time.Now()
}
} }
return nil return nil
@ -895,9 +899,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd); err != nil { if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd); err != nil {
return NonStatTy, err return NonStatTy, err
} }
// Write other block data using a batch. rawdb.WriteBlock(bc.db, block)
batch := bc.db.NewBatch()
rawdb.WriteBlock(batch, block)
root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
if err != nil { if err != nil {
@ -951,6 +953,9 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
} }
} }
} }
// Write other block data using a batch.
batch := bc.db.NewBatch()
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
// If the total difficulty is higher than our known, add it to the canonical chain // If the total difficulty is higher than our known, add it to the canonical chain
@ -1012,7 +1017,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())
@ -1203,8 +1208,8 @@ type insertStats struct {
startTime mclock.AbsTime startTime mclock.AbsTime
} }
// statsReportLimit is the time limit during import after which we always print // statsReportLimit is the time limit during import and export after which we
// out progress. This avoids the user wondering what's going on. // always print out progress. This avoids the user wondering what's going on.
const statsReportLimit = 8 * time.Second const statsReportLimit = 8 * time.Second
// report prints statistics if some number of blocks have been processed // report prints statistics if some number of blocks have been processed

View file

@ -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)

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"sync" "sync"
@ -37,11 +38,11 @@ import (
type ChainIndexerBackend interface { type ChainIndexerBackend interface {
// Reset initiates the processing of a new chain segment, potentially terminating // Reset initiates the processing of a new chain segment, potentially terminating
// any partially completed operations (in case of a reorg). // any partially completed operations (in case of a reorg).
Reset(section uint64, prevHead common.Hash) error Reset(ctx context.Context, section uint64, prevHead common.Hash) error
// Process crunches through the next header in the chain segment. The caller // Process crunches through the next header in the chain segment. The caller
// will ensure a sequential order of headers. // will ensure a sequential order of headers.
Process(header *types.Header) Process(ctx context.Context, header *types.Header) error
// Commit finalizes the section metadata and stores it into the database. // Commit finalizes the section metadata and stores it into the database.
Commit() error Commit() error
@ -74,6 +75,8 @@ type ChainIndexer struct {
active uint32 // Flag whether the event loop was started active uint32 // Flag whether the event loop was started
update chan struct{} // Notification channel that headers should be processed update chan struct{} // Notification channel that headers should be processed
quit chan chan error // Quit channel to tear down running goroutines quit chan chan error // Quit channel to tear down running goroutines
ctx context.Context
ctxCancel func()
sectionSize uint64 // Number of blocks in a single chain segment to process sectionSize uint64 // Number of blocks in a single chain segment to process
confirmsReq uint64 // Number of confirmations before processing a completed segment confirmsReq uint64 // Number of confirmations before processing a completed segment
@ -82,6 +85,9 @@ type ChainIndexer struct {
knownSections uint64 // Number of sections known to be complete (block wise) knownSections uint64 // Number of sections known to be complete (block wise)
cascadedHead uint64 // Block number of the last completed section cascaded to subindexers cascadedHead uint64 // Block number of the last completed section cascaded to subindexers
checkpointSections uint64 // Number of sections covered by the checkpoint
checkpointHead common.Hash // Section head belonging to the checkpoint
throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources
log log.Logger log log.Logger
@ -105,17 +111,26 @@ func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBacken
} }
// Initialize database dependent fields and start the updater // Initialize database dependent fields and start the updater
c.loadValidSections() c.loadValidSections()
c.ctx, c.ctxCancel = context.WithCancel(context.Background())
go c.updateLoop() go c.updateLoop()
return c return c
} }
// AddKnownSectionHead marks a new section head as known/processed if it is newer // AddCheckpoint adds a checkpoint. Sections are never processed and the chain
// than the already known best section head // is not expected to be available before this point. The indexer assumes that
func (c *ChainIndexer) AddKnownSectionHead(section uint64, shead common.Hash) { // the backend has sufficient information available to process subsequent sections.
//
// Note: knownSections == 0 and storedSections == checkpointSections until
// syncing reaches the checkpoint
func (c *ChainIndexer) AddCheckpoint(section uint64, shead common.Hash) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
c.checkpointSections = section + 1
c.checkpointHead = shead
if section < c.storedSections { if section < c.storedSections {
return return
} }
@ -138,6 +153,8 @@ func (c *ChainIndexer) Start(chain ChainIndexerChain) {
func (c *ChainIndexer) Close() error { func (c *ChainIndexer) Close() error {
var errs []error var errs []error
c.ctxCancel()
// Tear down the primary update loop // Tear down the primary update loop
errc := make(chan error) errc := make(chan error)
c.quit <- errc c.quit <- errc
@ -226,16 +243,23 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) {
// If a reorg happened, invalidate all sections until that point // If a reorg happened, invalidate all sections until that point
if reorg { if reorg {
// Revert the known section number to the reorg point // Revert the known section number to the reorg point
changed := head / c.sectionSize known := head / c.sectionSize
if changed < c.knownSections { stored := known
c.knownSections = changed if known < c.checkpointSections {
known = 0
}
if stored < c.checkpointSections {
stored = c.checkpointSections
}
if known < c.knownSections {
c.knownSections = known
} }
// Revert the stored sections from the database to the reorg point // Revert the stored sections from the database to the reorg point
if changed < c.storedSections { if stored < c.storedSections {
c.setValidSections(changed) c.setValidSections(stored)
} }
// Update the new head number to the finalized section end and notify children // Update the new head number to the finalized section end and notify children
head = changed * c.sectionSize head = known * c.sectionSize
if head < c.cascadedHead { if head < c.cascadedHead {
c.cascadedHead = head c.cascadedHead = head
@ -249,7 +273,18 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) {
var sections uint64 var sections uint64
if head >= c.confirmsReq { if head >= c.confirmsReq {
sections = (head + 1 - c.confirmsReq) / c.sectionSize sections = (head + 1 - c.confirmsReq) / c.sectionSize
if sections < c.checkpointSections {
sections = 0
}
if sections > c.knownSections { if sections > c.knownSections {
if c.knownSections < c.checkpointSections {
// syncing reached the checkpoint, verify section head
syncedHead := rawdb.ReadCanonicalHash(c.chainDb, c.checkpointSections*c.sectionSize-1)
if syncedHead != c.checkpointHead {
c.log.Error("Synced chain does not match checkpoint", "number", c.checkpointSections*c.sectionSize-1, "expected", c.checkpointHead, "synced", syncedHead)
return
}
}
c.knownSections = sections c.knownSections = sections
select { select {
@ -297,6 +332,12 @@ func (c *ChainIndexer) updateLoop() {
c.lock.Unlock() c.lock.Unlock()
newHead, err := c.processSection(section, oldHead) newHead, err := c.processSection(section, oldHead)
if err != nil { if err != nil {
select {
case <-c.ctx.Done():
<-c.quit <- nil
return
default:
}
c.log.Error("Section processing failed", "error", err) c.log.Error("Section processing failed", "error", err)
} }
c.lock.Lock() c.lock.Lock()
@ -309,7 +350,6 @@ func (c *ChainIndexer) updateLoop() {
updating = false updating = false
c.log.Info("Finished upgrading chain index") c.log.Info("Finished upgrading chain index")
} }
c.cascadedHead = c.storedSections*c.sectionSize - 1 c.cascadedHead = c.storedSections*c.sectionSize - 1
for _, child := range c.children { for _, child := range c.children {
c.log.Trace("Cascading chain index update", "head", c.cascadedHead) c.log.Trace("Cascading chain index update", "head", c.cascadedHead)
@ -344,7 +384,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
// Reset and partial processing // Reset and partial processing
if err := c.backend.Reset(section, lastHead); err != nil { if err := c.backend.Reset(c.ctx, section, lastHead); err != nil {
c.setValidSections(0) c.setValidSections(0)
return common.Hash{}, err return common.Hash{}, err
} }
@ -360,11 +400,12 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
} else if header.ParentHash != lastHead { } else if header.ParentHash != lastHead {
return common.Hash{}, fmt.Errorf("chain reorged during section processing") return common.Hash{}, fmt.Errorf("chain reorged during section processing")
} }
c.backend.Process(header) if err := c.backend.Process(c.ctx, header); err != nil {
return common.Hash{}, err
}
lastHead = header.Hash() lastHead = header.Hash()
} }
if err := c.backend.Commit(); err != nil { if err := c.backend.Commit(); err != nil {
c.log.Error("Section commit failed", "error", err)
return common.Hash{}, err return common.Hash{}, err
} }
return lastHead, nil return lastHead, nil
@ -388,8 +429,14 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
c.children = append(c.children, indexer) c.children = append(c.children, indexer)
// Cascade any pending updates to new children too // Cascade any pending updates to new children too
if c.storedSections > 0 { sections := c.storedSections
indexer.newHead(c.storedSections*c.sectionSize-1, false) if c.knownSections < sections {
// if a section is "stored" but not "known" then it is a checkpoint without
// available chain data so we should not cascade it yet
sections = c.knownSections
}
if sections > 0 {
indexer.newHead(sections*c.sectionSize-1, false)
} }
} }

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"context"
"fmt" "fmt"
"math/big" "math/big"
"math/rand" "math/rand"
@ -210,13 +211,13 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 {
return b.stored * b.indexer.sectionSize return b.stored * b.indexer.sectionSize
} }
func (b *testChainIndexBackend) Reset(section uint64, prevHead common.Hash) error { func (b *testChainIndexBackend) Reset(ctx context.Context, section uint64, prevHead common.Hash) error {
b.section = section b.section = section
b.headerCnt = 0 b.headerCnt = 0
return nil return nil
} }
func (b *testChainIndexBackend) Process(header *types.Header) { func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Header) error {
b.headerCnt++ b.headerCnt++
if b.headerCnt > b.indexer.sectionSize { if b.headerCnt > b.indexer.sectionSize {
b.t.Error("Processing too many headers") b.t.Error("Processing too many headers")
@ -227,6 +228,7 @@ func (b *testChainIndexBackend) Process(header *types.Header) {
b.t.Fatal("Unexpected call to Process") b.t.Fatal("Unexpected call to Process")
case b.processCh <- header.Number.Uint64(): case b.processCh <- header.Number.Uint64():
} }
return nil
} }
func (b *testChainIndexBackend) Commit() error { func (b *testChainIndexBackend) Commit() error {

View file

@ -240,7 +240,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
Difficulty: parent.Difficulty(), Difficulty: parent.Difficulty(),
UncleHash: parent.UncleHash(), UncleHash: parent.UncleHash(),
}), }),
GasLimit: CalcGasLimit(parent), GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()),
Number: new(big.Int).Add(parent.Number(), common.Big1), Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time, Time: time,
} }

View file

@ -29,9 +29,6 @@ type PendingLogsEvent struct {
Logs []*types.Log Logs []*types.Log
} }
// PendingStateEvent is posted pre mining and notifies of pending state changes.
type PendingStateEvent struct{}
// NewMinedBlockEvent is posted when a block has been imported. // NewMinedBlockEvent is posted when a block has been imported.
type NewMinedBlockEvent struct{ Block *types.Block } type NewMinedBlockEvent struct{ Block *types.Block }

View file

@ -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())

View file

@ -489,10 +489,13 @@ func (self *StateDB) Copy() *StateDB {
state.stateObjectsDirty[addr] = struct{}{} state.stateObjectsDirty[addr] = struct{}{}
} }
} }
for hash, logs := range self.logs { for hash, logs := range self.logs {
state.logs[hash] = make([]*types.Log, len(logs)) cpy := make([]*types.Log, len(logs))
copy(state.logs[hash], logs) for i, l := range logs {
cpy[i] = new(types.Log)
*cpy[i] = *l
}
state.logs[hash] = cpy
} }
for hash, preimage := range self.preimages { for hash, preimage := range self.preimages {
state.preimages[hash] = preimage state.preimages[hash] = preimage

View file

@ -61,7 +61,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
allLogs []*types.Log allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit()) gp = new(GasPool).AddGas(block.GasLimit())
) )
// Mutate the the block and state according to any hard-fork specs // Mutate the block and state according to any hard-fork specs
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(statedb) misc.ApplyDAOHardFork(statedb)
} }

View file

@ -22,7 +22,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
// senderCacher is a concurrent tranaction sender recoverer anc cacher. // senderCacher is a concurrent transaction sender recoverer anc cacher.
var senderCacher = newTxSenderCacher(runtime.NumCPU()) var senderCacher = newTxSenderCacher(runtime.NumCPU())
// txSenderCacherRequest is a request for recovering transaction senders with a // txSenderCacherRequest is a request for recovering transaction senders with a
@ -45,7 +45,7 @@ type txSenderCacher struct {
} }
// newTxSenderCacher creates a new transaction sender background cacher and starts // newTxSenderCacher creates a new transaction sender background cacher and starts
// as many procesing goroutines as allowed by the GOMAXPROCS on construction. // as many processing goroutines as allowed by the GOMAXPROCS on construction.
func newTxSenderCacher(threads int) *txSenderCacher { func newTxSenderCacher(threads int) *txSenderCacher {
cacher := &txSenderCacher{ cacher := &txSenderCacher{
tasks: make(chan *txSenderCacherRequest, threads), tasks: make(chan *txSenderCacherRequest, threads),

View file

@ -34,7 +34,7 @@ var errNoActiveJournal = errors.New("no active journal")
// devNull is a WriteCloser that just discards anything written into it. Its // devNull is a WriteCloser that just discards anything written into it. Its
// goal is to allow the transaction journal to write into a fake journal when // goal is to allow the transaction journal to write into a fake journal when
// loading transactions on startup without printing warnings due to no file // loading transactions on startup without printing warnings due to no file
// being readt for write. // being read for write.
type devNull struct{} type devNull struct{}
func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil } func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil }
@ -57,7 +57,7 @@ func newTxJournal(path string) *txJournal {
// load parses a transaction journal dump from disk, loading its contents into // load parses a transaction journal dump from disk, loading its contents into
// the specified pool. // the specified pool.
func (journal *txJournal) load(add func([]*types.Transaction) []error) error { func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
// Skip the parsing if the journal file doens't exist at all // Skip the parsing if the journal file doesn't exist at all
if _, err := os.Stat(journal.path); os.IsNotExist(err) { if _, err := os.Stat(journal.path); os.IsNotExist(err) {
return nil return nil
} }
@ -78,7 +78,7 @@ func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
// Create a method to load a limited batch of transactions and bump the // Create a method to load a limited batch of transactions and bump the
// appropriate progress counters. Then use this method to load all the // appropriate progress counters. Then use this method to load all the
// journalled transactions in small-ish batches. // journaled transactions in small-ish batches.
loadBatch := func(txs types.Transactions) { loadBatch := func(txs types.Transactions) {
for _, err := range add(txs) { for _, err := range add(txs) {
if err != nil { if err != nil {
@ -103,7 +103,7 @@ func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
} }
break break
} }
// New transaction parsed, queue up for later, import if threnshold is reached // New transaction parsed, queue up for later, import if threshold is reached
total++ total++
if batch = append(batch, tx); batch.Len() > 1024 { if batch = append(batch, tx); batch.Len() > 1024 {

View file

@ -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

View file

@ -123,6 +123,7 @@ type blockChain interface {
// TxPoolConfig are the configuration parameters of the transaction pool. // TxPoolConfig are the configuration parameters of the transaction pool.
type TxPoolConfig struct { type TxPoolConfig struct {
Locals []common.Address // Addresses that should be treated by default as local
NoLocals bool // Whether local transaction handling should be disabled NoLocals bool // Whether local transaction handling should be disabled
Journal string // Journal of local transactions to survive node restarts Journal string // Journal of local transactions to survive node restarts
Rejournal time.Duration // Time interval to regenerate the local transaction journal Rejournal time.Duration // Time interval to regenerate the local transaction journal
@ -231,6 +232,10 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
gasPrice: new(big.Int).SetUint64(config.PriceLimit), gasPrice: new(big.Int).SetUint64(config.PriceLimit),
} }
pool.locals = newAccountSet(pool.signer) pool.locals = newAccountSet(pool.signer)
for _, addr := range config.Locals {
log.Info("Setting new local account", "address", addr)
pool.locals.add(addr)
}
pool.priced = newTxPricedList(pool.all) pool.priced = newTxPricedList(pool.all)
pool.reset(nil, chain.CurrentBlock().Header()) pool.reset(nil, chain.CurrentBlock().Header())
@ -534,6 +539,14 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
return pending, nil return pending, nil
} }
// Locals retrieves the accounts currently considered local by the pool.
func (pool *TxPool) Locals() []common.Address {
pool.mu.Lock()
defer pool.mu.Unlock()
return pool.locals.flatten()
}
// local retrieves all currently known local transactions, groupped by origin // local retrieves all currently known local transactions, groupped by origin
// account and sorted by nonce. The returned transaction set is a copy and can be // account and sorted by nonce. The returned transaction set is a copy and can be
// freely modified by calling code. // freely modified by calling code.
@ -665,8 +678,11 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
} }
// Mark local addresses and journal local transactions // Mark local addresses and journal local transactions
if local { if local {
if !pool.locals.contains(from) {
log.Info("Setting new local account", "address", from)
pool.locals.add(from) pool.locals.add(from)
} }
}
pool.journalTx(from, tx) pool.journalTx(from, tx)
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
@ -1138,6 +1154,7 @@ func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type accountSet struct { type accountSet struct {
accounts map[common.Address]struct{} accounts map[common.Address]struct{}
signer types.Signer signer types.Signer
cache *[]common.Address
} }
// newAccountSet creates a new address set with an associated signer for sender // newAccountSet creates a new address set with an associated signer for sender
@ -1167,6 +1184,20 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool {
// add inserts a new address into the set to track. // add inserts a new address into the set to track.
func (as *accountSet) add(addr common.Address) { func (as *accountSet) add(addr common.Address) {
as.accounts[addr] = struct{}{} as.accounts[addr] = struct{}{}
as.cache = nil
}
// flatten returns the list of addresses within this set, also caching it for later
// reuse. The returned slice should not be changed!
func (as *accountSet) flatten() []common.Address {
if as.cache == nil {
accounts := make([]common.Address, 0, len(as.accounts))
for account := range as.accounts {
accounts = append(accounts, account)
}
as.cache = &accounts
}
return *as.cache
} }
// txLookup is used internally by TxPool to track transactions while allowing lookup without // txLookup is used internally by TxPool to track transactions while allowing lookup without

View file

@ -102,25 +102,6 @@ func (h *Header) Hash() common.Hash {
return rlpHash(h) return rlpHash(h)
} }
// HashNoNonce returns the hash which is used as input for the proof-of-work search.
func (h *Header) HashNoNonce() common.Hash {
return rlpHash([]interface{}{
h.ParentHash,
h.UncleHash,
h.Coinbase,
h.Root,
h.TxHash,
h.ReceiptHash,
h.Bloom,
h.Difficulty,
h.Number,
h.GasLimit,
h.GasUsed,
h.Time,
h.Extra,
})
}
// Size returns the approximate memory used by all internal contents. It is used // Size returns the approximate memory used by all internal contents. It is used
// to approximate and limit the memory consumption of various caches. // to approximate and limit the memory consumption of various caches.
func (h *Header) Size() common.StorageSize { func (h *Header) Size() common.StorageSize {
@ -324,10 +305,6 @@ func (b *Block) Header() *Header { return CopyHeader(b.header) }
// Body returns the non-header content of the block. // Body returns the non-header content of the block.
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} } func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
func (b *Block) HashNoNonce() common.Hash {
return b.header.HashNoNonce()
}
// Size returns the true RLP encoded storage size of the block, either by encoding // Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previsouly cached value. // and returning it, or returning a previsouly cached value.
func (b *Block) Size() common.StorageSize { func (b *Block) Size() common.StorageSize {

View file

@ -119,7 +119,7 @@ func isProtectedV(V *big.Int) bool {
v := V.Uint64() v := V.Uint64()
return v != 27 && v != 28 return v != 27 && v != 28
} }
// anything not 27 or 28 are considered unprotected // anything not 27 or 28 is considered protected
return true return true
} }

View file

@ -40,7 +40,7 @@ type AccountRef common.Address
func (ar AccountRef) Address() common.Address { return (common.Address)(ar) } func (ar AccountRef) Address() common.Address { return (common.Address)(ar) }
// Contract represents an ethereum contract in the state database. It contains // Contract represents an ethereum contract in the state database. It contains
// the the contract code, calling arguments. Contract implements ContractRef // the contract code, calling arguments. Contract implements ContractRef
type Contract struct { type Contract struct {
// CallerAddress is the result of the caller which initialised this // CallerAddress is the result of the caller which initialised this
// contract. However when the "call method" is delegated this value // contract. However when the "call method" is delegated this value

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