mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
Merge pull request #1 from ethereum/master
Pull latest changes from go-ethereum
This commit is contained in:
commit
96d8a3a201
301 changed files with 12836 additions and 5337 deletions
4
.github/no-response.yml
vendored
4
.github/no-response.yml
vendored
|
|
@ -7,5 +7,5 @@ closeComment: >
|
||||||
This issue has been automatically closed because there has been no response
|
This issue has been automatically closed because there has been no response
|
||||||
to our request for more information from the original author. With only the
|
to our request for more information from the original author. With only the
|
||||||
information that is currently in the issue, we don't have enough information
|
information that is currently in the issue, we don't have enough information
|
||||||
to take action. Please reach out if you have or find the answers we need so
|
to take action. Please reach out if you have more relevant information or
|
||||||
that we can investigate further.
|
answers to our questions so that we can investigate further.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/6874
|
||||||
)](https://godoc.org/github.com/ethereum/go-ethereum)
|
)](https://godoc.org/github.com/ethereum/go-ethereum)
|
||||||
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
||||||
[](https://travis-ci.org/ethereum/go-ethereum)
|
[](https://travis-ci.org/ethereum/go-ethereum)
|
||||||
[](https://gitter.im/ethereum/go-ethereum?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://discord.gg/nthXNEv)
|
||||||
|
|
||||||
Automated builds are available for stable releases and the unstable master branch.
|
Automated builds are available for stable releases and the unstable master branch.
|
||||||
Binary archives are published at https://geth.ethereum.org/downloads/.
|
Binary archives are published at https://geth.ethereum.org/downloads/.
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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{})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,17 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxUint256 = big.NewInt(0).Add(
|
||||||
|
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil),
|
||||||
|
big.NewInt(-1))
|
||||||
|
maxInt256 = big.NewInt(0).Add(
|
||||||
|
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil),
|
||||||
|
big.NewInt(-1))
|
||||||
|
)
|
||||||
|
|
||||||
// reads the integer based on its kind
|
// reads the integer based on its kind
|
||||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Uint8:
|
case reflect.Uint8:
|
||||||
return b[len(b)-1]
|
return b[len(b)-1]
|
||||||
|
|
@ -45,7 +54,20 @@ func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
||||||
default:
|
default:
|
||||||
return new(big.Int).SetBytes(b)
|
// the only case lefts for integer is int256/uint256.
|
||||||
|
// big.SetBytes can't tell if a number is negative, positive on itself.
|
||||||
|
// On EVM, if the returned number > max int256, it is negative.
|
||||||
|
ret := new(big.Int).SetBytes(b)
|
||||||
|
if typ == UintTy {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.Cmp(maxInt256) > 0 {
|
||||||
|
ret.Add(maxUint256, big.NewInt(0).Neg(ret))
|
||||||
|
ret.Add(ret, big.NewInt(1))
|
||||||
|
ret.Neg(ret)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +201,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
case StringTy: // variable arrays are written at the end of the return bytes
|
case StringTy: // variable arrays are written at the end of the return bytes
|
||||||
return string(output[begin : begin+end]), nil
|
return string(output[begin : begin+end]), nil
|
||||||
case IntTy, UintTy:
|
case IntTy, UintTy:
|
||||||
return readInteger(t.Kind, returnOutput), nil
|
return readInteger(t.T, t.Kind, returnOutput), nil
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
return readBool(returnOutput)
|
return readBool(returnOutput)
|
||||||
case AddressTy:
|
case AddressTy:
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,11 @@ var unpackTests = []unpackTest{
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
want: big.NewInt(1),
|
want: big.NewInt(1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256"}]`,
|
||||||
|
enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
want: big.NewInt(-1),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "address"}]`,
|
def: `[{"type": "address"}]`,
|
||||||
enc: "0000000000000000000000000100000000000000000000000000000000000000",
|
enc: "0000000000000000000000000100000000000000000000000000000000000000",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
11
build/ci.go
11
build/ci.go
|
|
@ -644,17 +644,6 @@ func (meta debMetadata) ExeName(exe debExecutable) string {
|
||||||
return exe.Package()
|
return exe.Package()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthereumSwarmPackageName returns the name of the swarm package based on
|
|
||||||
// environment, e.g. "ethereum-swarm-unstable", or "ethereum-swarm".
|
|
||||||
// This is needed so that we make sure that "ethereum" package,
|
|
||||||
// depends on and installs "ethereum-swarm"
|
|
||||||
func (meta debMetadata) EthereumSwarmPackageName() string {
|
|
||||||
if isUnstableBuild(meta.Env) {
|
|
||||||
return debSwarm.Name + "-unstable"
|
|
||||||
}
|
|
||||||
return debSwarm.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExeConflicts returns the content of the Conflicts field
|
// ExeConflicts returns the content of the Conflicts field
|
||||||
// for executable packages.
|
// for executable packages.
|
||||||
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
|
|
||||||
Package: {{.Name}}
|
Package: {{.Name}}
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${misc:Depends}, {{.EthereumSwarmPackageName}}, {{.ExeList}}
|
Depends: ${misc:Depends}, {{.ExeList}}
|
||||||
Description: Meta-package to install geth, swarm, and other tools
|
Description: Meta-package to install geth, swarm, and other tools
|
||||||
Meta-package to install geth, swarm and other tools
|
Meta-package to install geth, swarm and other tools
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ func initializeSecrets(c *cli.Context) error {
|
||||||
if _, err := os.Stat(location); err == nil {
|
if _, err := os.Stat(location); err == nil {
|
||||||
return fmt.Errorf("file %v already exists, will not overwrite", location)
|
return fmt.Errorf("file %v already exists, will not overwrite", location)
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(location, masterSeed, 0700)
|
err = ioutil.WriteFile(location, masterSeed, 0400)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -540,14 +540,14 @@ func readMasterKey(ctx *cli.Context) ([]byte, error) {
|
||||||
|
|
||||||
// checkFile is a convenience function to check if a file
|
// checkFile is a convenience function to check if a file
|
||||||
// * exists
|
// * exists
|
||||||
// * is mode 0600
|
// * is mode 0400
|
||||||
func checkFile(filename string) error {
|
func checkFile(filename string) error {
|
||||||
info, err := os.Stat(filename)
|
info, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed stat on %s: %v", filename, err)
|
return fmt.Errorf("failed stat on %s: %v", filename, err)
|
||||||
}
|
}
|
||||||
// Check the unix permission bits
|
// Check the unix permission bits
|
||||||
if info.Mode().Perm()&077 != 0 {
|
if info.Mode().Perm()&0377 != 0 {
|
||||||
return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
|
return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,33 @@ Private key information can be printed by using the `--private` flag;
|
||||||
make sure to use this feature with great caution!
|
make sure to use this feature with great caution!
|
||||||
|
|
||||||
|
|
||||||
### `ethkey sign <keyfile> <message/file>`
|
### `ethkey signmessage <keyfile> <message/file>`
|
||||||
|
|
||||||
Sign the message with a keyfile.
|
Sign the message with a keyfile.
|
||||||
It is possible to refer to a file containing the message.
|
It is possible to refer to a file containing the message.
|
||||||
|
To sign a message contained in a file, use the `--msgfile` flag.
|
||||||
|
|
||||||
|
|
||||||
### `ethkey verify <address> <signature> <message/file>`
|
### `ethkey verifymessage <address> <signature> <message/file>`
|
||||||
|
|
||||||
Verify the signature of the message.
|
Verify the signature of the message.
|
||||||
It is possible to refer to a file containing the message.
|
It is possible to refer to a file containing the message.
|
||||||
|
To sign a message contained in a file, use the --msgfile flag.
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey changepassphrase <keyfile>`
|
||||||
|
|
||||||
|
Change the passphrase of a keyfile.
|
||||||
|
use the `--newpasswordfile` to point to the new password file.
|
||||||
|
|
||||||
|
|
||||||
## Passphrases
|
## Passphrases
|
||||||
|
|
||||||
For every command that uses a keyfile, you will be prompted to provide the
|
For every command that uses a keyfile, you will be prompted to provide the
|
||||||
passphrase for decrypting the keyfile. To avoid this message, it is possible
|
passphrase for decrypting the keyfile. To avoid this message, it is possible
|
||||||
to pass the passphrase by using the `--passphrase` flag pointing to a file that
|
to pass the passphrase by using the `--passwordfile` flag pointing to a file that
|
||||||
contains the passphrase.
|
contains the passphrase.
|
||||||
|
|
||||||
|
## JSON
|
||||||
|
|
||||||
|
In case you need to output the result in a JSON format, you shall by using the `--json` flag.
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func disasmCmd(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
code := strings.TrimSpace(string(in[:]))
|
code := strings.TrimSpace(string(in))
|
||||||
fmt.Printf("%v\n", code)
|
fmt.Printf("%v\n", code)
|
||||||
return asm.PrintDisassembled(code)
|
return asm.PrintDisassembled(code)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,8 @@ func main() {
|
||||||
if blob, err = ioutil.ReadFile(*accPassFlag); err != nil {
|
if blob, err = ioutil.ReadFile(*accPassFlag); err != nil {
|
||||||
log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err)
|
log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err)
|
||||||
}
|
}
|
||||||
pass := string(blob)
|
// Delete trailing newline in password
|
||||||
|
pass := strings.TrimSuffix(string(blob), "\n")
|
||||||
|
|
||||||
ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP)
|
ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
if blob, err = ioutil.ReadFile(*accJSONFlag); err != nil {
|
if blob, err = ioutil.ReadFile(*accJSONFlag); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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,7 +86,7 @@ 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: `
|
||||||
|
|
@ -105,7 +104,7 @@ be gzipped.`,
|
||||||
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: `
|
||||||
|
|
@ -119,7 +118,7 @@ be gzipped.`,
|
||||||
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: `
|
||||||
|
|
@ -149,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: `
|
||||||
|
|
@ -163,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: `
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,9 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
|
if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
|
||||||
cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
|
cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
|
||||||
}
|
}
|
||||||
|
if ctx.GlobalIsSet(utils.WhisperRestrictConnectionBetweenLightClientsFlag.Name) {
|
||||||
|
cfg.Shh.RestrictConnectionBetweenLightClients = true
|
||||||
|
}
|
||||||
utils.RegisterShhService(stack, &cfg.Shh)
|
utils.RegisterShhService(stack, &cfg.Shh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,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,
|
||||||
|
|
@ -82,8 +83,6 @@ var (
|
||||||
utils.TxPoolAccountQueueFlag,
|
utils.TxPoolAccountQueueFlag,
|
||||||
utils.TxPoolGlobalQueueFlag,
|
utils.TxPoolGlobalQueueFlag,
|
||||||
utils.TxPoolLifetimeFlag,
|
utils.TxPoolLifetimeFlag,
|
||||||
utils.FastSyncFlag,
|
|
||||||
utils.LightModeFlag,
|
|
||||||
utils.SyncModeFlag,
|
utils.SyncModeFlag,
|
||||||
utils.GCModeFlag,
|
utils.GCModeFlag,
|
||||||
utils.LightServFlag,
|
utils.LightServFlag,
|
||||||
|
|
@ -96,11 +95,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,
|
||||||
|
|
@ -121,7 +130,6 @@ var (
|
||||||
utils.NoCompactionFlag,
|
utils.NoCompactionFlag,
|
||||||
utils.GpoBlocksFlag,
|
utils.GpoBlocksFlag,
|
||||||
utils.GpoPercentileFlag,
|
utils.GpoPercentileFlag,
|
||||||
utils.ExtraDataFlag,
|
|
||||||
configFileFlag,
|
configFileFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,6 +151,7 @@ var (
|
||||||
utils.WhisperEnabledFlag,
|
utils.WhisperEnabledFlag,
|
||||||
utils.WhisperMaxMessageSizeFlag,
|
utils.WhisperMaxMessageSizeFlag,
|
||||||
utils.WhisperMinPOWFlag,
|
utils.WhisperMinPOWFlag,
|
||||||
|
utils.WhisperRestrictConnectionBetweenLightClientsFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsFlags = []cli.Flag{
|
metricsFlags = []cli.Flag{
|
||||||
|
|
@ -229,7 +238,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,25 +331,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(ðereum); err != nil {
|
if err := stack.Service(ðereum); 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,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,
|
||||||
|
|
@ -185,10 +186,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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -230,8 +235,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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine --minerthreads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> geth.sh
|
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()
|
||||||
|
|
@ -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,6 +222,7 @@ 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
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -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))
|
||||||
|
|
@ -250,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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -87,7 +87,7 @@ func (w *wizard) makeServer() string {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectServer lists the user all the currnetly known servers to choose from,
|
// selectServer lists the user all the currently known servers to choose from,
|
||||||
// also granting the option to add a new one.
|
// also granting the option to add a new one.
|
||||||
func (w *wizard) selectServer() string {
|
func (w *wizard) selectServer() string {
|
||||||
// List the available server to the user and wait for a choice
|
// List the available server to the user and wait for a choice
|
||||||
|
|
@ -115,7 +115,7 @@ func (w *wizard) selectServer() string {
|
||||||
// manageComponents displays a list of network components the user can tear down
|
// manageComponents displays a list of network components the user can tear down
|
||||||
// and an option
|
// and an option
|
||||||
func (w *wizard) manageComponents() {
|
func (w *wizard) manageComponents() {
|
||||||
// List all the componens we can tear down, along with an entry to deploy a new one
|
// List all the components we can tear down, along with an entry to deploy a new one
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
var serviceHosts, serviceNames []string
|
var serviceHosts, serviceNames []string
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
233
cmd/swarm/access.go
Normal file
233
cmd/swarm/access.go
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// 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.DoPassword(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 {
|
||||||
|
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.DoPK(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]
|
||||||
|
pkGrantees = []string{}
|
||||||
|
passGrantees = []string{}
|
||||||
|
pkGranteesFilename = ctx.String(SwarmAccessGrantKeysFlag.Name)
|
||||||
|
passGranteesFilename = ctx.String(utils.PasswordFileFlag.Name)
|
||||||
|
privateKey = getPrivKey(ctx)
|
||||||
|
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||||
|
)
|
||||||
|
if pkGranteesFilename == "" && passGranteesFilename == "" {
|
||||||
|
utils.Fatalf("you have to provide either a grantee public-keys file or an encryption passwords file (or both)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkGranteesFilename != "" {
|
||||||
|
bytes, err := ioutil.ReadFile(pkGranteesFilename)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("had an error reading the grantee public key list")
|
||||||
|
}
|
||||||
|
pkGrantees = strings.Split(string(bytes), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if passGranteesFilename != "" {
|
||||||
|
bytes, err := ioutil.ReadFile(passGranteesFilename)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("could not read password filename: %v", err)
|
||||||
|
}
|
||||||
|
passGrantees = strings.Split(string(bytes), "\n")
|
||||||
|
}
|
||||||
|
accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees)
|
||||||
|
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
|
||||||
|
}
|
||||||
605
cmd/swarm/access_test.go
Normal file
605
cmd/swarm/access_test.go
Normal file
|
|
@ -0,0 +1,605 @@
|
||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||||
|
"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"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hashRegexp = `[a-f\d]{128}`
|
||||||
|
data = "notsorandomdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultCurve = crypto.S256()
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
|
||||||
|
dataFilename := testutil.TempFileWithContent(t, data)
|
||||||
|
defer os.RemoveAll(dataFilename)
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
tmp, err := ioutil.TempDir("", "swarm-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
password := "smth"
|
||||||
|
passwordFilename := testutil.TempFileWithContent(t, "smth")
|
||||||
|
defer os.RemoveAll(passwordFilename)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
if a.Publisher != "" {
|
||||||
|
t.Fatal("should be empty")
|
||||||
|
}
|
||||||
|
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 := testutil.TempFileWithContent(t, "just wr0ng")
|
||||||
|
defer os.RemoveAll(wrongPasswordFilename)
|
||||||
|
|
||||||
|
//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, 2)
|
||||||
|
defer cluster.Shutdown()
|
||||||
|
|
||||||
|
dataFilename := testutil.TempFileWithContent(t, data)
|
||||||
|
defer os.RemoveAll(dataFilename)
|
||||||
|
|
||||||
|
// upload the file with 'swarm up' and expect a hash
|
||||||
|
up := runSwarm(t,
|
||||||
|
"--bzzapi",
|
||||||
|
cluster.Nodes[0].URL,
|
||||||
|
"up",
|
||||||
|
"--encrypt",
|
||||||
|
dataFilename)
|
||||||
|
_, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordFilename := testutil.TempFileWithContent(t, testPassphrase)
|
||||||
|
defer os.RemoveAll(passwordFilename)
|
||||||
|
|
||||||
|
_, publisherAccount := getTestAccount(t, publisherDir)
|
||||||
|
up = runSwarm(t,
|
||||||
|
"--bzzaccount",
|
||||||
|
publisherAccount.Address.String(),
|
||||||
|
"--password",
|
||||||
|
passwordFilename,
|
||||||
|
"--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")
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the public key from the publisher directory
|
||||||
|
publicKeyFromDataDir := runSwarm(t,
|
||||||
|
"--bzzaccount",
|
||||||
|
publisherAccount.Address.String(),
|
||||||
|
"--password",
|
||||||
|
passwordFilename,
|
||||||
|
"--datadir",
|
||||||
|
publisherDir,
|
||||||
|
"print-keys",
|
||||||
|
"--compressed",
|
||||||
|
)
|
||||||
|
_, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+")
|
||||||
|
publicKeyFromDataDir.ExpectExit()
|
||||||
|
pkComp := strings.Split(publicKeyString[0], "=")[1]
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
if a.Publisher != pkComp {
|
||||||
|
t.Fatal("publisher key did not match")
|
||||||
|
}
|
||||||
|
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 creation of the ACT manifest end-to-end, without any bogus entries (i.e. default scenario = 3 nodes 1 unauthorized)
|
||||||
|
func TestAccessACT(t *testing.T) {
|
||||||
|
testAccessACT(t, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAccessACTScale tests the creation of the ACT manifest end-to-end, with 1000 bogus entries (i.e. 1000 EC keys + default scenario = 3 nodes 1 unauthorized = 1003 keys in the ACT manifest)
|
||||||
|
func TestAccessACTScale(t *testing.T) {
|
||||||
|
testAccessACT(t, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAccessACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection
|
||||||
|
// the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data
|
||||||
|
// set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access.
|
||||||
|
// the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails.
|
||||||
|
// the publisher uploads through one of the nodes then disappears.
|
||||||
|
func testAccessACT(t *testing.T, bogusEntries int) {
|
||||||
|
// Setup Swarm and upload a test file to it
|
||||||
|
const clusterSize = 3
|
||||||
|
cluster := newTestCluster(t, clusterSize)
|
||||||
|
defer cluster.Shutdown()
|
||||||
|
|
||||||
|
var uploadThroughNode = cluster.Nodes[0]
|
||||||
|
client := swarm.NewClient(uploadThroughNode.URL)
|
||||||
|
|
||||||
|
r1 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
|
||||||
|
nodeToSkip := r1.Intn(clusterSize) // a number between 0 and 2 (node indices in `cluster`)
|
||||||
|
dataFilename := testutil.TempFileWithContent(t, data)
|
||||||
|
defer os.RemoveAll(dataFilename)
|
||||||
|
|
||||||
|
// upload the file with 'swarm up' and expect a hash
|
||||||
|
up := runSwarm(t,
|
||||||
|
"--bzzapi",
|
||||||
|
cluster.Nodes[0].URL,
|
||||||
|
"up",
|
||||||
|
"--encrypt",
|
||||||
|
dataFilename)
|
||||||
|
_, 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
if bogusEntries > 0 {
|
||||||
|
bogusGrantees := []string{}
|
||||||
|
|
||||||
|
for i := 0; i < bogusEntries; i++ {
|
||||||
|
prv, err := ecies.GenerateKey(rand.Reader, DefaultCurve, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bogusGrantees = append(bogusGrantees, hex.EncodeToString(crypto.CompressPubkey(&prv.ExportECDSA().PublicKey)))
|
||||||
|
}
|
||||||
|
r2 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
|
||||||
|
for i := 0; i < len(grantees); i++ {
|
||||||
|
insertAtIdx := r2.Intn(len(bogusGrantees))
|
||||||
|
bogusGrantees = append(bogusGrantees[:insertAtIdx], append([]string{grantees[i]}, bogusGrantees[insertAtIdx:]...)...)
|
||||||
|
}
|
||||||
|
grantees = bogusGrantees
|
||||||
|
}
|
||||||
|
granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n"))
|
||||||
|
defer os.RemoveAll(granteesPubkeyListFile)
|
||||||
|
|
||||||
|
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(publisherDir)
|
||||||
|
|
||||||
|
passwordFilename := testutil.TempFileWithContent(t, testPassphrase)
|
||||||
|
defer os.RemoveAll(passwordFilename)
|
||||||
|
actPasswordFilename := testutil.TempFileWithContent(t, "smth")
|
||||||
|
defer os.RemoveAll(actPasswordFilename)
|
||||||
|
_, publisherAccount := getTestAccount(t, publisherDir)
|
||||||
|
up = runSwarm(t,
|
||||||
|
"--bzzaccount",
|
||||||
|
publisherAccount.Address.String(),
|
||||||
|
"--password",
|
||||||
|
passwordFilename,
|
||||||
|
"--datadir",
|
||||||
|
publisherDir,
|
||||||
|
"--bzzapi",
|
||||||
|
cluster.Nodes[0].URL,
|
||||||
|
"access",
|
||||||
|
"new",
|
||||||
|
"act",
|
||||||
|
"--grant-keys",
|
||||||
|
granteesPubkeyListFile,
|
||||||
|
"--password",
|
||||||
|
actPasswordFilename,
|
||||||
|
ref,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, matches = up.ExpectRegexp(`[a-f\d]{64}`)
|
||||||
|
up.ExpectExit()
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
t.Fatalf("stdout not matched")
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the public key from the publisher directory
|
||||||
|
publicKeyFromDataDir := runSwarm(t,
|
||||||
|
"--bzzaccount",
|
||||||
|
publisherAccount.Address.String(),
|
||||||
|
"--password",
|
||||||
|
passwordFilename,
|
||||||
|
"--datadir",
|
||||||
|
publisherDir,
|
||||||
|
"print-keys",
|
||||||
|
"--compressed",
|
||||||
|
)
|
||||||
|
_, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+")
|
||||||
|
publicKeyFromDataDir.ExpectExit()
|
||||||
|
pkComp := strings.Split(publicKeyString[0], "=")[1]
|
||||||
|
|
||||||
|
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.Publisher != pkComp {
|
||||||
|
t.Fatal("publisher key did not match")
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// try downloading using a password instead, using the unauthorized node
|
||||||
|
passwordUrl := strings.Replace(url, "http://", "http://:smth@", -1)
|
||||||
|
response, err = httpClient.Get(passwordUrl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
t.Fatal("should be a 200")
|
||||||
|
}
|
||||||
|
|
||||||
|
// now try with the wrong password, expect 401
|
||||||
|
passwordUrl = strings.Replace(url, "http://", "http://:smthWrong@", -1)
|
||||||
|
response, err = httpClient.Get(passwordUrl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Fatal("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
77
cmd/swarm/bootnodes.go
Normal 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",
|
||||||
|
}
|
||||||
|
|
@ -78,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -132,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
|
||||||
|
|
||||||
|
|
@ -142,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
|
||||||
}
|
}
|
||||||
|
|
@ -231,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
|
||||||
}
|
}
|
||||||
|
|
@ -332,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,12 +43,12 @@ 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) {
|
||||||
// This test fails on travis as this executable exits with code 1
|
|
||||||
// and without any log messages in the log.
|
|
||||||
// /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse
|
|
||||||
t.Skip()
|
|
||||||
|
|
||||||
cluster := newTestCluster(t, 3)
|
cluster := newTestCluster(t, 3)
|
||||||
defer cluster.Shutdown()
|
defer cluster.Shutdown()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func hash(ctx *cli.Context) {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
stat, _ := f.Stat()
|
stat, _ := f.Stat()
|
||||||
fileStore := storage.NewFileStore(storage.NewMapChunkStore(), storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams())
|
||||||
addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
|
addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("%v\n", err)
|
utils.Fatalf("%v\n", err)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -37,7 +38,6 @@ 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/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
|
@ -68,13 +68,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 (
|
||||||
|
|
@ -155,6 +148,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",
|
||||||
|
|
@ -167,6 +168,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 ',')",
|
||||||
|
|
@ -199,6 +209,10 @@ var (
|
||||||
Name: "data",
|
Name: "data",
|
||||||
Usage: "Initializes the resource with the given hex-encoded data. Data must be prefixed by 0x",
|
Usage: "Initializes the resource with the given hex-encoded data. Data must be prefixed by 0x",
|
||||||
}
|
}
|
||||||
|
SwarmCompressedFlag = cli.BoolFlag{
|
||||||
|
Name: "compressed",
|
||||||
|
Usage: "Prints encryption keys in compressed form",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
//declare a few constant error messages, useful for later error check comparisons in test
|
//declare a few constant error messages, useful for later error check comparisons in test
|
||||||
|
|
@ -243,6 +257,14 @@ func init() {
|
||||||
Usage: "Print version numbers",
|
Usage: "Print version numbers",
|
||||||
Description: "The output of this command is supposed to be machine-readable",
|
Description: "The output of this command is supposed to be machine-readable",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Action: keys,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "print-keys",
|
||||||
|
Flags: []cli.Flag{SwarmCompressedFlag},
|
||||||
|
Usage: "Print public key information",
|
||||||
|
Description: "The output of this command is supposed to be machine-readable",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Action: upload,
|
Action: upload,
|
||||||
CustomHelpTemplate: helpTemplate,
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
|
@ -252,6 +274,62 @@ 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,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
|
@ -306,14 +384,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,
|
||||||
|
|
@ -322,23 +397,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",
|
||||||
|
|
@ -413,16 +488,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,
|
||||||
|
|
@ -521,6 +594,17 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keys(ctx *cli.Context) error {
|
||||||
|
privateKey := getPrivKey(ctx)
|
||||||
|
pub := hex.EncodeToString(crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||||
|
pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
|
||||||
|
if !ctx.Bool(SwarmCompressedFlag.Name) {
|
||||||
|
fmt.Println(fmt.Sprintf("publicKey=%s", pub))
|
||||||
|
}
|
||||||
|
fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
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:", sv.VersionWithMeta)
|
fmt.Println("Version:", sv.VersionWithMeta)
|
||||||
|
|
@ -535,6 +619,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)
|
||||||
|
|
@ -551,12 +636,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)
|
||||||
|
|
@ -574,16 +663,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
|
||||||
}
|
}
|
||||||
|
|
@ -691,17 +770,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.
|
||||||
|
|
@ -714,3 +782,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))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
579
cmd/swarm/manifest_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ func main() {
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "cluster-endpoint",
|
Name: "cluster-endpoint",
|
||||||
Value: "testing",
|
Value: "testing",
|
||||||
Usage: "cluster to point to (open, or testing)",
|
Usage: "cluster to point to (local, open or testing)",
|
||||||
Destination: &cluster,
|
Destination: &cluster,
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
|
|
@ -76,8 +76,8 @@ func main() {
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "filesize",
|
Name: "filesize",
|
||||||
Value: 1,
|
Value: 1024,
|
||||||
Usage: "file size for generated random file in MB",
|
Usage: "file size for generated random file in KB",
|
||||||
Destination: &filesize,
|
Destination: &filesize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ import (
|
||||||
func generateEndpoints(scheme string, cluster string, from int, to int) {
|
func generateEndpoints(scheme string, cluster string, from int, to int) {
|
||||||
if cluster == "prod" {
|
if cluster == "prod" {
|
||||||
cluster = ""
|
cluster = ""
|
||||||
|
} else if cluster == "local" {
|
||||||
|
for port := from; port <= to; port++ {
|
||||||
|
endpoints = append(endpoints, fmt.Sprintf("%s://localhost:%v", scheme, port))
|
||||||
|
}
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
cluster = cluster + "."
|
cluster = cluster + "."
|
||||||
}
|
}
|
||||||
|
|
@ -53,13 +58,13 @@ func generateEndpoints(scheme string, cluster string, from int, to int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cliUploadAndSync(c *cli.Context) error {
|
func cliUploadAndSync(c *cli.Context) error {
|
||||||
defer func(now time.Time) { log.Info("total time", "time", time.Since(now), "size", filesize) }(time.Now())
|
defer func(now time.Time) { log.Info("total time", "time", time.Since(now), "size (kb)", filesize) }(time.Now())
|
||||||
|
|
||||||
generateEndpoints(scheme, cluster, from, to)
|
generateEndpoints(scheme, cluster, from, to)
|
||||||
|
|
||||||
log.Info("uploading to " + endpoints[0] + " and syncing")
|
log.Info("uploading to " + endpoints[0] + " and syncing")
|
||||||
|
|
||||||
f, cleanup := generateRandomFile(filesize * 1000000)
|
f, cleanup := generateRandomFile(filesize * 1000)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
hash, err := upload(f, endpoints[0])
|
hash, err := upload(f, endpoints[0])
|
||||||
|
|
@ -76,12 +81,7 @@ func cliUploadAndSync(c *cli.Context) error {
|
||||||
|
|
||||||
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash))
|
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash))
|
||||||
|
|
||||||
if filesize < 10 {
|
time.Sleep(3 * time.Second)
|
||||||
time.Sleep(35 * time.Second)
|
|
||||||
} else {
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
time.Sleep(2 * time.Duration(filesize) * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
|
|
@ -109,7 +109,7 @@ func cliUploadAndSync(c *cli.Context) error {
|
||||||
// fetch is getting the requested `hash` from the `endpoint` and compares it with the `original` file
|
// fetch is getting the requested `hash` from the `endpoint` and compares it with the `original` file
|
||||||
func fetch(hash string, endpoint string, original []byte, ruid string) error {
|
func fetch(hash string, endpoint string, original []byte, ruid string) error {
|
||||||
log.Trace("sleeping", "ruid", ruid)
|
log.Trace("sleeping", "ruid", ruid)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
log.Trace("http get request", "ruid", ruid, "api", endpoint, "hash", hash)
|
log.Trace("http get request", "ruid", ruid, "api", endpoint, "hash", hash)
|
||||||
res, err := http.Get(endpoint + "/bzz:/" + hash + "/")
|
res, err := http.Get(endpoint + "/bzz:/" + hash + "/")
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -158,14 +157,6 @@ var (
|
||||||
Usage: "Document Root for HTTPClient file scheme",
|
Usage: "Document Root for HTTPClient file scheme",
|
||||||
Value: DirectoryString{homeDir()},
|
Value: DirectoryString{homeDir()},
|
||||||
}
|
}
|
||||||
FastSyncFlag = cli.BoolFlag{
|
|
||||||
Name: "fast",
|
|
||||||
Usage: "Enable fast syncing through state downloads (replaced by --syncmode)",
|
|
||||||
}
|
|
||||||
LightModeFlag = cli.BoolFlag{
|
|
||||||
Name: "light",
|
|
||||||
Usage: "Enable light client mode (replaced by --syncmode)",
|
|
||||||
}
|
|
||||||
defaultSyncMode = eth.DefaultConfig.SyncMode
|
defaultSyncMode = eth.DefaultConfig.SyncMode
|
||||||
SyncModeFlag = TextMarshalerFlag{
|
SyncModeFlag = TextMarshalerFlag{
|
||||||
Name: "syncmode",
|
Name: "syncmode",
|
||||||
|
|
@ -242,6 +233,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",
|
||||||
|
|
@ -318,29 +313,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: "Minimum gas price for mining a transaction",
|
||||||
|
Value: eth.DefaultConfig.MinerGasPrice,
|
||||||
|
}
|
||||||
|
MinerLegacyGasPriceFlag = BigFlag{
|
||||||
|
Name: "gasprice",
|
||||||
|
Usage: "Minimum gas price for mining a transaction (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",
|
||||||
|
|
@ -530,6 +567,10 @@ var (
|
||||||
Usage: "Minimum POW accepted",
|
Usage: "Minimum POW accepted",
|
||||||
Value: whisper.DefaultMinimumPoW,
|
Value: whisper.DefaultMinimumPoW,
|
||||||
}
|
}
|
||||||
|
WhisperRestrictConnectionBetweenLightClientsFlag = cli.BoolFlag{
|
||||||
|
Name: "shh.restrict-light",
|
||||||
|
Usage: "Restrict connection between two whisper light clients",
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics flags
|
// Metrics flags
|
||||||
MetricsEnabledFlag = cli.BoolFlag{
|
MetricsEnabledFlag = cli.BoolFlag{
|
||||||
|
|
@ -810,10 +851,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
|
||||||
}
|
}
|
||||||
|
|
@ -844,7 +894,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)
|
||||||
|
|
||||||
|
|
@ -944,6 +994,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)
|
||||||
}
|
}
|
||||||
|
|
@ -1043,14 +1103,15 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
|
||||||
if ctx.GlobalIsSet(WhisperMinPOWFlag.Name) {
|
if ctx.GlobalIsSet(WhisperMinPOWFlag.Name) {
|
||||||
cfg.MinimumAcceptedPOW = ctx.GlobalFloat64(WhisperMinPOWFlag.Name)
|
cfg.MinimumAcceptedPOW = ctx.GlobalFloat64(WhisperMinPOWFlag.Name)
|
||||||
}
|
}
|
||||||
|
if ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) {
|
||||||
|
cfg.RestrictConnectionBetweenLightClients = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEthConfig applies eth-related command line flags to the config.
|
// SetEthConfig applies eth-related command line flags to the 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)
|
||||||
|
|
@ -1059,13 +1120,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)
|
||||||
|
|
@ -1090,17 +1146,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
|
||||||
|
|
@ -1142,8 +1219,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
|
||||||
|
|
@ -1214,12 +1291,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")
|
||||||
|
|
@ -1248,7 +1319,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)
|
||||||
|
|
@ -1293,7 +1364,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" {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func Hex2BytesFixed(str string, flen int) []byte {
|
||||||
return h[len(h)-flen:]
|
return h[len(h)-flen:]
|
||||||
}
|
}
|
||||||
hh := make([]byte, flen)
|
hh := make([]byte, flen)
|
||||||
copy(hh[flen-len(h):flen], h[:])
|
copy(hh[flen-len(h):flen], h)
|
||||||
return hh
|
return hh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
129
common/mclock/simclock.go
Normal 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
57
common/prque/prque.go
Executable 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)
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
// CookieJar - A contestant's algorithm toolbox
|
// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque".
|
||||||
// Copyright (c) 2013 Peter Szilagyi. All rights reserved.
|
|
||||||
//
|
|
||||||
// CookieJar is dual licensed: use of this source code is governed by a BSD
|
|
||||||
// license that can be found in the LICENSE file. Alternatively, the CookieJar
|
|
||||||
// toolbox may be used in accordance with the terms and conditions contained
|
|
||||||
// in a signed written agreement between you and the author(s).
|
|
||||||
|
|
||||||
package prque
|
package prque
|
||||||
|
|
||||||
|
|
@ -12,15 +6,24 @@ package prque
|
||||||
const blockSize = 4096
|
const blockSize = 4096
|
||||||
|
|
||||||
// A prioritized item in the sorted stack.
|
// 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 {
|
type item struct {
|
||||||
value interface{}
|
value interface{}
|
||||||
priority float32
|
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
|
// 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
|
// the stack (heap) functionality and the Len, Less and Swap methods for the
|
||||||
// sortability requirements of the heaps.
|
// sortability requirements of the heaps.
|
||||||
type sstack struct {
|
type sstack struct {
|
||||||
|
setIndex setIndexCallback
|
||||||
size int
|
size int
|
||||||
capacity int
|
capacity int
|
||||||
offset int
|
offset int
|
||||||
|
|
@ -30,8 +33,9 @@ type sstack struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new, empty stack.
|
// Creates a new, empty stack.
|
||||||
func newSstack() *sstack {
|
func newSstack(setIndex setIndexCallback) *sstack {
|
||||||
result := new(sstack)
|
result := new(sstack)
|
||||||
|
result.setIndex = setIndex
|
||||||
result.active = make([]*item, blockSize)
|
result.active = make([]*item, blockSize)
|
||||||
result.blocks = [][]*item{result.active}
|
result.blocks = [][]*item{result.active}
|
||||||
result.capacity = blockSize
|
result.capacity = blockSize
|
||||||
|
|
@ -50,6 +54,9 @@ func (s *sstack) Push(data interface{}) {
|
||||||
s.active = s.blocks[s.size/blockSize]
|
s.active = s.blocks[s.size/blockSize]
|
||||||
s.offset = 0
|
s.offset = 0
|
||||||
}
|
}
|
||||||
|
if s.setIndex != nil {
|
||||||
|
s.setIndex(data.(*item).value, s.size)
|
||||||
|
}
|
||||||
s.active[s.offset] = data.(*item)
|
s.active[s.offset] = data.(*item)
|
||||||
s.offset++
|
s.offset++
|
||||||
s.size++
|
s.size++
|
||||||
|
|
@ -65,6 +72,9 @@ func (s *sstack) Pop() (res interface{}) {
|
||||||
s.active = s.blocks[s.size/blockSize]
|
s.active = s.blocks[s.size/blockSize]
|
||||||
}
|
}
|
||||||
res, s.active[s.offset] = s.active[s.offset], nil
|
res, s.active[s.offset] = s.active[s.offset], nil
|
||||||
|
if s.setIndex != nil {
|
||||||
|
s.setIndex(res.(*item).value, -1)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,16 +86,21 @@ func (s *sstack) Len() int {
|
||||||
// Compares the priority of two elements of the stack (higher is first).
|
// Compares the priority of two elements of the stack (higher is first).
|
||||||
// Required by sort.Interface.
|
// Required by sort.Interface.
|
||||||
func (s *sstack) Less(i, j int) bool {
|
func (s *sstack) Less(i, j int) bool {
|
||||||
return s.blocks[i/blockSize][i%blockSize].priority > s.blocks[j/blockSize][j%blockSize].priority
|
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.
|
// Swaps two elements in the stack. Required by sort.Interface.
|
||||||
func (s *sstack) Swap(i, j int) {
|
func (s *sstack) Swap(i, j int) {
|
||||||
ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize
|
ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize
|
||||||
s.blocks[ib][io], s.blocks[jb][jo] = s.blocks[jb][jo], s.blocks[ib][io]
|
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.
|
// Resets the stack, effectively clearing its contents.
|
||||||
func (s *sstack) Reset() {
|
func (s *sstack) Reset() {
|
||||||
*s = *newSstack()
|
*s = *newSstack(s.setIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ import (
|
||||||
const (
|
const (
|
||||||
// HashLength is the expected length of the hash
|
// HashLength is the expected length of the hash
|
||||||
HashLength = 32
|
HashLength = 32
|
||||||
// AddressLength is the expected length of the adddress
|
// AddressLength is the expected length of the address
|
||||||
AddressLength = 20
|
AddressLength = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,27 +93,33 @@ var (
|
||||||
|
|
||||||
// errMissingSignature is returned if a block's extra-data section doesn't seem
|
// errMissingSignature is returned if a block's extra-data section doesn't seem
|
||||||
// to contain a 65 byte secp256k1 signature.
|
// to contain a 65 byte secp256k1 signature.
|
||||||
errMissingSignature = errors.New("extra-data 65 byte suffix signature missing")
|
errMissingSignature = errors.New("extra-data 65 byte signature suffix missing")
|
||||||
|
|
||||||
// errExtraSigners is returned if non-checkpoint block contain signer data in
|
// errExtraSigners is returned if non-checkpoint block contain signer data in
|
||||||
// their extra-data fields.
|
// their extra-data fields.
|
||||||
errExtraSigners = errors.New("non-checkpoint block contains extra signer list")
|
errExtraSigners = errors.New("non-checkpoint block contains extra signer list")
|
||||||
|
|
||||||
// errInvalidCheckpointSigners is returned if a checkpoint block contains an
|
// errInvalidCheckpointSigners is returned if a checkpoint block contains an
|
||||||
// invalid list of signers (i.e. non divisible by 20 bytes, or not the correct
|
// invalid list of signers (i.e. non divisible by 20 bytes).
|
||||||
// ones).
|
|
||||||
errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block")
|
errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block")
|
||||||
|
|
||||||
|
// errMismatchingCheckpointSigners is returned if a checkpoint block contains a
|
||||||
|
// list of signers different than the one the local node calculated.
|
||||||
|
errMismatchingCheckpointSigners = errors.New("mismatching signer list on checkpoint block")
|
||||||
|
|
||||||
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
|
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
|
||||||
errInvalidMixDigest = errors.New("non-zero mix digest")
|
errInvalidMixDigest = errors.New("non-zero mix digest")
|
||||||
|
|
||||||
// errInvalidUncleHash is returned if a block contains an non-empty uncle list.
|
// errInvalidUncleHash is returned if a block contains an non-empty uncle list.
|
||||||
errInvalidUncleHash = errors.New("non empty uncle hash")
|
errInvalidUncleHash = errors.New("non empty uncle hash")
|
||||||
|
|
||||||
// errInvalidDifficulty is returned if the difficulty of a block is not either
|
// errInvalidDifficulty is returned if the difficulty of a block neither 1 or 2.
|
||||||
// of 1 or 2, or if the value does not match the turn of the signer.
|
|
||||||
errInvalidDifficulty = errors.New("invalid difficulty")
|
errInvalidDifficulty = errors.New("invalid difficulty")
|
||||||
|
|
||||||
|
// errWrongDifficulty is returned if the difficulty of a block doesn't match the
|
||||||
|
// turn of the signer.
|
||||||
|
errWrongDifficulty = errors.New("wrong difficulty")
|
||||||
|
|
||||||
// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
|
// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
|
||||||
// the previous block's timestamp + the minimum block period.
|
// the previous block's timestamp + the minimum block period.
|
||||||
ErrInvalidTimestamp = errors.New("invalid timestamp")
|
ErrInvalidTimestamp = errors.New("invalid timestamp")
|
||||||
|
|
@ -122,8 +128,12 @@ var (
|
||||||
// be modified via out-of-range or non-contiguous headers.
|
// be modified via out-of-range or non-contiguous headers.
|
||||||
errInvalidVotingChain = errors.New("invalid voting chain")
|
errInvalidVotingChain = errors.New("invalid voting chain")
|
||||||
|
|
||||||
// errUnauthorized is returned if a header is signed by a non-authorized entity.
|
// errUnauthorizedSigner is returned if a header is signed by a non-authorized entity.
|
||||||
errUnauthorized = errors.New("unauthorized")
|
errUnauthorizedSigner = errors.New("unauthorized signer")
|
||||||
|
|
||||||
|
// errRecentlySigned is returned if a header is signed by an authorized entity
|
||||||
|
// that already signed a header recently, thus is temporarily not allowed to.
|
||||||
|
errRecentlySigned = errors.New("recently signed")
|
||||||
|
|
||||||
// errWaitTransactions is returned if an empty block is attempted to be sealed
|
// errWaitTransactions is returned if an empty block is attempted to be sealed
|
||||||
// on an instant chain (0 second period). It's important to refuse these as the
|
// on an instant chain (0 second period). It's important to refuse these as the
|
||||||
|
|
@ -205,6 +215,9 @@ type Clique struct {
|
||||||
signer common.Address // Ethereum address of the signing key
|
signer common.Address // Ethereum address of the signing key
|
||||||
signFn SignerFn // Signer function to authorize hashes with
|
signFn SignerFn // Signer function to authorize hashes with
|
||||||
lock sync.RWMutex // Protects the signer fields
|
lock sync.RWMutex // Protects the signer fields
|
||||||
|
|
||||||
|
// The fields below are for testing only
|
||||||
|
fakeDiff bool // Skip difficulty verifications
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Clique proof-of-authority consensus engine with the initial
|
// New creates a Clique proof-of-authority consensus engine with the initial
|
||||||
|
|
@ -359,7 +372,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type
|
||||||
}
|
}
|
||||||
extraSuffix := len(header.Extra) - extraSeal
|
extraSuffix := len(header.Extra) - extraSeal
|
||||||
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) {
|
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) {
|
||||||
return errInvalidCheckpointSigners
|
return errMismatchingCheckpointSigners
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// All basic checks passed, verify the seal and return
|
// All basic checks passed, verify the seal and return
|
||||||
|
|
@ -387,23 +400,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 == 0 || (number%c.config.Epoch == 0 && chain.GetHeaderByNumber(number-1) == nil) {
|
||||||
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 {
|
||||||
|
|
@ -480,23 +494,25 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, ok := snap.Signers[signer]; !ok {
|
if _, ok := snap.Signers[signer]; !ok {
|
||||||
return errUnauthorized
|
return errUnauthorizedSigner
|
||||||
}
|
}
|
||||||
for seen, recent := range snap.Recents {
|
for seen, recent := range snap.Recents {
|
||||||
if recent == signer {
|
if recent == signer {
|
||||||
// Signer is among recents, only fail if the current block doesn't shift it out
|
// Signer is among recents, only fail if the current block doesn't shift it out
|
||||||
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
|
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
|
||||||
return errUnauthorized
|
return errRecentlySigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ensure that the difficulty corresponds to the turn-ness of the signer
|
// Ensure that the difficulty corresponds to the turn-ness of the signer
|
||||||
|
if !c.fakeDiff {
|
||||||
inturn := snap.inturn(header.Number.Uint64(), signer)
|
inturn := snap.inturn(header.Number.Uint64(), signer)
|
||||||
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
|
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
|
||||||
return errInvalidDifficulty
|
return errWrongDifficulty
|
||||||
}
|
}
|
||||||
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
|
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
|
||||||
return errInvalidDifficulty
|
return errWrongDifficulty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -589,17 +605,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 +625,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 errUnauthorizedSigner
|
||||||
}
|
}
|
||||||
// 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 +636,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 +649,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 +695,11 @@ 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.
|
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads.
|
||||||
func (c *Clique) Close() error {
|
func (c *Clique) Close() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,12 @@ type Snapshot struct {
|
||||||
Tally map[common.Address]Tally `json:"tally"` // Current vote tally to avoid recalculating
|
Tally map[common.Address]Tally `json:"tally"` // Current vote tally to avoid recalculating
|
||||||
}
|
}
|
||||||
|
|
||||||
// signers implements the sort interface to allow sorting a list of addresses
|
// signersAscending implements the sort interface to allow sorting a list of addresses
|
||||||
type signers []common.Address
|
type signersAscending []common.Address
|
||||||
|
|
||||||
func (s signers) Len() int { return len(s) }
|
func (s signersAscending) Len() int { return len(s) }
|
||||||
func (s signers) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }
|
func (s signersAscending) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }
|
||||||
func (s signers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s signersAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
// newSnapshot creates a new snapshot with the specified startup parameters. This
|
// newSnapshot creates a new snapshot with the specified startup parameters. This
|
||||||
// method does not initialize the set of recent signers, so only ever use if for
|
// method does not initialize the set of recent signers, so only ever use if for
|
||||||
|
|
@ -214,11 +214,11 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, ok := snap.Signers[signer]; !ok {
|
if _, ok := snap.Signers[signer]; !ok {
|
||||||
return nil, errUnauthorized
|
return nil, errUnauthorizedSigner
|
||||||
}
|
}
|
||||||
for _, recent := range snap.Recents {
|
for _, recent := range snap.Recents {
|
||||||
if recent == signer {
|
if recent == signer {
|
||||||
return nil, errUnauthorized
|
return nil, errRecentlySigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snap.Recents[number] = signer
|
snap.Recents[number] = signer
|
||||||
|
|
@ -298,7 +298,7 @@ func (s *Snapshot) signers() []common.Address {
|
||||||
for sig := range s.Signers {
|
for sig := range s.Signers {
|
||||||
sigs = append(sigs, sig)
|
sigs = append(sigs, sig)
|
||||||
}
|
}
|
||||||
sort.Sort(signers(sigs))
|
sort.Sort(signersAscending(sigs))
|
||||||
return sigs
|
return sigs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,24 +19,18 @@ package clique
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"math/big"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testerVote struct {
|
|
||||||
signer string
|
|
||||||
voted string
|
|
||||||
auth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// testerAccountPool is a pool to maintain currently active tester accounts,
|
// testerAccountPool is a pool to maintain currently active tester accounts,
|
||||||
// mapped from textual names used in the tests below to actual Ethereum private
|
// mapped from textual names used in the tests below to actual Ethereum private
|
||||||
// keys capable of signing transactions.
|
// keys capable of signing transactions.
|
||||||
|
|
@ -50,17 +44,26 @@ func newTesterAccountPool() *testerAccountPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ap *testerAccountPool) sign(header *types.Header, signer string) {
|
// checkpoint creates a Clique checkpoint signer section from the provided list
|
||||||
// Ensure we have a persistent key for the signer
|
// of authorized signers and embeds it into the provided header.
|
||||||
if ap.accounts[signer] == nil {
|
func (ap *testerAccountPool) checkpoint(header *types.Header, signers []string) {
|
||||||
ap.accounts[signer], _ = crypto.GenerateKey()
|
auths := make([]common.Address, len(signers))
|
||||||
|
for i, signer := range signers {
|
||||||
|
auths[i] = ap.address(signer)
|
||||||
|
}
|
||||||
|
sort.Sort(signersAscending(auths))
|
||||||
|
for i, auth := range auths {
|
||||||
|
copy(header.Extra[extraVanity+i*common.AddressLength:], auth.Bytes())
|
||||||
}
|
}
|
||||||
// Sign the header and embed the signature in extra data
|
|
||||||
sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer])
|
|
||||||
copy(header.Extra[len(header.Extra)-65:], sig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// address retrieves the Ethereum address of a tester account by label, creating
|
||||||
|
// a new account if no previous one exists yet.
|
||||||
func (ap *testerAccountPool) address(account string) common.Address {
|
func (ap *testerAccountPool) address(account string) common.Address {
|
||||||
|
// Return the zero account for non-addresses
|
||||||
|
if account == "" {
|
||||||
|
return common.Address{}
|
||||||
|
}
|
||||||
// Ensure we have a persistent key for the account
|
// Ensure we have a persistent key for the account
|
||||||
if ap.accounts[account] == nil {
|
if ap.accounts[account] == nil {
|
||||||
ap.accounts[account], _ = crypto.GenerateKey()
|
ap.accounts[account], _ = crypto.GenerateKey()
|
||||||
|
|
@ -69,32 +72,38 @@ func (ap *testerAccountPool) address(account string) common.Address {
|
||||||
return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
|
return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testerChainReader implements consensus.ChainReader to access the genesis
|
// sign calculates a Clique digital signature for the given block and embeds it
|
||||||
// block. All other methods and requests will panic.
|
// back into the header.
|
||||||
type testerChainReader struct {
|
func (ap *testerAccountPool) sign(header *types.Header, signer string) {
|
||||||
db ethdb.Database
|
// Ensure we have a persistent key for the signer
|
||||||
|
if ap.accounts[signer] == nil {
|
||||||
|
ap.accounts[signer], _ = crypto.GenerateKey()
|
||||||
|
}
|
||||||
|
// Sign the header and embed the signature in extra data
|
||||||
|
sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer])
|
||||||
|
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *testerChainReader) Config() *params.ChainConfig { return params.AllCliqueProtocolChanges }
|
// testerVote represents a single block signed by a parcitular account, where
|
||||||
func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") }
|
// the account may or may not have cast a Clique vote.
|
||||||
func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") }
|
type testerVote struct {
|
||||||
func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") }
|
signer string
|
||||||
func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") }
|
voted string
|
||||||
func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header {
|
auth bool
|
||||||
if number == 0 {
|
checkpoint []string
|
||||||
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0)
|
newbatch bool
|
||||||
}
|
|
||||||
panic("not supported")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that voting is evaluated correctly for various simple and complex scenarios.
|
// Tests that Clique signer voting is evaluated correctly for various simple and
|
||||||
func TestVoting(t *testing.T) {
|
// complex scenarios, as well as that a few special corner cases fail correctly.
|
||||||
|
func TestClique(t *testing.T) {
|
||||||
// Define the various voting scenarios to test
|
// Define the various voting scenarios to test
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
epoch uint64
|
epoch uint64
|
||||||
signers []string
|
signers []string
|
||||||
votes []testerVote
|
votes []testerVote
|
||||||
results []string
|
results []string
|
||||||
|
failure error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// Single signer, no votes cast
|
// Single signer, no votes cast
|
||||||
|
|
@ -322,10 +331,49 @@ func TestVoting(t *testing.T) {
|
||||||
votes: []testerVote{
|
votes: []testerVote{
|
||||||
{signer: "A", voted: "C", auth: true},
|
{signer: "A", voted: "C", auth: true},
|
||||||
{signer: "B"},
|
{signer: "B"},
|
||||||
{signer: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots)
|
{signer: "A", checkpoint: []string{"A", "B"}},
|
||||||
{signer: "B", voted: "C", auth: true},
|
{signer: "B", voted: "C", auth: true},
|
||||||
},
|
},
|
||||||
results: []string{"A", "B"},
|
results: []string{"A", "B"},
|
||||||
|
}, {
|
||||||
|
// An unauthorized signer should not be able to sign blocks
|
||||||
|
signers: []string{"A"},
|
||||||
|
votes: []testerVote{
|
||||||
|
{signer: "B"},
|
||||||
|
},
|
||||||
|
failure: errUnauthorizedSigner,
|
||||||
|
}, {
|
||||||
|
// An authorized signer that signed recenty should not be able to sign again
|
||||||
|
signers: []string{"A", "B"},
|
||||||
|
votes: []testerVote{
|
||||||
|
{signer: "A"},
|
||||||
|
{signer: "A"},
|
||||||
|
},
|
||||||
|
failure: errRecentlySigned,
|
||||||
|
}, {
|
||||||
|
// Recent signatures should not reset on checkpoint blocks imported in a batch
|
||||||
|
epoch: 3,
|
||||||
|
signers: []string{"A", "B", "C"},
|
||||||
|
votes: []testerVote{
|
||||||
|
{signer: "A"},
|
||||||
|
{signer: "B"},
|
||||||
|
{signer: "A", checkpoint: []string{"A", "B", "C"}},
|
||||||
|
{signer: "A"},
|
||||||
|
},
|
||||||
|
failure: errRecentlySigned,
|
||||||
|
}, {
|
||||||
|
// Recent signatures should not reset on checkpoint blocks imported in a new
|
||||||
|
// batch (https://github.com/ethereum/go-ethereum/issues/17593). Whilst this
|
||||||
|
// seems overly specific and weird, it was a Rinkeby consensus split.
|
||||||
|
epoch: 3,
|
||||||
|
signers: []string{"A", "B", "C"},
|
||||||
|
votes: []testerVote{
|
||||||
|
{signer: "A"},
|
||||||
|
{signer: "B"},
|
||||||
|
{signer: "A", checkpoint: []string{"A", "B", "C"}},
|
||||||
|
{signer: "A", newbatch: true},
|
||||||
|
},
|
||||||
|
failure: errRecentlySigned,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Run through the scenarios and test them
|
// Run through the scenarios and test them
|
||||||
|
|
@ -356,28 +404,78 @@ func TestVoting(t *testing.T) {
|
||||||
genesis.Commit(db)
|
genesis.Commit(db)
|
||||||
|
|
||||||
// Assemble a chain of headers from the cast votes
|
// Assemble a chain of headers from the cast votes
|
||||||
headers := make([]*types.Header, len(tt.votes))
|
config := *params.TestChainConfig
|
||||||
for j, vote := range tt.votes {
|
config.Clique = ¶ms.CliqueConfig{
|
||||||
headers[j] = &types.Header{
|
Period: 1,
|
||||||
Number: big.NewInt(int64(j) + 1),
|
Epoch: tt.epoch,
|
||||||
Time: big.NewInt(int64(j) * 15),
|
|
||||||
Coinbase: accounts.address(vote.voted),
|
|
||||||
Extra: make([]byte, extraVanity+extraSeal),
|
|
||||||
}
|
}
|
||||||
|
engine := New(config.Clique, db)
|
||||||
|
engine.fakeDiff = true
|
||||||
|
|
||||||
|
blocks, _ := core.GenerateChain(&config, genesis.ToBlock(db), engine, db, len(tt.votes), func(j int, gen *core.BlockGen) {
|
||||||
|
// Cast the vote contained in this block
|
||||||
|
gen.SetCoinbase(accounts.address(tt.votes[j].voted))
|
||||||
|
if tt.votes[j].auth {
|
||||||
|
var nonce types.BlockNonce
|
||||||
|
copy(nonce[:], nonceAuthVote)
|
||||||
|
gen.SetNonce(nonce)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Iterate through the blocks and seal them individually
|
||||||
|
for j, block := range blocks {
|
||||||
|
// Geth the header and prepare it for signing
|
||||||
|
header := block.Header()
|
||||||
if j > 0 {
|
if j > 0 {
|
||||||
headers[j].ParentHash = headers[j-1].Hash()
|
header.ParentHash = blocks[j-1].Hash()
|
||||||
}
|
}
|
||||||
if vote.auth {
|
header.Extra = make([]byte, extraVanity+extraSeal)
|
||||||
copy(headers[j].Nonce[:], nonceAuthVote)
|
if auths := tt.votes[j].checkpoint; auths != nil {
|
||||||
|
header.Extra = make([]byte, extraVanity+len(auths)*common.AddressLength+extraSeal)
|
||||||
|
accounts.checkpoint(header, auths)
|
||||||
}
|
}
|
||||||
accounts.sign(headers[j], vote.signer)
|
header.Difficulty = diffInTurn // Ignored, we just need a valid number
|
||||||
|
|
||||||
|
// Generate the signature, embed it into the header and the block
|
||||||
|
accounts.sign(header, tt.votes[j].signer)
|
||||||
|
blocks[j] = block.WithSeal(header)
|
||||||
|
}
|
||||||
|
// Split the blocks up into individual import batches (cornercase testing)
|
||||||
|
batches := [][]*types.Block{nil}
|
||||||
|
for j, block := range blocks {
|
||||||
|
if tt.votes[j].newbatch {
|
||||||
|
batches = append(batches, nil)
|
||||||
|
}
|
||||||
|
batches[len(batches)-1] = append(batches[len(batches)-1], block)
|
||||||
}
|
}
|
||||||
// Pass all the headers through clique and ensure tallying succeeds
|
// Pass all the headers through clique and ensure tallying succeeds
|
||||||
head := headers[len(headers)-1]
|
chain, err := core.NewBlockChain(db, nil, &config, engine, vm.Config{})
|
||||||
|
|
||||||
snap, err := New(¶ms.CliqueConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("test %d: failed to create voting snapshot: %v", i, err)
|
t.Errorf("test %d: failed to create test chain: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
failed := false
|
||||||
|
for j := 0; j < len(batches)-1; j++ {
|
||||||
|
if k, err := chain.InsertChain(batches[j]); err != nil {
|
||||||
|
t.Errorf("test %d: failed to import batch %d, block %d: %v", i, j, k, err)
|
||||||
|
failed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err = chain.InsertChain(batches[len(batches)-1]); err != tt.failure {
|
||||||
|
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
|
||||||
|
}
|
||||||
|
if tt.failure != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// No failure was produced or requested, generate the final voting snapshot
|
||||||
|
head := blocks[len(blocks)-1]
|
||||||
|
|
||||||
|
snap, err := engine.snapshot(chain, head.NumberU64(), head.Hash(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d: failed to retrieve voting snapshot: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Verify the final list of signers against the expected ones
|
// Verify the final list of signers against the expected ones
|
||||||
|
|
|
||||||
|
|
@ -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 algorithm.
|
||||||
|
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.
|
||||||
|
|
|
||||||
|
|
@ -729,7 +729,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
|
||||||
|
|
||||||
go func(idx int) {
|
go func(idx int) {
|
||||||
defer pend.Done()
|
defer pend.Done()
|
||||||
ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal})
|
ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal}, nil, false)
|
||||||
defer ethash.Close()
|
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)
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,31 @@ 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
|
||||||
|
ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
|
||||||
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
|
||||||
|
|
||||||
|
// calcDifficultyConstantinople is the difficulty adjustment algorithm for Constantinople.
|
||||||
|
// It returns the difficulty that a new block should have when created at time given the
|
||||||
|
// parent block's time and difficulty. The calculation uses the Byzantium rules, but with
|
||||||
|
// bomb offset 5M.
|
||||||
|
// Specification EIP-1234: https://eips.ethereum.org/EIPS/eip-1234
|
||||||
|
calcDifficultyConstantinople = makeDifficultyCalculator(big.NewInt(5000000))
|
||||||
|
|
||||||
|
// calcDifficultyByzantium is the difficulty adjustment algorithm. It returns
|
||||||
|
// the difficulty that a new block should have when created at time given the
|
||||||
|
// parent block's time and difficulty. The calculation uses the Byzantium rules.
|
||||||
|
// Specification EIP-649: https://eips.ethereum.org/EIPS/eip-649
|
||||||
|
calcDifficultyByzantium = makeDifficultyCalculator(big.NewInt(3000000))
|
||||||
)
|
)
|
||||||
|
|
||||||
// Various error messages to mark blocks invalid. These should be private to
|
// Various error messages to mark blocks invalid. These should be private to
|
||||||
|
|
@ -297,6 +313,8 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, p
|
||||||
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
|
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
|
||||||
next := new(big.Int).Add(parent.Number, big1)
|
next := new(big.Int).Add(parent.Number, big1)
|
||||||
switch {
|
switch {
|
||||||
|
case config.IsConstantinople(next):
|
||||||
|
return calcDifficultyConstantinople(time, parent)
|
||||||
case config.IsByzantium(next):
|
case config.IsByzantium(next):
|
||||||
return calcDifficultyByzantium(time, parent)
|
return calcDifficultyByzantium(time, parent)
|
||||||
case config.IsHomestead(next):
|
case config.IsHomestead(next):
|
||||||
|
|
@ -314,13 +332,16 @@ var (
|
||||||
big9 = big.NewInt(9)
|
big9 = big.NewInt(9)
|
||||||
big10 = big.NewInt(10)
|
big10 = big.NewInt(10)
|
||||||
bigMinus99 = big.NewInt(-99)
|
bigMinus99 = big.NewInt(-99)
|
||||||
big2999999 = big.NewInt(2999999)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// calcDifficultyByzantium is the difficulty adjustment algorithm. It returns
|
// makeDifficultyCalculator creates a difficultyCalculator with the given bomb-delay.
|
||||||
// the difficulty that a new block should have when created at time given the
|
// the difficulty is calculated with Byzantium rules, which differs from Homestead in
|
||||||
// parent block's time and difficulty. The calculation uses the Byzantium rules.
|
// how uncles affect the calculation
|
||||||
func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int {
|
func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int {
|
||||||
|
// Note, the calculations below looks at the parent number, which is 1 below
|
||||||
|
// the block number. Thus we remove one from the delay given
|
||||||
|
bombDelayFromParent := new(big.Int).Sub(bombDelay, big1)
|
||||||
|
return func(time uint64, parent *types.Header) *big.Int {
|
||||||
// https://github.com/ethereum/EIPs/issues/100.
|
// https://github.com/ethereum/EIPs/issues/100.
|
||||||
// algorithm:
|
// algorithm:
|
||||||
// diff = (parent_diff +
|
// diff = (parent_diff +
|
||||||
|
|
@ -355,12 +376,11 @@ func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int {
|
||||||
if x.Cmp(params.MinimumDifficulty) < 0 {
|
if x.Cmp(params.MinimumDifficulty) < 0 {
|
||||||
x.Set(params.MinimumDifficulty)
|
x.Set(params.MinimumDifficulty)
|
||||||
}
|
}
|
||||||
// calculate a fake block number for the ice-age delay:
|
// calculate a fake block number for the ice-age delay
|
||||||
// https://github.com/ethereum/EIPs/pull/669
|
// Specification: https://eips.ethereum.org/EIPS/eip-1234
|
||||||
// fake_block_number = max(0, block.number - 3_000_000)
|
|
||||||
fakeBlockNumber := new(big.Int)
|
fakeBlockNumber := new(big.Int)
|
||||||
if parent.Number.Cmp(big2999999) >= 0 {
|
if parent.Number.Cmp(bombDelayFromParent) >= 0 {
|
||||||
fakeBlockNumber = fakeBlockNumber.Sub(parent.Number, big2999999) // Note, parent is 1 less than the actual block number
|
fakeBlockNumber = fakeBlockNumber.Sub(parent.Number, bombDelayFromParent)
|
||||||
}
|
}
|
||||||
// for the exponential factor
|
// for the exponential factor
|
||||||
periodCount := fakeBlockNumber
|
periodCount := fakeBlockNumber
|
||||||
|
|
@ -375,6 +395,7 @@ func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int {
|
||||||
}
|
}
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// calcDifficultyHomestead is the difficulty adjustment algorithm. It returns
|
// calcDifficultyHomestead is the difficulty adjustment algorithm. It returns
|
||||||
// the difficulty that a new block should have when created at time given the
|
// the difficulty that a new block should have when created at time given the
|
||||||
|
|
@ -461,6 +482,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 +499,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 +573,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)
|
||||||
|
|
@ -537,6 +611,9 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
|
||||||
if config.IsByzantium(header.Number) {
|
if config.IsByzantium(header.Number) {
|
||||||
blockReward = ByzantiumBlockReward
|
blockReward = ByzantiumBlockReward
|
||||||
}
|
}
|
||||||
|
if config.IsConstantinople(header.Number) {
|
||||||
|
blockReward = ConstantinopleBlockReward
|
||||||
|
}
|
||||||
// Accumulate the rewards for the miner and any included uncles
|
// Accumulate the rewards for the miner and any included uncles
|
||||||
reward := new(big.Int).Set(blockReward)
|
reward := new(big.Int).Set(blockReward)
|
||||||
r := new(big.Int)
|
r := new(big.Int)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
|
@ -45,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
|
||||||
|
|
@ -281,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
|
||||||
|
|
@ -292,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)
|
||||||
|
|
@ -306,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
|
||||||
|
|
@ -348,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 {
|
||||||
|
|
@ -391,6 +405,12 @@ 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.
|
// mineResult wraps the pow solution parameters for the specified block.
|
||||||
type mineResult struct {
|
type mineResult struct {
|
||||||
nonce types.BlockNonce
|
nonce types.BlockNonce
|
||||||
|
|
@ -430,8 +450,7 @@ type Ethash struct {
|
||||||
hashrate metrics.Meter // Meter tracking the average hashrate
|
hashrate metrics.Meter // Meter tracking the average hashrate
|
||||||
|
|
||||||
// Remote sealer related fields
|
// Remote sealer related fields
|
||||||
workCh chan *types.Block // Notification channel to push new work to remote sealer
|
workCh chan *sealTask // Notification channel to push new work and relative result channel to remote sealer
|
||||||
resultCh chan *types.Block // Channel used by mining threads to return result
|
|
||||||
fetchWorkCh chan *sealWork // Channel used for remote sealer to fetch mining work
|
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
|
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.
|
fetchRateCh chan chan uint64 // Channel used to gather submitted hash rate for local or remote sealer.
|
||||||
|
|
@ -447,8 +466,10 @@ type Ethash struct {
|
||||||
exitCh chan chan error // Notification channel to exiting backend threads
|
exitCh chan chan error // Notification channel to exiting backend threads
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a full sized ethash PoW scheme and starts a background thread for remote mining.
|
// 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
|
||||||
|
|
@ -464,37 +485,35 @@ func New(config Config) *Ethash {
|
||||||
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.NewMeterForced(),
|
||||||
workCh: make(chan *types.Block),
|
workCh: make(chan *sealTask),
|
||||||
resultCh: make(chan *types.Block),
|
|
||||||
fetchWorkCh: make(chan *sealWork),
|
fetchWorkCh: make(chan *sealWork),
|
||||||
submitWorkCh: make(chan *mineResult),
|
submitWorkCh: make(chan *mineResult),
|
||||||
fetchRateCh: make(chan chan uint64),
|
fetchRateCh: make(chan chan uint64),
|
||||||
submitRateCh: make(chan *hashrate),
|
submitRateCh: make(chan *hashrate),
|
||||||
exitCh: make(chan chan error),
|
exitCh: make(chan chan error),
|
||||||
}
|
}
|
||||||
go ethash.remote()
|
go ethash.remote(notify, noverify)
|
||||||
return ethash
|
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 {
|
||||||
ethash := &Ethash{
|
ethash := &Ethash{
|
||||||
config: Config{PowMode: ModeTest},
|
config: Config{PowMode: ModeTest},
|
||||||
caches: newlru("cache", 1, newCache),
|
caches: newlru("cache", 1, newCache),
|
||||||
datasets: newlru("dataset", 1, newDataset),
|
datasets: newlru("dataset", 1, newDataset),
|
||||||
update: make(chan struct{}),
|
update: make(chan struct{}),
|
||||||
hashrate: metrics.NewMeter(),
|
hashrate: metrics.NewMeterForced(),
|
||||||
workCh: make(chan *types.Block),
|
workCh: make(chan *sealTask),
|
||||||
resultCh: make(chan *types.Block),
|
|
||||||
fetchWorkCh: make(chan *sealWork),
|
fetchWorkCh: make(chan *sealWork),
|
||||||
submitWorkCh: make(chan *mineResult),
|
submitWorkCh: make(chan *mineResult),
|
||||||
fetchRateCh: make(chan chan uint64),
|
fetchRateCh: make(chan chan uint64),
|
||||||
submitRateCh: make(chan *hashrate),
|
submitRateCh: make(chan *hashrate),
|
||||||
exitCh: make(chan chan error),
|
exitCh: make(chan chan error),
|
||||||
}
|
}
|
||||||
go ethash.remote()
|
go ethash.remote(notify, noverify)
|
||||||
return ethash
|
return ethash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -587,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,19 +32,26 @@ import (
|
||||||
|
|
||||||
// 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)
|
||||||
defer ethash.Close()
|
defer ethash.Close()
|
||||||
block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil)
|
|
||||||
|
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.
|
||||||
|
|
@ -55,7 +62,7 @@ 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()
|
defer e.Close()
|
||||||
|
|
||||||
workers := 8
|
workers := 8
|
||||||
|
|
@ -78,71 +85,63 @@ 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) {
|
func TestRemoteSealer(t *testing.T) {
|
||||||
ethash := NewTester()
|
ethash := NewTester(nil, false)
|
||||||
defer ethash.Close()
|
defer ethash.Close()
|
||||||
|
|
||||||
api := &API{ethash}
|
api := &API{ethash}
|
||||||
if _, err := api.GetWork(); err != errNoMiningWork {
|
if _, err := api.GetWork(); err != errNoMiningWork {
|
||||||
t.Error("expect to return an error indicate there is no mining work")
|
t.Error("expect to return an error indicate there is no mining work")
|
||||||
}
|
}
|
||||||
|
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||||
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
block := types.NewBlockWithHeader(header)
|
||||||
block := types.NewBlockWithHeader(head)
|
sealhash := ethash.SealHash(header)
|
||||||
|
|
||||||
// Push new work.
|
// Push new work.
|
||||||
ethash.Seal(nil, block, nil)
|
results := make(chan *types.Block)
|
||||||
|
ethash.Seal(nil, block, results, nil)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
work [3]string
|
work [3]string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
|
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
|
||||||
t.Error("expect to return a mining work has same hash")
|
t.Error("expect to return a mining work has same hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
if res := api.SubmitWork(types.BlockNonce{}, sealhash, common.Hash{}); res {
|
||||||
t.Error("expect to return false when submit a fake solution")
|
t.Error("expect to return false when submit a fake solution")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push new block with same block number to replace the original one.
|
// Push new block with same block number to replace the original one.
|
||||||
head = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
|
header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
|
||||||
block = types.NewBlockWithHeader(head)
|
block = types.NewBlockWithHeader(header)
|
||||||
ethash.Seal(nil, block, nil)
|
sealhash = ethash.SealHash(header)
|
||||||
|
ethash.Seal(nil, block, results, nil)
|
||||||
|
|
||||||
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
|
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
|
||||||
t.Error("expect to return the latest pushed work")
|
t.Error("expect to return the latest pushed work")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push block with higher block number.
|
|
||||||
newHead := &types.Header{Number: big.NewInt(2), Difficulty: big.NewInt(100)}
|
|
||||||
newBlock := types.NewBlockWithHeader(newHead)
|
|
||||||
ethash.Seal(nil, newBlock, nil)
|
|
||||||
|
|
||||||
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
|
||||||
t.Error("expect to return false when submit a stale solution")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHashRate(t *testing.T) {
|
func TestHashRate(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
ethash = NewTester()
|
|
||||||
api = &API{ethash}
|
|
||||||
hashrate = []hexutil.Uint64{100, 200, 300}
|
hashrate = []hexutil.Uint64{100, 200, 300}
|
||||||
expect uint64
|
expect uint64
|
||||||
ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")}
|
ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")}
|
||||||
)
|
)
|
||||||
|
ethash := NewTester(nil, false)
|
||||||
defer ethash.Close()
|
defer ethash.Close()
|
||||||
|
|
||||||
if tot := ethash.Hashrate(); tot != 0 {
|
if tot := ethash.Hashrate(); tot != 0 {
|
||||||
t.Error("expect the result should be zero")
|
t.Error("expect the result should be zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api := &API{ethash}
|
||||||
for i := 0; i < len(hashrate); i += 1 {
|
for i := 0; i < len(hashrate); i += 1 {
|
||||||
if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
|
if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
|
||||||
t.Error("remote miner submit hashrate failed")
|
t.Error("remote miner submit hashrate failed")
|
||||||
|
|
@ -155,9 +154,8 @@ func TestHashRate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClosedRemoteSealer(t *testing.T) {
|
func TestClosedRemoteSealer(t *testing.T) {
|
||||||
ethash := NewTester()
|
ethash := NewTester(nil, false)
|
||||||
// Make sure exit channel has been listened
|
time.Sleep(1 * time.Second) // ensure exit channel is listening
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
ethash.Close()
|
ethash.Close()
|
||||||
|
|
||||||
api := &API{ethash}
|
api := &API{ethash}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,14 @@
|
||||||
package ethash
|
package ethash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -32,6 +35,11 @@ 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 (
|
var (
|
||||||
errNoMiningWork = errors.New("no mining work available yet")
|
errNoMiningWork = errors.New("no mining work available yet")
|
||||||
errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
|
errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
|
||||||
|
|
@ -39,16 +47,21 @@ var (
|
||||||
|
|
||||||
// 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{})
|
||||||
|
|
@ -59,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()))
|
||||||
}
|
}
|
||||||
|
|
@ -72,34 +85,45 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
|
||||||
}
|
}
|
||||||
// Push new work to remote sealer
|
// Push new work to remote sealer
|
||||||
if ethash.workCh != nil {
|
if ethash.workCh != nil {
|
||||||
ethash.workCh <- block
|
ethash.workCh <- &sealTask{block: block, results: results}
|
||||||
}
|
}
|
||||||
var pend sync.WaitGroup
|
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, ethash.resultCh)
|
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 = <-ethash.resultCh:
|
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
|
||||||
|
|
@ -108,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 (
|
||||||
|
|
@ -161,98 +185,139 @@ search:
|
||||||
runtime.KeepAlive(dataset)
|
runtime.KeepAlive(dataset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remote starts a standalone goroutine to handle remote mining related stuff.
|
// remote is a standalone goroutine to handle remote mining related stuff.
|
||||||
func (ethash *Ethash) remote() {
|
func (ethash *Ethash) remote(notify []string, noverify bool) {
|
||||||
var (
|
var (
|
||||||
works = make(map[common.Hash]*types.Block)
|
works = make(map[common.Hash]*types.Block)
|
||||||
rates = make(map[common.Hash]hashrate)
|
rates = make(map[common.Hash]hashrate)
|
||||||
currentWork *types.Block
|
|
||||||
)
|
|
||||||
|
|
||||||
// getWork returns a work package for external miner.
|
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:
|
// The work package consists of 3 strings:
|
||||||
// result[0], 32 bytes hex encoded current block header pow-hash
|
// result[0], 32 bytes hex encoded current block header pow-hash
|
||||||
// result[1], 32 bytes hex encoded seed hash used for DAG
|
// result[1], 32 bytes hex encoded seed hash used for DAG
|
||||||
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||||
getWork := func() ([3]string, error) {
|
makeWork := func(block *types.Block) {
|
||||||
var res [3]string
|
hash := ethash.SealHash(block.Header())
|
||||||
if currentWork == nil {
|
|
||||||
return res, errNoMiningWork
|
|
||||||
}
|
|
||||||
res[0] = currentWork.HashNoNonce().Hex()
|
|
||||||
res[1] = common.BytesToHash(SeedHash(currentWork.NumberU64())).Hex()
|
|
||||||
|
|
||||||
// Calculate the "target" to be returned to the external sealer.
|
currentWork[0] = hash.Hex()
|
||||||
n := big.NewInt(1)
|
currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex()
|
||||||
n.Lsh(n, 255)
|
currentWork[2] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex()
|
||||||
n.Div(n, currentWork.Difficulty())
|
|
||||||
n.Lsh(n, 1)
|
|
||||||
res[2] = common.BytesToHash(n.Bytes()).Hex()
|
|
||||||
|
|
||||||
// Trace the seal work fetched by remote sealer.
|
// Trace the seal work fetched by remote sealer.
|
||||||
works[currentWork.HashNoNonce()] = currentWork
|
currentBlock = block
|
||||||
return res, nil
|
works[hash] = block
|
||||||
}
|
}
|
||||||
|
|
||||||
// submitWork verifies the submitted pow solution, returning
|
// submitWork verifies the submitted pow solution, returning
|
||||||
// whether the solution was accepted or not (not can be both a bad pow as well as
|
// 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).
|
// any other error, like no pending work or stale mining result).
|
||||||
submitWork := func(nonce types.BlockNonce, mixDigest common.Hash, hash common.Hash) bool {
|
submitWork := func(nonce types.BlockNonce, mixDigest common.Hash, sealhash common.Hash) bool {
|
||||||
// Make sure the work submitted is present
|
if currentBlock == nil {
|
||||||
block := works[hash]
|
log.Error("Pending work without block", "sealhash", sealhash)
|
||||||
if block == nil {
|
return false
|
||||||
log.Info("Work submitted but none pending", "hash", hash)
|
}
|
||||||
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the correctness of submitted result.
|
// Verify the correctness of submitted result.
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
header.Nonce = nonce
|
header.Nonce = nonce
|
||||||
header.MixDigest = mixDigest
|
header.MixDigest = mixDigest
|
||||||
if err := ethash.VerifySeal(nil, header); err != nil {
|
|
||||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Make sure the result channel is created.
|
// Make sure the result channel is assigned.
|
||||||
if ethash.resultCh == nil {
|
if results == nil {
|
||||||
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
||||||
return false
|
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.
|
// 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 {
|
select {
|
||||||
case ethash.resultCh <- block.WithSeal(header):
|
case results <- solution:
|
||||||
delete(works, hash)
|
log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
log.Info("Work submitted is stale", "hash", hash)
|
log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash)
|
||||||
return false
|
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)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case block := <-ethash.workCh:
|
case work := <-ethash.workCh:
|
||||||
if currentWork != nil && block.ParentHash() != currentWork.ParentHash() {
|
|
||||||
// Start new round mining, throw out all previous work.
|
|
||||||
works = make(map[common.Hash]*types.Block)
|
|
||||||
}
|
|
||||||
// Update current work with new received block.
|
// Update current work with new received block.
|
||||||
// Note same work can be past twice, happens when changing CPU threads.
|
// Note same work can be past twice, happens when changing CPU threads.
|
||||||
currentWork = block
|
results = work.results
|
||||||
|
|
||||||
|
makeWork(work.block)
|
||||||
|
|
||||||
|
// Notify and requested URLs of the new work availability
|
||||||
|
notifyWork()
|
||||||
|
|
||||||
case work := <-ethash.fetchWorkCh:
|
case work := <-ethash.fetchWorkCh:
|
||||||
// Return current mining work to remote miner.
|
// Return current mining work to remote miner.
|
||||||
miningWork, err := getWork()
|
if currentBlock == nil {
|
||||||
if err != nil {
|
work.errc <- errNoMiningWork
|
||||||
work.errc <- err
|
|
||||||
} else {
|
} else {
|
||||||
work.res <- miningWork
|
work.res <- currentWork
|
||||||
}
|
}
|
||||||
|
|
||||||
case result := <-ethash.submitWorkCh:
|
case result := <-ethash.submitWorkCh:
|
||||||
|
|
@ -284,6 +349,14 @@ func (ethash *Ethash) remote() {
|
||||||
delete(rates, id)
|
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:
|
case errc := <-ethash.exitCh:
|
||||||
// Exit remote loop if ethash is closed and return relevant error.
|
// Exit remote loop if ethash is closed and return relevant error.
|
||||||
|
|
|
||||||
211
consensus/ethash/sealer_test.go
Normal file
211
consensus/ethash/sealer_test.go
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Wait for server to start listening
|
||||||
|
var tries int
|
||||||
|
for tries = 0; tries < 10; tries++ {
|
||||||
|
conn, _ := net.DialTimeout("tcp", listener.Addr().String(), 1*time.Second)
|
||||||
|
if conn != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tries == 10 {
|
||||||
|
t.Fatal("tcp listener not ready for more than 10 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(3 * 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(3 * time.Second):
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
|
|
|
||||||
|
|
@ -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")))
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -43,7 +44,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -151,7 +151,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
cacheConfig: cacheConfig,
|
cacheConfig: cacheConfig,
|
||||||
db: db,
|
db: db,
|
||||||
triegc: prque.New(),
|
triegc: prque.New(nil),
|
||||||
stateCache: state.NewDatabase(db),
|
stateCache: state.NewDatabase(db),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
bodyCache: bodyCache,
|
bodyCache: bodyCache,
|
||||||
|
|
@ -899,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 {
|
||||||
|
|
@ -917,7 +915,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
||||||
} else {
|
} else {
|
||||||
// Full but not archive node, do proper garbage collection
|
// Full but not archive node, do proper garbage collection
|
||||||
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
|
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
|
||||||
bc.triegc.Push(root, -float32(block.NumberU64()))
|
bc.triegc.Push(root, -int64(block.NumberU64()))
|
||||||
|
|
||||||
if current := block.NumberU64(); current > triesInMemory {
|
if current := block.NumberU64(); current > triesInMemory {
|
||||||
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
||||||
|
|
@ -955,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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,7 +445,7 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
|
||||||
func (c *ChainIndexer) loadValidSections() {
|
func (c *ChainIndexer) loadValidSections() {
|
||||||
data, _ := c.indexDb.Get([]byte("count"))
|
data, _ := c.indexDb.Get([]byte("count"))
|
||||||
if len(data) == 8 {
|
if len(data) == 8 {
|
||||||
c.storedSections = binary.BigEndian.Uint64(data[:])
|
c.storedSections = binary.BigEndian.Uint64(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ func (b *BlockGen) SetExtra(data []byte) {
|
||||||
b.header.Extra = data
|
b.header.Extra = data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the nonce field of the generated block.
|
||||||
|
func (b *BlockGen) SetNonce(nonce types.BlockNonce) {
|
||||||
|
b.header.Nonce = nonce
|
||||||
|
}
|
||||||
|
|
||||||
// AddTx adds a transaction to the generated block. If no coinbase has
|
// AddTx adds a transaction to the generated block. If no coinbase has
|
||||||
// been set, the block's coinbase is set to the zero address.
|
// been set, the block's coinbase is set to the zero address.
|
||||||
//
|
//
|
||||||
|
|
@ -190,13 +195,14 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
|
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
|
||||||
misc.ApplyDAOHardFork(statedb)
|
misc.ApplyDAOHardFork(statedb)
|
||||||
}
|
}
|
||||||
// Execute any user modifications to the block and finalize it
|
// Execute any user modifications to the block
|
||||||
if gen != nil {
|
if gen != nil {
|
||||||
gen(i, b)
|
gen(i, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.engine != nil {
|
if b.engine != nil {
|
||||||
|
// Finalize and seal the block
|
||||||
block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
|
block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
|
||||||
|
|
||||||
// Write state changes to db
|
// Write state changes to db
|
||||||
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
|
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -240,7 +246,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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +110,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
||||||
*usedGas += gas
|
*usedGas += gas
|
||||||
|
|
||||||
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
|
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
|
||||||
// based on the eip phase, we're passing wether the root touch-delete accounts.
|
// based on the eip phase, we're passing whether the root touch-delete accounts.
|
||||||
receipt := types.NewReceipt(root, failed, *usedGas)
|
receipt := types.NewReceipt(root, failed, *usedGas)
|
||||||
receipt.TxHash = tx.Hash()
|
receipt.TxHash = tx.Hash()
|
||||||
receipt.GasUsed = gas
|
receipt.GasUsed = gas
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"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/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"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/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -971,11 +987,11 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||||
if pending > pool.config.GlobalSlots {
|
if pending > pool.config.GlobalSlots {
|
||||||
pendingBeforeCap := pending
|
pendingBeforeCap := pending
|
||||||
// Assemble a spam order to penalize large transactors first
|
// Assemble a spam order to penalize large transactors first
|
||||||
spammers := prque.New()
|
spammers := prque.New(nil)
|
||||||
for addr, list := range pool.pending {
|
for addr, list := range pool.pending {
|
||||||
// Only evict transactions from high rollers
|
// Only evict transactions from high rollers
|
||||||
if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
|
if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
|
||||||
spammers.Push(addr, float32(list.Len()))
|
spammers.Push(addr, int64(list.Len()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Gradually drop transactions from offenders
|
// Gradually drop transactions from offenders
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ func LogsBloom(logs []*Log) *big.Int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func bloom9(b []byte) *big.Int {
|
func bloom9(b []byte) *big.Int {
|
||||||
b = crypto.Keccak256(b[:])
|
b = crypto.Keccak256(b)
|
||||||
|
|
||||||
r := new(big.Int)
|
r := new(big.Int)
|
||||||
|
|
||||||
|
|
@ -130,7 +130,7 @@ var Bloom9 = bloom9
|
||||||
|
|
||||||
func BloomLookup(bin Bloom, topic bytesBacked) bool {
|
func BloomLookup(bin Bloom, topic bytesBacked) bool {
|
||||||
bloom := bin.Big()
|
bloom := bin.Big()
|
||||||
cmp := bloom9(topic.Bytes()[:])
|
cmp := bloom9(topic.Bytes())
|
||||||
|
|
||||||
return bloom.And(bloom, cmp).Cmp(cmp) == 0
|
return bloom.And(bloom, cmp).Cmp(cmp) == 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
|
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
|
||||||
func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
|
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
|
||||||
if contract.CodeAddr != nil {
|
if contract.CodeAddr != nil {
|
||||||
precompiles := PrecompiledContractsHomestead
|
precompiles := PrecompiledContractsHomestead
|
||||||
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
|
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
|
||||||
|
|
@ -61,7 +61,7 @@ func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
|
||||||
}(evm.interpreter)
|
}(evm.interpreter)
|
||||||
evm.interpreter = interpreter
|
evm.interpreter = interpreter
|
||||||
}
|
}
|
||||||
return interpreter.Run(contract, input)
|
return interpreter.Run(contract, input, readOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, ErrNoCompatibleInterpreter
|
return nil, ErrNoCompatibleInterpreter
|
||||||
|
|
@ -210,7 +210,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ret, err = run(evm, contract, input)
|
ret, err = run(evm, contract, input, false)
|
||||||
|
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
|
|
@ -255,7 +255,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
contract := NewContract(caller, to, value, gas)
|
contract := NewContract(caller, to, value, gas)
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
|
|
||||||
ret, err = run(evm, contract, input)
|
ret, err = run(evm, contract, input, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != errExecutionReverted {
|
||||||
|
|
@ -288,7 +288,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
|
|
||||||
ret, err = run(evm, contract, input)
|
ret, err = run(evm, contract, input, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != errExecutionReverted {
|
||||||
|
|
@ -310,13 +310,6 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
// Make sure the readonly is only set if we aren't in readonly yet
|
|
||||||
// this makes also sure that the readonly flag isn't removed for
|
|
||||||
// child calls.
|
|
||||||
if !evm.interpreter.IsReadOnly() {
|
|
||||||
evm.interpreter.SetReadOnly(true)
|
|
||||||
defer func() { evm.interpreter.SetReadOnly(false) }()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
to = AccountRef(addr)
|
to = AccountRef(addr)
|
||||||
|
|
@ -331,7 +324,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
// when we're in Homestead this also counts for code storage gas errors.
|
// when we're in Homestead this also counts for code storage gas errors.
|
||||||
ret, err = run(evm, contract, input)
|
ret, err = run(evm, contract, input, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != errExecutionReverted {
|
||||||
|
|
@ -382,7 +375,7 @@ func (evm *EVM) create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
ret, err := run(evm, contract, nil)
|
ret, err := run(evm, contract, nil, false)
|
||||||
|
|
||||||
// check whether the max code size has been exceeded
|
// check whether the max code size has been exceeded
|
||||||
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
|
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
|
||||||
|
|
@ -427,7 +420,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
|
|
||||||
// Create2 creates a new contract using code as deployment code.
|
// Create2 creates a new contract using code as deployment code.
|
||||||
//
|
//
|
||||||
// The different between Create2 with Create is Create2 uses sha3(msg.sender ++ salt ++ init_code)[12:]
|
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
||||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), code)
|
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), code)
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *
|
||||||
defer interpreter.intPool.put(shift) // First operand back into the pool
|
defer interpreter.intPool.put(shift) // First operand back into the pool
|
||||||
|
|
||||||
if shift.Cmp(common.Big256) >= 0 {
|
if shift.Cmp(common.Big256) >= 0 {
|
||||||
if value.Sign() > 0 {
|
if value.Sign() >= 0 {
|
||||||
value.SetUint64(0)
|
value.SetUint64(0)
|
||||||
} else {
|
} else {
|
||||||
value.SetInt64(-1)
|
value.SetInt64(-1)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ type StateDB interface {
|
||||||
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool)
|
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM EVM
|
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
||||||
// depends on this context being implemented for doing subcalls and initialising new EVM contracts.
|
// depends on this context being implemented for doing subcalls and initialising new EVM contracts.
|
||||||
type CallContext interface {
|
type CallContext interface {
|
||||||
// Call another contract
|
// Call another contract
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ type Config struct {
|
||||||
type Interpreter interface {
|
type Interpreter interface {
|
||||||
// Run loops and evaluates the contract's code with the given input data and returns
|
// Run loops and evaluates the contract's code with the given input data and returns
|
||||||
// the return byte-slice and an error if one occurred.
|
// the return byte-slice and an error if one occurred.
|
||||||
Run(contract *Contract, input []byte) ([]byte, error)
|
Run(contract *Contract, input []byte, static bool) ([]byte, error)
|
||||||
// CanRun tells if the contract, passed as an argument, can be
|
// CanRun tells if the contract, passed as an argument, can be
|
||||||
// run by the current interpreter. This is meant so that the
|
// run by the current interpreter. This is meant so that the
|
||||||
// caller can do something like:
|
// caller can do something like:
|
||||||
|
|
@ -61,10 +61,6 @@ type Interpreter interface {
|
||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
CanRun([]byte) bool
|
CanRun([]byte) bool
|
||||||
// IsReadOnly reports if the interpreter is in read only mode.
|
|
||||||
IsReadOnly() bool
|
|
||||||
// SetReadOnly sets (or unsets) read only mode in the interpreter.
|
|
||||||
SetReadOnly(bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVMInterpreter represents an EVM interpreter
|
// EVMInterpreter represents an EVM interpreter
|
||||||
|
|
@ -125,7 +121,7 @@ func (in *EVMInterpreter) enforceRestrictions(op OpCode, operation operation, st
|
||||||
// It's important to note that any errors returned by the interpreter should be
|
// It's important to note that any errors returned by the interpreter should be
|
||||||
// considered a revert-and-consume-all-gas operation except for
|
// considered a revert-and-consume-all-gas operation except for
|
||||||
// errExecutionReverted which means revert-and-keep-gas-left.
|
// errExecutionReverted which means revert-and-keep-gas-left.
|
||||||
func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
|
||||||
if in.intPool == nil {
|
if in.intPool == nil {
|
||||||
in.intPool = poolOfIntPools.get()
|
in.intPool = poolOfIntPools.get()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
@ -138,6 +134,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
||||||
|
// Make sure the readOnly is only set if we aren't in readOnly yet.
|
||||||
|
// This makes also sure that the readOnly flag isn't removed for child calls.
|
||||||
|
if readOnly && !in.readOnly {
|
||||||
|
in.readOnly = true
|
||||||
|
defer func() { in.readOnly = false }()
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the previous call's return data. It's unimportant to preserve the old buffer
|
// Reset the previous call's return data. It's unimportant to preserve the old buffer
|
||||||
// as every returning call will return new data anyway.
|
// as every returning call will return new data anyway.
|
||||||
in.returnData = nil
|
in.returnData = nil
|
||||||
|
|
@ -263,13 +266,3 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err
|
||||||
func (in *EVMInterpreter) CanRun(code []byte) bool {
|
func (in *EVMInterpreter) CanRun(code []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsReadOnly reports if the interpreter is in read only mode.
|
|
||||||
func (in *EVMInterpreter) IsReadOnly() bool {
|
|
||||||
return in.readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadOnly sets (or unsets) read only mode in the interpreter.
|
|
||||||
func (in *EVMInterpreter) SetReadOnly(ro bool) {
|
|
||||||
in.readOnly = ro
|
|
||||||
}
|
|
||||||
|
|
|
||||||
28
crypto/bn256/LICENSE
Normal file
28
crypto/bn256/LICENSE
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2018 Péter Szilágyi. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||||
// This file is part of the go-ethereum library.
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
//
|
// in the LICENSE file.
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// +build amd64 arm64
|
// +build amd64 arm64
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||||
// This file is part of the go-ethereum library.
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
//
|
// in the LICENSE file.
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// +build gofuzz
|
// +build gofuzz
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||||
// This file is part of the go-ethereum library.
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
//
|
// in the LICENSE file.
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// +build !amd64,!arm64
|
// +build !amd64,!arm64
|
||||||
|
|
||||||
|
|
|
||||||
27
crypto/bn256/cloudflare/LICENSE
Normal file
27
crypto/bn256/cloudflare/LICENSE
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -110,7 +110,7 @@ TEXT ·gfpMul(SB),0,$160-24
|
||||||
MOVQ b+16(FP), SI
|
MOVQ b+16(FP), SI
|
||||||
|
|
||||||
// Jump to a slightly different implementation if MULX isn't supported.
|
// Jump to a slightly different implementation if MULX isn't supported.
|
||||||
CMPB runtime·support_bmi2(SB), $0
|
CMPB ·hasBMI2(SB), $0
|
||||||
JE nobmi2Mul
|
JE nobmi2Mul
|
||||||
|
|
||||||
mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI))
|
mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@ package bn256
|
||||||
// This file contains forward declarations for the architecture-specific
|
// This file contains forward declarations for the architecture-specific
|
||||||
// assembly implementations of these functions, provided that they exist.
|
// assembly implementations of these functions, provided that they exist.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:varcheck
|
||||||
|
var hasBMI2 = cpu.X86.HasBMI2
|
||||||
|
|
||||||
// go:noescape
|
// go:noescape
|
||||||
func gfpNeg(c, a *gfP)
|
func gfpNeg(c, a *gfP)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package bn256 implements a particular bilinear group at the 128-bit security level.
|
// Package bn256 implements a particular bilinear group.
|
||||||
//
|
//
|
||||||
// Bilinear groups are the basis of many of the new cryptographic protocols
|
// Bilinear groups are the basis of many of the new cryptographic protocols
|
||||||
// that have been proposed over the past decade. They consist of a triplet of
|
// that have been proposed over the past decade. They consist of a triplet of
|
||||||
|
|
@ -14,6 +14,10 @@
|
||||||
// Barreto-Naehrig curve as described in
|
// Barreto-Naehrig curve as described in
|
||||||
// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
|
// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
|
||||||
// with the implementation described in that paper.
|
// with the implementation described in that paper.
|
||||||
|
//
|
||||||
|
// (This package previously claimed to operate at a 128-bit security level.
|
||||||
|
// However, recent improvements in attacks mean that is no longer true. See
|
||||||
|
// https://moderncrypto.org/mail-archive/curves/2016/000740.html.)
|
||||||
package bn256
|
package bn256
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -50,8 +54,8 @@ func RandomG1(r io.Reader) (*big.Int, *G1, error) {
|
||||||
return k, new(G1).ScalarBaseMult(k), nil
|
return k, new(G1).ScalarBaseMult(k), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *G1) String() string {
|
func (e *G1) String() string {
|
||||||
return "bn256.G1" + g.p.String()
|
return "bn256.G1" + e.p.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurvePoints returns p's curve points in big integer
|
// CurvePoints returns p's curve points in big integer
|
||||||
|
|
@ -98,15 +102,19 @@ func (e *G1) Neg(a *G1) *G1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal converts n to a byte slice.
|
// Marshal converts n to a byte slice.
|
||||||
func (n *G1) Marshal() []byte {
|
func (e *G1) Marshal() []byte {
|
||||||
n.p.MakeAffine(nil)
|
|
||||||
|
|
||||||
xBytes := new(big.Int).Mod(n.p.x, P).Bytes()
|
|
||||||
yBytes := new(big.Int).Mod(n.p.y, P).Bytes()
|
|
||||||
|
|
||||||
// Each value is a 256-bit number.
|
// Each value is a 256-bit number.
|
||||||
const numBytes = 256 / 8
|
const numBytes = 256 / 8
|
||||||
|
|
||||||
|
if e.p.IsInfinity() {
|
||||||
|
return make([]byte, numBytes*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.p.MakeAffine(nil)
|
||||||
|
|
||||||
|
xBytes := new(big.Int).Mod(e.p.x, P).Bytes()
|
||||||
|
yBytes := new(big.Int).Mod(e.p.y, P).Bytes()
|
||||||
|
|
||||||
ret := make([]byte, numBytes*2)
|
ret := make([]byte, numBytes*2)
|
||||||
copy(ret[1*numBytes-len(xBytes):], xBytes)
|
copy(ret[1*numBytes-len(xBytes):], xBytes)
|
||||||
copy(ret[2*numBytes-len(yBytes):], yBytes)
|
copy(ret[2*numBytes-len(yBytes):], yBytes)
|
||||||
|
|
@ -175,8 +183,8 @@ func RandomG2(r io.Reader) (*big.Int, *G2, error) {
|
||||||
return k, new(G2).ScalarBaseMult(k), nil
|
return k, new(G2).ScalarBaseMult(k), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *G2) String() string {
|
func (e *G2) String() string {
|
||||||
return "bn256.G2" + g.p.String()
|
return "bn256.G2" + e.p.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurvePoints returns the curve points of p which includes the real
|
// CurvePoints returns the curve points of p which includes the real
|
||||||
|
|
@ -216,6 +224,13 @@ func (e *G2) Add(a, b *G2) *G2 {
|
||||||
|
|
||||||
// Marshal converts n into a byte slice.
|
// Marshal converts n into a byte slice.
|
||||||
func (n *G2) Marshal() []byte {
|
func (n *G2) Marshal() []byte {
|
||||||
|
// Each value is a 256-bit number.
|
||||||
|
const numBytes = 256 / 8
|
||||||
|
|
||||||
|
if n.p.IsInfinity() {
|
||||||
|
return make([]byte, numBytes*4)
|
||||||
|
}
|
||||||
|
|
||||||
n.p.MakeAffine(nil)
|
n.p.MakeAffine(nil)
|
||||||
|
|
||||||
xxBytes := new(big.Int).Mod(n.p.x.x, P).Bytes()
|
xxBytes := new(big.Int).Mod(n.p.x.x, P).Bytes()
|
||||||
|
|
@ -223,9 +238,6 @@ func (n *G2) Marshal() []byte {
|
||||||
yxBytes := new(big.Int).Mod(n.p.y.x, P).Bytes()
|
yxBytes := new(big.Int).Mod(n.p.y.x, P).Bytes()
|
||||||
yyBytes := new(big.Int).Mod(n.p.y.y, P).Bytes()
|
yyBytes := new(big.Int).Mod(n.p.y.y, P).Bytes()
|
||||||
|
|
||||||
// Each value is a 256-bit number.
|
|
||||||
const numBytes = 256 / 8
|
|
||||||
|
|
||||||
ret := make([]byte, numBytes*4)
|
ret := make([]byte, numBytes*4)
|
||||||
copy(ret[1*numBytes-len(xxBytes):], xxBytes)
|
copy(ret[1*numBytes-len(xxBytes):], xxBytes)
|
||||||
copy(ret[2*numBytes-len(xyBytes):], xyBytes)
|
copy(ret[2*numBytes-len(xyBytes):], xyBytes)
|
||||||
|
|
|
||||||
|
|
@ -245,11 +245,19 @@ func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoi
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets
|
||||||
|
// c to 0 : 1 : 0.
|
||||||
func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint {
|
func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint {
|
||||||
if words := c.z.Bits(); len(words) == 1 && words[0] == 1 {
|
if words := c.z.Bits(); len(words) == 1 && words[0] == 1 {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
if c.IsInfinity() {
|
||||||
|
c.x.SetInt64(0)
|
||||||
|
c.y.SetInt64(1)
|
||||||
|
c.z.SetInt64(0)
|
||||||
|
c.t.SetInt64(0)
|
||||||
|
return c
|
||||||
|
}
|
||||||
zInv := pool.Get().ModInverse(c.z, P)
|
zInv := pool.Get().ModInverse(c.z, P)
|
||||||
t := pool.Get().Mul(c.y, zInv)
|
t := pool.Get().Mul(c.y, zInv)
|
||||||
t.Mod(t, P)
|
t.Mod(t, P)
|
||||||
|
|
|
||||||
|
|
@ -225,11 +225,19 @@ func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoi
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets
|
||||||
|
// c to 0 : 1 : 0.
|
||||||
func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint {
|
func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint {
|
||||||
if c.z.IsOne() {
|
if c.z.IsOne() {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
if c.IsInfinity() {
|
||||||
|
c.x.SetZero()
|
||||||
|
c.y.SetOne()
|
||||||
|
c.z.SetZero()
|
||||||
|
c.t.SetZero()
|
||||||
|
return c
|
||||||
|
}
|
||||||
zInv := newGFp2(pool).Invert(c.z, pool)
|
zInv := newGFp2(pool).Invert(c.z, pool)
|
||||||
t := newGFp2(pool).Mul(c.y, zInv, pool)
|
t := newGFp2(pool).Mul(c.y, zInv, pool)
|
||||||
zInv2 := newGFp2(pool).Square(zInv, pool)
|
zInv2 := newGFp2(pool).Square(zInv, pool)
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ func CreateAddress(b common.Address, nonce uint64) common.Address {
|
||||||
|
|
||||||
// CreateAddress2 creates an ethereum address given the address bytes, initial
|
// CreateAddress2 creates an ethereum address given the address bytes, initial
|
||||||
// contract code and a salt.
|
// contract code and a salt.
|
||||||
func CreateAddress2(b common.Address, salt common.Hash, code []byte) common.Address {
|
func CreateAddress2(b common.Address, salt [32]byte, code []byte) common.Address {
|
||||||
return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt.Bytes(), code)[12:])
|
return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], Keccak256(code))[12:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToECDSA creates a private key with the given D value.
|
// ToECDSA creates a private key with the given D value.
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func Sign(msg []byte, seckey []byte) ([]byte, error) {
|
||||||
return sig, nil
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecoverPubkey returns the the public key of the signer.
|
// RecoverPubkey returns the public key of the signer.
|
||||||
// msg must be the 32-byte hash of the message to be signed.
|
// msg must be the 32-byte hash of the message to be signed.
|
||||||
// sig must be a 65-byte compact ECDSA signature containing the
|
// sig must be a 65-byte compact ECDSA signature containing the
|
||||||
// recovery id as the last element.
|
// recovery id as the last element.
|
||||||
|
|
|
||||||
53
eth/api.go
53
eth/api.go
|
|
@ -24,7 +24,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
@ -33,7 +35,6 @@ import (
|
||||||
"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/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
@ -93,47 +94,22 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
|
||||||
return &PrivateMinerAPI{e: e}
|
return &PrivateMinerAPI{e: e}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the miner with the given number of threads. If threads is nil the number
|
// Start starts the miner with the given number of threads. If threads is nil,
|
||||||
// of workers started is equal to the number of logical CPUs that are usable by
|
// the number of workers started is equal to the number of logical CPUs that are
|
||||||
// this process. If mining is already running, this method adjust the number of
|
// usable by this process. If mining is already running, this method adjust the
|
||||||
// threads allowed to use and updates the minimum price required by the transaction
|
// number of threads allowed to use and updates the minimum price required by the
|
||||||
// pool.
|
// transaction pool.
|
||||||
func (api *PrivateMinerAPI) Start(threads *int) error {
|
func (api *PrivateMinerAPI) Start(threads *int) error {
|
||||||
// Set the number of threads if the seal engine supports it
|
|
||||||
if threads == nil {
|
if threads == nil {
|
||||||
threads = new(int)
|
return api.e.StartMining(runtime.NumCPU())
|
||||||
} else if *threads == 0 {
|
|
||||||
*threads = -1 // Disable the miner from within
|
|
||||||
}
|
}
|
||||||
type threaded interface {
|
return api.e.StartMining(*threads)
|
||||||
SetThreads(threads int)
|
|
||||||
}
|
|
||||||
if th, ok := api.e.engine.(threaded); ok {
|
|
||||||
log.Info("Updated mining threads", "threads", *threads)
|
|
||||||
th.SetThreads(*threads)
|
|
||||||
}
|
|
||||||
// Start the miner and return
|
|
||||||
if !api.e.IsMining() {
|
|
||||||
// Propagate the initial price point to the transaction pool
|
|
||||||
api.e.lock.RLock()
|
|
||||||
price := api.e.gasPrice
|
|
||||||
api.e.lock.RUnlock()
|
|
||||||
api.e.txPool.SetGasPrice(price)
|
|
||||||
return api.e.StartMining(true)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the miner
|
// Stop terminates the miner, both at the consensus engine level as well as at
|
||||||
func (api *PrivateMinerAPI) Stop() bool {
|
// the block creation level.
|
||||||
type threaded interface {
|
func (api *PrivateMinerAPI) Stop() {
|
||||||
SetThreads(threads int)
|
|
||||||
}
|
|
||||||
if th, ok := api.e.engine.(threaded); ok {
|
|
||||||
th.SetThreads(-1)
|
|
||||||
}
|
|
||||||
api.e.StopMining()
|
api.e.StopMining()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExtra sets the extra data string that is included when this miner mines a block.
|
// SetExtra sets the extra data string that is included when this miner mines a block.
|
||||||
|
|
@ -160,6 +136,11 @@ func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRecommitInterval updates the interval for miner sealing work recommitting.
|
||||||
|
func (api *PrivateMinerAPI) SetRecommitInterval(interval int) {
|
||||||
|
api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHashrate returns the current hashrate of the miner.
|
// GetHashrate returns the current hashrate of the miner.
|
||||||
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
||||||
return api.e.miner.HashRate()
|
return api.e.miner.HashRate()
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue